commit 26afc66bc9689672b03b8df1c78cf72e25e3bea2 Author: 廖德云 Date: Mon Nov 24 22:54:02 2025 +0800 ruoyi-geek-springboot3 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d92e2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### JRebel ### +rebel.xml + +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp +*.lck + +!*/build/*.java +!*/build/*.html +!*/build/*.xml + +effective-pom.xml \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..06bf337 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "RuoYiApplication", + "request": "launch", + "mainClass": "com.ruoyi.RuoYiApplication", + "projectName": "ruoyi-admin", + "vmArgs": "-Dfile.encoding=GBK " + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ee9e343 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,65 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive", + "java.compile.nullAnalysis.mode": "disabled", + "maven.executable.options": "-T 4", + "maven.pomfile.autoUpdateEffectivePOM": true, + "java.debug.settings.hotCodeReplace": "auto", + "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=9 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx4G -Xms4G -Xlog:disable", + "maven.excludedFolders": [ + "**/.vscode", + "**/.idea", + "**/target", + "**/.*", + "**/node_modules", + "**/target", + "**/bin", + "**/archetype-resources" + ], + "boot-java.rewrite.refactorings.on": true, + "maven.executable.preferMavenWrapper": true, + "java.import.maven.enabled": true, + "dbcode.connections": [ + { + "connectionId": "btr-_nFe7R0oOvCj0mMun", + "name": "ry-mysql", + "driver": "mysql", + "connectionType": "host", + "host": "127.0.0.1", + "port": 3306, + "ssl": false, + "username": "root", + "password": "123456", + "savePassword": "secretStorage", + "database": "ry", + "connectionTimeout": 30, + "driverOptions": { + "retrievePublickey": true + } + }, + { + "connectionId": "7NX2UhXl__9t3Ca6TzEsB", + "name": "ry-postgres", + "driver": "postgres", + "connectionType": "host", + "host": "127.0.0.1", + "port": 5432, + "ssl": false, + "username": "postgres", + "password": "123456", + "savePassword": "secretStorage", + "connectionTimeout": 30 + }, + { + "connectionId": "fNsY4HlOb21w_5TnIGy_d", + "name": "localhost", + "driver": "redis", + "connectionType": "host", + "host": "127.0.0.1", + "port": 6379, + "ssl": false, + "savePassword": "na", + "readOnly": false, + "connectionTimeout": 30 + } + ], +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8564f29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 RuoYi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..edb7ccf --- /dev/null +++ b/README.md @@ -0,0 +1,219 @@ +

+ + logo + + + + + logo + +

+

RuoYi-Geek v3.8.9-G

+

基于SpringBoot3+Vue3前后端分离的Java快速开发框架

+

+ +

+ +# 当前版本是3.8.9-G + +本人的其他两个推荐搭配的项目 + +1. [RuoYi-App-Geek: 这是若依极客生态的小程序版本 (gitee.com)](https://gitee.com/geek-xd/geek-uniapp-vue3-uview-plus-uchart) +2. [RuoYi-Vue3-Geek: 这是若依极客生态的Vue3版本 (gitee.com)](https://gitee.com/geek-xd/ruo-yi-vue3-geek) + +与本项目同为一个作者开发,兼容性最好,学习成本最低。 + +# 引言 + +RuoYi-Vue与RuoYi-App是基于SpringBoot2+Vue2打造的企业级开发框架,得到了广大开发者的喜爱和积极反馈。随着技术的迭代进步,SpringBoot3与Vue3逐渐进入开发者的视野。为了满足开发者对于新技术的追求,RuoYi官方文档提供了SpringBoot2至SpringBoot3的升级方法。与此同时,社区也涌现出了RuoYi-Vue3、RuoYi-App-Vue3的版本,展现了开发者社区对于技术升级的热情与努力。 + +然而,在升级的过程中,官方的方法为了兼顾Java1.8的特性与一些老旧的方法,未完全拥抱SpringBoot3与Java17的全部特性。而社区的RuoYi-Vue3、RuoYi-App-Vue3版本由于出自不同的团队之手,兼容性及整合性上存在些许不足。更为关键的是,尽管这些版本支持TypeScript,但缺乏与之相匹配的tsconfig.json配置文件,这使得在主流编辑器如VSCode中,TypeScript的语法提示环境并未达到最佳状态。 + +鉴于此,RuoYi-Geek生态应运而生。它旨在为广大开发者提供一个既保留原版本核心特性,又整合社区版优点的全新解决方案。在RuoYi-Geek中,我们深入调研了企业开发中常用的RuoYi扩展,并直接在框架中集成,确保开发者能够快速上手,高效开发。同时,我们采用了最新的SpringBoot3+Vue3技术栈,彻底移除了为了兼容Java1.8而保留的老旧方法。更为重要的是,我们为TypeScript开发环境加入了常用的tsconfig.json配置,使得开发者在VSCode等编辑器中能够获得更为舒适、便捷的语法提示体验。 + +RuoYi-Geek不仅仅是一个简单的升级版本,更是对于RuoYi生态的一次全面优化与整合。我们相信,通过RuoYi-Geek,开发者将能够更为高效、愉悦地开发出优秀的企业级应用。 + +## 平台简介 + +若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 + +* 前端采用Vue3、Element Plus。 +* 后端采用Spring Boot3、Spring Security、Redis & Jwt。 +* 权限认证使用Jwt,支持多终端认证系统。 +* 支持加载动态权限菜单,多方式轻松权限控制。 +* 高效率开发,使用代码生成器可以一键生成前后端代码。 +* 多数据源与分库分表默认集成 +* 所有非基本模块可随意插拔,让开发更加简单高效 +* 提供了多个工具模块助力开发,如:在线接口模块、mybatis-jpa模块 +* 提供了多个常见业务模块简化开发,如:第三方认证模块、支付模块 +* 提供了多个常见的服务模块集成开发,如:websocket模块、minio模块 +* 特别鸣谢:[element](https://github.com/ElemeFE/element),[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin),[eladmin-web](https://github.com/elunez/eladmin-web)。 +* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)   +* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)   + +## 本项目与原项目的区别 + +### 核心 + +**模块化架构设计,支持各个模块的快速安拆,对第三方认证、第三方支付模块设计了基础的规范和基础模块。** + +### 细节 + +1. 改用SpringBoot3+java17的更新的技术栈并改掉所有的弃用的方法,全面拥抱java17的全新特性。 +2. 升级了代码生成器(配合本项目的vue3版本才可用),使关联表生成更加简单! +3. 改用最新版本的SpringSecurity安全框架,以及采用最新的lambda 表达式的配置方式,更加通俗易懂! +4. 自动Api文档以springfox替代springdoc来适配knife4j框架的4.x版本,更好的适配springboot3! +5. 默认引入mybatis-plus增强mybatis,并自创工具模块mybatis-jpa简化CRUD! +6. 默认引入lombok简化代码(注:基础模块并未使用mybatis-plus和lombok,对这两个扩展有争议的小伙伴可以直接删除,不会影响到框架本身滴,以及knife4j直接删除也不会影响到springdoc,主要还是为了方便咱们开发者呢!) +7. 提供了大量可随意插拔的模块,助力快速开发!同时ruoyi插件集成中的常用插件也以模块的形式直接集成在了项目中,也是可以随意插拔的嗷~ + +## 模块介绍(简单开发必看) + +* 最简单的开发就是删除所有的可移除模块,按需添加模块。 +* 测试中的模块请自己使用的时候一定要测试一下。 +* 对于小白,开发中的模块请直接删除。 + +``` + + +com.ruoyi +├── common // 工具类 +│ └── annotation // 自定义注解 +│ └── config // 全局配置 +│ └── constant // 通用常量 +│ └── core // 核心控制 +│ └── enums // 通用枚举 +│ └── exception // 通用异常 +│ └── filter // 过滤器处理 +│ └── utils // 通用类处理 +├── framework // 框架核心 +│ └── aspectj // 注解实现 +│ └── config // 系统配置 +│ └── datasource // 数据权限 +│ └── interceptor // 拦截器 +│ └── manager // 异步处理 +│ └── security // 权限控制 +│ └── web // 前端控制 +├── ruoyi-admin // 后台服务 +├── ruoyi-pay // 支付场景(基本业务完成) +│ └── common // 支付框架基础模块(基础框架搭建完成) +│ └── sqb // 收钱吧支付模块(功能可用,业务需要开发) +│ └── wx // 微信支付模块(基础业务完成) +│ └── alipay // 支付宝支付模块(基础业务完成) +│ └── starter // 支付场景启动器 +├── ruoyi-auth // 第三方认证场景(基础框架搭建完成) +│ └── common // 第三方认证基础模块(开发中) +│ └── justauth // 网站第三方认证模块(测试中,参照若依扩展改进,因没有这么多场景的code,请大家测试出问题后help解决一下发出来) +│ └── wx // 微信小程序认证模块(测试中) +│ └── phone // 手机认证模块(基础业务完成) +│ └── email // 邮箱认证模块(基础业务完成) +│ └── starter // 第三方认证启动器 +├── ruoyi-file // 文件服务系统(支持公开访问、临时凭证、分片上传等常见功能) +│ └── common // 基础模块和本地磁盘方式 +│ └── minio // minio分布式文件服务 +│ └── oss-alibaba // alibaba的oss云储存服务 +│ └── starter // 第三方认证启动器 +├── ruoyi-models // 业务场景 +│ └── online // 在线开发模块(可移除) +│ └── quartz // 定时任务(可移除) +│ └── generator // 代码生成(可移除) +│ └── form // 自定义在线表单(可移除) +│ └── flowable // 流程管理(可移除)(依赖于form模块) +│ └── message // 消息模块(可移除)(可以与phone、email模块协同) +│ └── starter // 业务场景启动器 +├── ruoyi-plugins // 插件 +│ └── ehcache // ehcache缓存插件(与redis模块同类,两者二选一) +│ └── mybatis-jpa // mybatis-jpa插件(可移除)(简化CRUD,以数据模型为基础开发) +│ └── mybatis-plus // mybatis-plus插件(可移除)(简化CRUD) +│ └── mybatis-interceptor // mybatis-interceptor插件(可移除)(简化数据鉴权和分页,扩展性强) +│ └── atomikos // atomikos分布式事务插件(可移除) +│ └── netty // netty插件(可移除) +│ └── rabbitmq // rabbitmq队列服务模块 +│ └── redis // redis缓存服务模块(与ehcache插件同类,两者二选一) +│ └── websocket // websocket插件(可移除) +│ └── starter // 插件整合模块 +├── ruoyi-system // 系统代码 +``` + +## 内置功能 + +1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 +2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 +3. 岗位管理:配置系统用户所属担任职务。 +4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。 +5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 +6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 +7. 参数管理:对系统动态配置常用参数。 +8. 通知公告:系统通知公告信息发布维护。 +9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 +10. 登录日志:系统登录日志记录查询包含登录异常。 +11. 在线用户:当前系统中活跃用户状态监控。 +12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 +13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 +14. 系统接口:根据业务代码自动生成相关的api接口文档。 +15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 +16. 缓存监控:对系统的缓存信息查询,命令统计等。 +17. 在线构建器:拖动表单元素生成相应的HTML代码。 +18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 +19. 支付场景 +20. 第三方登录场景 +21. 中间件场景 + +## 演示图 + +### 新加功能和增强功能演示 + + + + + + + + + + + + + + +
+ +### 原有功能演示 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +# 联系我们: + +QQ交流群:744785891 diff --git a/bin/clean.bat b/bin/clean.bat new file mode 100644 index 0000000..24c0974 --- /dev/null +++ b/bin/clean.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] target· +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean + +pause \ No newline at end of file diff --git a/bin/package.bat b/bin/package.bat new file mode 100644 index 0000000..c693ec0 --- /dev/null +++ b/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] Weḅwar/jarļ +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean package -Dmaven.test.skip=true + +pause \ No newline at end of file diff --git a/bin/run.bat b/bin/run.bat new file mode 100644 index 0000000..41efbd0 --- /dev/null +++ b/bin/run.bat @@ -0,0 +1,14 @@ +@echo off +echo. +echo [Ϣ] ʹJarWeb̡ +echo. + +cd %~dp0 +cd ../ruoyi-admin/target + +set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m + +java -jar %JAVA_OPTS% ruoyi-admin.jar + +cd bin +pause \ No newline at end of file diff --git a/doc/image/code-edit.png b/doc/image/code-edit.png new file mode 100644 index 0000000..5fc972b Binary files /dev/null and b/doc/image/code-edit.png differ diff --git a/doc/image/code-show.png b/doc/image/code-show.png new file mode 100644 index 0000000..24378e8 Binary files /dev/null and b/doc/image/code-show.png differ diff --git a/doc/image/form-edit.png b/doc/image/form-edit.png new file mode 100644 index 0000000..4054d34 Binary files /dev/null and b/doc/image/form-edit.png differ diff --git a/doc/image/logo.png b/doc/image/logo.png new file mode 100644 index 0000000..dab35ca Binary files /dev/null and b/doc/image/logo.png differ diff --git a/doc/image/online-mb-code.png b/doc/image/online-mb-code.png new file mode 100644 index 0000000..45a4973 Binary files /dev/null and b/doc/image/online-mb-code.png differ diff --git a/doc/image/online-mb-edit.png b/doc/image/online-mb-edit.png new file mode 100644 index 0000000..6f3b378 Binary files /dev/null and b/doc/image/online-mb-edit.png differ diff --git a/doc/image/online-mb-list.png b/doc/image/online-mb-list.png new file mode 100644 index 0000000..0e1c032 Binary files /dev/null and b/doc/image/online-mb-list.png differ diff --git a/doc/代码生成.drawio b/doc/代码生成.drawio new file mode 100644 index 0000000..c892fbe --- /dev/null +++ b/doc/代码生成.drawio @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/关于多数据源与分布式事务.drawio b/doc/关于多数据源与分布式事务.drawio new file mode 100644 index 0000000..54ac704 --- /dev/null +++ b/doc/关于多数据源与分布式事务.drawio @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/微信登录.drawio b/doc/微信登录.drawio new file mode 100644 index 0000000..509b195 --- /dev/null +++ b/doc/微信登录.drawio @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/支付流程图.drawio b/doc/支付流程图.drawio new file mode 100644 index 0000000..184cb9f --- /dev/null +++ b/doc/支付流程图.drawio @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/支付系统订单状态图.drawio b/doc/支付系统订单状态图.drawio new file mode 100644 index 0000000..ce41fa8 --- /dev/null +++ b/doc/支付系统订单状态图.drawio @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/权限控制.md b/doc/权限控制.md new file mode 100644 index 0000000..be6d0a7 --- /dev/null +++ b/doc/权限控制.md @@ -0,0 +1,68 @@ +### 与权限有关的注解 + +`@Anonymous`注解用于配置公开接口 + +`@PreAuthorize`注解用于配置接口要求用户拥有某些权限才可访问,它拥有如下方法 + +| 方法 | 参数 | 描述 | +| ----------- | ------ | ---------------------------------------------- | +| hasPermi | String | 验证用户是否具备某权限 | +| lacksPermi | String | 验证用户是否不具备某权限,与 hasPermi逻辑相反 | +| hasAnyPermi | String | 验证用户是否具有以下任意一个权限 | +| hasRole | String | 判断用户是否拥有某个角色 | +| lacksRole | String | 验证用户是否不具备某角色,与 isRole逻辑相反 | +| hasAnyRoles | String | 验证用户是否具有以下任意一个角色,多个逗号分隔 | + +```java +@PreAuthorize("@ss.hasPermi('system:user:list')") +@PreAuthorize("@ss.lacksPermi('system:user:list')") +@PreAuthorize("@ss.hasAnyPermi('system:user:add,system:user:edit')") +``` + +`@DataScope`注解用于配置接口数据权限 + +* `deptAlias`用于指定部门表的别名; +* `userAlias`用于指定用户表的别名; +* 实体需要继承BaseEntity类; +* `全部数据权限`、`自定数据权限`、`部门数据权限`、`部门及以下数据权限`、`仅本人数据权限`五种权限模式在后台角色管理界面配置数据权限 + +```java +// 部门数据权限注解 +@DataScope(deptAlias = "d") +// 部门及用户权限注解 +@DataScope(deptAlias = "d", userAlias = "u") +``` + +1. 使用注解 + +```java + +@DataScope(deptAlias = "d", userAlias = "u") +public List<...> select(...) +{ + return mapper.select(...); +} +``` + +2. 配置mybatis的xml + +```xml + +``` + +### 安全工具类 + +com.ruoyi.common.utils.SecurityUtils + +| 方法 | 参数 | 返回 | 描述 | +| ------------ | ------ | ---------- | ------------------------ | +| getUserId | 无 | Long | 获取当前用户ID | +| getDeptId | 无 | Long | 获取当前部门ID | +| getUsername | 无 | String | 获取当前用户账户 | +| getLoginUser | 无 | LonginUser | 获取当前登录用户代理 | +| hasPermi | String | boolean | 验证用户是否具备某权限 | +| hasRole | String | boolean | 验证用户是否拥有某个角色 | diff --git a/doc/模块依赖关系.drawio b/doc/模块依赖关系.drawio new file mode 100644 index 0000000..6b2ebac --- /dev/null +++ b/doc/模块依赖关系.drawio @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/登录+JWT逻辑.drawio b/doc/登录+JWT逻辑.drawio new file mode 100644 index 0000000..9d05d20 --- /dev/null +++ b/doc/登录+JWT逻辑.drawio @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/若依环境使用手册.docx b/doc/若依环境使用手册.docx new file mode 100644 index 0000000..9e4daef Binary files /dev/null and b/doc/若依环境使用手册.docx differ diff --git a/doc/邮箱或短信验证码登录注册重置流程图.drawio b/doc/邮箱或短信验证码登录注册重置流程图.drawio new file mode 100644 index 0000000..7e623e9 --- /dev/null +++ b/doc/邮箱或短信验证码登录注册重置流程图.drawio @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/限流逻辑.drawio b/doc/限流逻辑.drawio new file mode 100644 index 0000000..7ad47d0 --- /dev/null +++ b/doc/限流逻辑.drawio @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f8532e9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,361 @@ + + + 4.0.0 + + com.ruoyi.geekxd + ruoyi + 3.9.0-G + + ruoyi + https://gitee.com/geek-xd/ruoyi-geek-springboot3.git + 若依Geek管理系统 + + + 3.9.0-G + UTF-8 + UTF-8 + 21 + 3.1.1 + 3.5.6 + 1.2.27 + 1.1 + + 1.21 + + 2.3.3 + 2.1.1 + 2.0.58 + 2.19.2 + 6.8.3 + 2.20.0 + 4.5.0 + 5.4.1 + 5.5 + 2.4.1 + 0.12.6 + 4.5.0 + 3.0.4 + 3.5.16 + + 8.3.0 + 42.7.7 + 4.0.2 + 4.0.5 + 2.8.9 + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + com.mysql + mysql-connector-j + ${mysql.version} + runtime + + + + + org.postgresql + postgresql + ${postgresql.version} + runtime + + + + + com.alibaba + druid-spring-boot-3-starter + ${druid.version} + + + + javax.transaction + jta + ${jta.version} + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot.version} + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${pagehelper.boot.version} + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + + commons-io + commons-io + ${commons-io.version} + + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + + org.apache.httpcomponents.client5 + httpclient5 + ${httpclient5.version} + + + + + org.apache.commons + commons-collections4 + ${commons.collections.version} + + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson.version} + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + + + + io.jsonwebtoken + jjwt-api + ${jwt.version} + + + + io.jsonwebtoken + jjwt-impl + ${jwt.version} + + + + io.jsonwebtoken + jjwt-jackson + ${jwt.version} + + + + + jakarta.xml.bind + jakarta.xml.bind-api + ${jaxb-api.version} + + + com.sun.xml.bind + jaxb-impl + ${jaxb.version} + + + com.sun.xml.bind + jaxb-core + ${jaxb.version} + + + + + eu.bitwalker + UserAgentUtils + ${bitwalker.version} + + + + + pro.fessional + kaptcha + ${kaptcha.version} + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + ${knife4j.version} + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + + + + + com.ruoyi.geekxd + ruoyi-framework + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-system + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-common + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-auth-starter + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-pay-starter + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-file-starter + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-plugins-starter + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-models-starter + ${ruoyi.version} + + + + + + ruoyi-admin + ruoyi-framework + ruoyi-system + ruoyi-common + ruoyi-scene-auth + ruoyi-scene-pay + ruoyi-scene-file + ruoyi-plugins + ruoyi-models + ruoyi-models/ruoyi-labMeter + + + + pom + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + com.ruoyi.RuoYiApplication + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + true + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + false + + + + + \ No newline at end of file diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml new file mode 100644 index 0000000..618c779 --- /dev/null +++ b/ruoyi-admin/pom.xml @@ -0,0 +1,114 @@ + + + + ruoyi + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + jar + ruoyi-admin + + + web服务入口 + + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + org.springframework.boot + spring-boot-devtools + true + + + + + com.mysql + mysql-connector-j + + + + + org.postgresql + postgresql + + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + + + + + com.ruoyi.geekxd + ruoyi-framework + + + + + com.ruoyi.geekxd + ruoyi-auth-starter + + + + + com.ruoyi.geekxd + ruoyi-pay-starter + + + + + com.ruoyi.geekxd + ruoyi-file-starter + + + + + com.ruoyi.geekxd + ruoyi-plugins-starter + + + + + com.ruoyi.geekxd + ruoyi-models-starter + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.1.0 + + false + ${project.artifactId} + + + + ${project.artifactId} + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java new file mode 100644 index 0000000..95d4467 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java @@ -0,0 +1,45 @@ +package com.ruoyi; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.Environment; + +/** + * 启动程序 + * + * @author ruoyi + */ +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) + + public class RuoYiApplication { + public static void main(String[] args) throws UnknownHostException { + // System.setProperty("spring.devtools.restart.enabled", "false"); + ConfigurableApplicationContext application = SpringApplication.run(RuoYiApplication.class, args); + System.out.println("(♥◠‿◠)ノ゙ 若依极客启动成功 ლ(´ڡ`ლ)゙ \n" + + " .-------. ____ __ \n" + + " | _ _ \\ \\ \\ / / \n" + + " | ( ' ) | \\ _. / ' \n" + + " |(_ o _) / _( )_ .' \n" + + " | (_,_).' __ ___(_ o _)' " + " ____ _ " + "\n" + + " | |\\ \\ | || |(_,_)' " + " / ___| ___ ___| | __ " + "\n" + + " | | \\ `' /| `-' / " + "| | _ / _ \\/ _ \\ |/ / " + "\n" + + " | | \\ / \\ / " + " | |_| | __/ __/ < " + "\n" + + " ''-' `'-' `-..-' " + "\\____|\\___|\\___|_|\\_\\"); + + Environment env = application.getEnvironment(); + String ip = InetAddress.getLocalHost().getHostAddress(); + String port = env.getProperty("server.port"); + System.out.println("\n----------------------------------------------------------\n" + + " Application Ruoyi-Geek is running! Access URLs:\n" + + " Local: http://localhost:" + port + "/\n" + + " External: http://" + ip + ":" + port + "/\n" + + " Swagger文档: http://" + ip + ":" + port + "/swagger-ui/index.html\n" + + " Knife4j文档: http://" + ip + ":" + port + "/doc.html" + "" + "\n" + + "----------------------------------------------------------"); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java new file mode 100644 index 0000000..6de67dc --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java @@ -0,0 +1,18 @@ +package com.ruoyi; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * web容器中进行部署 + * + * @author ruoyi + */ +public class RuoYiServletInitializer extends SpringBootServletInitializer +{ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) + { + return application.sources(RuoYiApplication.class); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java new file mode 100644 index 0000000..fcd3f3f --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java @@ -0,0 +1,81 @@ +package com.ruoyi.web.controller.common; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import javax.imageio.ImageIO; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.FastByteArrayOutputStream; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.google.code.kaptcha.Producer; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.sign.Base64; +import com.ruoyi.common.utils.uuid.IdUtils; +import com.ruoyi.system.service.ISysConfigService; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 验证码操作处理 + * + * @author ruoyi + */ +@RestController +public class CaptchaController { + + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + @Autowired + private ISysConfigService configService; + + /** + * 生成验证码 + */ + @GetMapping("/captchaImage") + public AjaxResult getCode(HttpServletResponse response) throws IOException { + AjaxResult ajax = AjaxResult.success(); + boolean captchaEnabled = configService.selectCaptchaEnabled(); + ajax.put("captchaEnabled", captchaEnabled); + if (!captchaEnabled) { + return ajax; + } + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(), capStr = null, code = null; + BufferedImage image = null; + // 生成验证码 + String captchaType = RuoYiConfig.getCaptchaType(); + if ("math".equals(captchaType)) { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); + } else if ("char".equals(captchaType)) { + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); + } + CacheUtils.put(CacheConstants.CAPTCHA_CODE_KEY, uuid, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + try { + ImageIO.write(image, "jpg", os); + } catch (IOException e) { + return AjaxResult.error(e.getMessage()); + } + ajax.put("uuid", uuid); + ajax.put("img", Base64.encode(os.toByteArray())); + return ajax; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java new file mode 100644 index 0000000..99397e9 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java @@ -0,0 +1,162 @@ +package com.ruoyi.web.controller.common; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.file.utils.FileOperateUtils; +import com.ruoyi.framework.config.ServerConfig; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 通用请求处理 + * + * @author ruoyi + */ +@Tag(name = "通用请求处理") +@RestController +@RequestMapping("/common") +public class CommonController { + + private static final Logger log = LoggerFactory.getLogger(CommonController.class); + + private static final String FILE_DELIMETER = ","; + + @Autowired + private ServerConfig serverConfig; + + /** + * 通用下载请求 + * + * @param fileName 文件名称 + * @param delete 是否删除 + */ + @Operation(summary = "通用下载请求") + @Parameters({ + @Parameter(name = "fileName", description = "文件名称"), + @Parameter(name = "delete", description = "是否删除") + }) + @GetMapping("/download") + @Anonymous + public void fileDownload( + @RequestParam("fileName") String fileName, + @RequestParam("delete") Boolean delete, + HttpServletResponse response, + HttpServletRequest request) { + try { + if (!FileUtils.checkAllowDownload(fileName)) { + throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName)); + } + String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); + String filePath = RuoYiConfig.getDownloadPath() + fileName; + + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, realFileName); + // FileUtils.writeBytes(filePath, response.getOutputStream()); + FileOperateUtils.downLoad(filePath, response.getOutputStream()); + if (delete) { + FileOperateUtils.deleteFile(fileName); + } + } catch (Exception e) { + log.error("下载文件失败", e); + } + } + + /** + * 通用上传请求(单个) + */ + @Operation(summary = "通用上传请求(单个)") + @PostMapping("/upload") + @Anonymous + public AjaxResult uploadFile(@RequestBody MultipartFile file) throws Exception { + try { + String url = FileOperateUtils.upload(file); + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", file.getOriginalFilename()); + ajax.put("newFileName", FileUtils.getName(file.getOriginalFilename())); + ajax.put("originalFilename", file.getOriginalFilename()); + return ajax; + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 通用上传请求(多个) + */ + @Operation(summary = "通用上传请求(多个)") + @PostMapping("/uploads") + @Anonymous + public AjaxResult uploadFiles(@RequestBody List files) + throws Exception { + try { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + List urls = new ArrayList(); + List fileNames = new ArrayList(); + List newFileNames = new ArrayList(); + List originalFilenames = new ArrayList(); + for (MultipartFile file : files) { + // 上传并返回新文件名称 + String fileName = FileOperateUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + urls.add(url); + fileNames.add(fileName); + newFileNames.add(FileUtils.getName(fileName)); + originalFilenames.add(file.getOriginalFilename()); + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER)); + ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER)); + ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER)); + ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER)); + return ajax; + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 本地资源通用下载 + */ + @Operation(summary = "本地资源通用下载") + @GetMapping("/download/resource") + @Anonymous + public void resourceDownload(@Parameter(name = "resource", description = "资源名称") String resource, + HttpServletRequest request, HttpServletResponse response) + throws Exception { + try { + if (!FileUtils.checkAllowDownload(resource)) { + throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource)); + } + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, resource); + FileOperateUtils.downLoad(resource, response.getOutputStream()); + } catch (Exception e) { + log.error("下载文件失败", e); + } + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java new file mode 100644 index 0000000..80ff3a8 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java @@ -0,0 +1,122 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.springframework.cache.Cache.ValueWrapper; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysCache; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 缓存监控 + * + * @author ruoyi + */ +@Tag(name = "缓存监控") +@RestController +@RequestMapping("/monitor/cache") +public class CacheController { + + private static String tmpCacheName = ""; + + private final static List caches = new ArrayList(); + { + caches.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息")); + caches.add(new SysCache(CacheConstants.SYS_CONFIG_KEY, "配置信息")); + caches.add(new SysCache(CacheConstants.SYS_DICT_KEY, "数据字典")); + caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码")); + caches.add(new SysCache(CacheConstants.PHONE_CODES, "短信验证码")); + caches.add(new SysCache(CacheConstants.EMAIL_CODES, "邮箱验证码")); + caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交")); + caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理")); + caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数")); + caches.add(new SysCache(CacheConstants.IP_ERR_CNT_KEY, "IP错误次数")); + caches.add(new SysCache(CacheConstants.FILE_MD5_PATH_KEY, "path-md5")); + caches.add(new SysCache(CacheConstants.FILE_PATH_MD5_KEY, "md5-path")); + } + + @Operation(summary = "获取缓存名列表") + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getNames") + public AjaxResult cache() { + return AjaxResult.success(caches); + } + + @Operation(summary = "获取缓存键列表") + @Parameters({ + @Parameter(name = "cacheName", description = "缓存名称", required = true), + }) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getKeys/{cacheName}") + public AjaxResult getCacheKeys(@PathVariable String cacheName) { + tmpCacheName = cacheName; + Set keyset = CacheUtils.getkeys(cacheName); + return AjaxResult.success(keyset); + } + + @Operation(summary = "获取缓存值列表") + @Parameters({ + @Parameter(name = "cacheName", description = "缓存名称", required = true), + @Parameter(name = "cacheKey", description = "缓存键名", required = true) + }) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getValue/{cacheName}/{cacheKey}") + public AjaxResult getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) { + ValueWrapper valueWrapper = CacheUtils.get(cacheName, cacheKey); + SysCache sysCache = new SysCache(); + sysCache.setCacheName(cacheName); + sysCache.setCacheKey(cacheKey); + if (StringUtils.isNotNull(valueWrapper)) { + sysCache.setCacheValue(Convert.toStr(valueWrapper.get(), "")); + } + return AjaxResult.success(sysCache); + } + + @Operation(summary = "清除缓存") + @Parameters({ + @Parameter(name = "cacheName", description = "缓存名称", required = true) + }) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheName/{cacheName}") + public AjaxResult clearCacheName(@PathVariable String cacheName) { + CacheUtils.clear(cacheName); + return AjaxResult.success(); + } + + @Operation(summary = "清除缓存值") + @Parameters({ + @Parameter(name = "cacheKey", description = "缓存键名", required = true) + }) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheKey/{cacheKey}") + public AjaxResult clearCacheKey(@PathVariable String cacheKey) { + CacheUtils.removeIfPresent(tmpCacheName, cacheKey); + return AjaxResult.success(); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheAll") + public AjaxResult clearCacheAll() { + for (String cacheName : CacheUtils.getCacheManager().getCacheNames()) { + CacheUtils.clear(cacheName); + } + return AjaxResult.success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java new file mode 100644 index 0000000..5588830 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java @@ -0,0 +1,32 @@ +package com.ruoyi.web.controller.monitor; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.framework.web.domain.Server; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 服务器监控 + * + * @author ruoyi + */ +@Tag(name = "服务器监控") +@RestController +@RequestMapping("/monitor/server") +public class ServerController { + + @Operation(summary = "获取服务器监控信息") + @PreAuthorize("@ss.hasPermi('monitor:server:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception { + Server server = new Server(); + server.copyTo(); + return AjaxResult.success(server); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java new file mode 100644 index 0000000..f81c648 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java @@ -0,0 +1,96 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.framework.web.service.SysPasswordService; +import com.ruoyi.system.domain.SysLogininfor; +import com.ruoyi.system.service.ISysLogininforService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 系统访问记录 + * + * @author ruoyi + */ +@Tag(name = "系统访问记录") +@RestController +@RequestMapping("/monitor/logininfor") +public class SysLogininforController extends BaseController { + + @Autowired + private ISysLogininforService logininforService; + + @Autowired + private SysPasswordService passwordService; + + @Operation(summary = "获取系统访问记录列表") + @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')") + @GetMapping("/list") + public TableDataInfo list(SysLogininfor logininfor) { + startPage(); + List list = logininforService.selectLogininforList(logininfor); + return getDataTable(list); + } + + @Operation(summary = "导出系统访问记录列表") + @Log(title = "登录日志", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('monitor:logininfor:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysLogininfor logininfor) { + List list = logininforService.selectLogininforList(logininfor); + ExcelUtil util = new ExcelUtil(SysLogininfor.class); + util.exportExcel(response, list, "登录日志"); + } + + @Operation(summary = "删除系统访问记录") + @Parameters({ + @Parameter(name = "infoIds", description = "记录id数组", required = true), + }) + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{infoIds}") + public AjaxResult remove(@PathVariable(name = "infoIds") Long[] infoIds) { + return toAjax(logininforService.deleteLogininforByIds(infoIds)); + } + + @Operation(summary = "清除系统访问记录") + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() { + logininforService.cleanLogininfor(); + return success(); + } + + @Operation(summary = "账户解锁") + @Parameters({ + @Parameter(name = "userName", description = "用户名", required = true), + }) + @PreAuthorize("@ss.hasPermi('monitor:logininfor:unlock')") + @Log(title = "账户解锁", businessType = BusinessType.OTHER) + @GetMapping("/unlock/{userName}") + public AjaxResult unlock(@PathVariable("userName") String userName) { + passwordService.clearLoginRecordCache(userName); + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java new file mode 100644 index 0000000..5c1b11e --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java @@ -0,0 +1,102 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.SysOperLog; +import com.ruoyi.system.service.ISysOperLogService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 操作日志记录 + * + * @author ruoyi + */ +@Tag(name = "操作日志记录") +@RestController +@RequestMapping("/monitor/operlog") +public class SysOperlogController extends BaseController { + + @Autowired + private ISysOperLogService operLogService; + + @Operation(summary = "获取操作日志记录列表") + @PreAuthorize("@ss.hasPermi('monitor:operlog:list')") + @GetMapping("/list") + public TableDataInfo list(SysOperLog operLog) { + startPage(); + List list = operLogService.selectOperLogList(operLog); + return getDataTable(list); + } + + @Operation(summary = "导出操作日志记录列表") + @Log(title = "操作日志", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('monitor:operlog:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysOperLog operLog) { + List list = operLogService.selectOperLogList(operLog); + ExcelUtil util = new ExcelUtil(SysOperLog.class); + util.exportExcel(response, list, "操作日志"); + } + + @Operation(summary = "删除操作日志记录") + @Parameters({ + @Parameter(name = "operIds", description = "记录id数组", required = true), + }) + @Log(title = "操作日志", businessType = BusinessType.DELETE) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/{operIds}") + public AjaxResult remove(@PathVariable(name = "operIds") Long[] operIds) { + return toAjax(operLogService.deleteOperLogByIds(operIds)); + } + + @Operation(summary = "清除操作日志记录") + @Log(title = "操作日志", businessType = BusinessType.CLEAN) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/clean") + public AjaxResult clean() { + operLogService.cleanOperLog(); + return success(); + } + + @Operation(summary = "业务监控") + @GetMapping("/business") + public R> business(SysOperLog operLog) { + // 查询并获取统计数据 + List> successStats = operLogService.getSuccessOperationStats(operLog); + List> failureStats = operLogService.getFailureOperationStats(operLog); + List> statusStats = operLogService.getStatusStats(operLog); + List> moduleOperationStats = operLogService.getModuleOperationStats(operLog); + // 创建一个新的 Map 来组织数据 + Map result = new LinkedHashMap<>(); + result.put("successStats", successStats); + result.put("failureStats", failureStats); + result.put("statusStats", statusStats); + result.put("moduleOperationStats", moduleOperationStats); + result.put("total", + successStats.size() + failureStats.size() + statusStats.size() + moduleOperationStats.size()); + return R.ok(result); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java new file mode 100644 index 0000000..9fa11e5 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java @@ -0,0 +1,72 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysUserOnline; +import com.ruoyi.system.service.ISysUserOnlineService; + +/** + * 在线用户监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/online") +public class SysUserOnlineController extends BaseController { + + @Autowired + private ISysUserOnlineService userOnlineService; + + @PreAuthorize("@ss.hasPermi('monitor:online:list')") + @GetMapping("/list") + public TableDataInfo list(String ipaddr, String userName) { + Collection keys = CacheUtils.getkeys(CacheConstants.LOGIN_TOKEN_KEY); + List userOnlineList = new ArrayList(); + for (String key : keys) { + LoginUser user = CacheUtils.get(CacheConstants.LOGIN_TOKEN_KEY, key, LoginUser.class); + if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) { + userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user)); + } else if (StringUtils.isNotEmpty(ipaddr)) { + userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user)); + } else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) { + userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user)); + } else { + userOnlineList.add(userOnlineService.loginUserToUserOnline(user)); + } + } + Collections.reverse(userOnlineList); + userOnlineList.removeAll(Collections.singleton(null)); + return getDataTable(userOnlineList); + } + + /** + * 强退用户 + */ + @PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')") + @Log(title = "在线用户", businessType = BusinessType.FORCE) + @DeleteMapping("/{tokenId}") + public AjaxResult forceLogout(@PathVariable String tokenId) { + CacheUtils.removeIfPresent(CacheConstants.LOGIN_TOKEN_KEY, tokenId); + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java new file mode 100644 index 0000000..0eea16b --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java @@ -0,0 +1,139 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.SysConfig; +import com.ruoyi.system.service.ISysConfigService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 参数配置 信息操作处理 + * + * @author ruoyi + */ +@Tag(name = "参数配置") +@RestController +@RequestMapping("/system/config") +public class SysConfigController extends BaseController { + + @Autowired + private ISysConfigService configService; + + /** + * 获取参数配置列表 + */ + @Operation(summary = "获取参数配置列表") + @PreAuthorize("@ss.hasPermi('system:config:list')") + @GetMapping("/list") + public TableDataInfo list(SysConfig config) { + startPage(); + List list = configService.selectConfigList(config); + return getDataTable(list); + } + + @Operation(summary = "参数管理") + @Log(title = "参数管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:config:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysConfig config) { + List list = configService.selectConfigList(config); + ExcelUtil util = new ExcelUtil(SysConfig.class); + util.exportExcel(response, list, "参数数据"); + } + + /** + * 根据参数编号获取详细信息 + */ + @Operation(summary = "根据参数编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:config:query')") + @GetMapping(value = "/{configId}") + public AjaxResult getInfo(@PathVariable(name = "configId") Long configId) { + return success(configService.selectConfigById(configId)); + } + + /** + * 根据参数键名查询参数值 + */ + @Operation(summary = "根据参数键名查询参数值") + @GetMapping(value = "/configKey/{configKey}") + @Anonymous + public AjaxResult getConfigKey(@PathVariable(name = "configKey") String configKey) { + return success(configService.selectConfigByKey(configKey)); + } + + /** + * 新增参数配置 + */ + @Operation(summary = "新增参数配置") + @PreAuthorize("@ss.hasPermi('system:config:add')") + @Log(title = "参数管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysConfig config) { + if (!configService.checkConfigKeyUnique(config)) { + return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setCreateBy(getUsername()); + return toAjax(configService.insertConfig(config)); + } + + /** + * 修改参数配置 + */ + @Operation(summary = "修改参数配置") + @PreAuthorize("@ss.hasPermi('system:config:edit')") + @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysConfig config) { + if (!configService.checkConfigKeyUnique(config)) { + return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setUpdateBy(getUsername()); + return toAjax(configService.updateConfig(config)); + } + + /** + * 删除参数配置 + */ + @Operation(summary = "删除参数配置") + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{configIds}") + public AjaxResult remove(@PathVariable(name = "configIds") Long[] configIds) { + configService.deleteConfigByIds(configIds); + return success(); + } + + /** + * 刷新参数缓存 + */ + @Operation(summary = "刷新参数缓存") + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() { + configService.resetConfigCache(); + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java new file mode 100644 index 0000000..fe378f0 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java @@ -0,0 +1,132 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; + +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysDeptService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 部门信息 + * + * @author ruoyi + */ +@Tag(name = "部门信息") +@RestController +@RequestMapping("/system/dept") +public class SysDeptController extends BaseController { + + @Autowired + private ISysDeptService deptService; + + /** + * 获取部门列表 + */ + @Operation(summary = "获取部门列表") + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list") + public AjaxResult list(SysDept dept) { + List depts = deptService.selectDeptList(dept); + return success(depts); + } + + /** + * 查询部门列表(排除节点) + */ + @Operation(summary = "查询部门列表", description = "排除节点") + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list/exclude/{deptId}") + public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) { + List depts = deptService.selectDeptList(new SysDept()); + depts.removeIf(d -> d.getDeptId().intValue() == deptId + || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + "")); + return success(depts); + } + + /** + * 根据部门编号获取详细信息 + */ + @Operation(summary = "根据部门编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:dept:query')") + @GetMapping(value = "/{deptId}") + public AjaxResult getInfo(@PathVariable(name = "deptId") Long deptId) { + deptService.checkDeptDataScope(deptId); + return success(deptService.selectDeptById(deptId)); + } + + /** + * 新增部门 + */ + @Operation(summary = "新增部门") + @PreAuthorize("@ss.hasPermi('system:dept:add')") + @Log(title = "部门管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDept dept) { + if (!deptService.checkDeptNameUnique(dept)) { + return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + dept.setCreateBy(getUsername()); + return toAjax(deptService.insertDept(dept)); + } + + /** + * 修改部门 + */ + @Operation(summary = "修改部门") + @PreAuthorize("@ss.hasPermi('system:dept:edit')") + @Log(title = "部门管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDept dept) { + Long deptId = dept.getDeptId(); + deptService.checkDeptDataScope(deptId); + if (!deptService.checkDeptNameUnique(dept)) { + return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } else if (dept.getParentId().equals(deptId)) { + return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己"); + } else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) + && deptService.selectNormalChildrenDeptById(deptId) > 0) { + return error("该部门包含未停用的子部门!"); + } + dept.setUpdateBy(getUsername()); + return toAjax(deptService.updateDept(dept)); + } + + /** + * 删除部门 + */ + @Operation(summary = "删除部门") + @PreAuthorize("@ss.hasPermi('system:dept:remove')") + @Log(title = "部门管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{deptId}") + public AjaxResult remove(@PathVariable(name = "deptId") Long deptId) { + if (deptService.hasChildByDeptId(deptId)) { + return warn("存在下级部门,不允许删除"); + } + if (deptService.checkDeptExistUser(deptId)) { + return warn("部门存在用户,不允许删除"); + } + deptService.checkDeptDataScope(deptId); + return toAjax(deptService.deleteDeptById(deptId)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java new file mode 100644 index 0000000..291b282 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java @@ -0,0 +1,126 @@ +package com.ruoyi.web.controller.system; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.service.ISysDictDataService; +import com.ruoyi.system.service.ISysDictTypeService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 数据字典信息 + * + * @author ruoyi + */ +@Tag(name = "数据字典信息(数据)") +@RestController +@RequestMapping("/system/dict/data") +public class SysDictDataController extends BaseController { + + @Autowired + private ISysDictDataService dictDataService; + + @Autowired + private ISysDictTypeService dictTypeService; + + @Operation(summary = "查询字典数据列表") + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictData dictData) { + startPage(); + List list = dictDataService.selectDictDataList(dictData); + return getDataTable(list); + } + + @Operation(summary = "导出字典数据列表") + @Log(title = "字典数据", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:dict:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysDictData dictData) { + List list = dictDataService.selectDictDataList(dictData); + ExcelUtil util = new ExcelUtil(SysDictData.class); + util.exportExcel(response, list, "字典数据"); + } + + /** + * 查询字典数据详细 + */ + @Operation(summary = "查询字典数据详细") + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictCode}") + public AjaxResult getInfo(@PathVariable(name = "dictCode") Long dictCode) { + return success(dictDataService.selectDictDataById(dictCode)); + } + + /** + * 根据字典类型查询字典数据信息 + */ + @Operation(summary = "根据字典类型查询字典数据信息") + @GetMapping(value = "/type/{dictType}") + public AjaxResult dictType(@PathVariable(name = "dictType") String dictType) { + List data = dictTypeService.selectDictDataByType(dictType); + if (StringUtils.isNull(data)) { + data = new ArrayList(); + } + return success(data); + } + + /** + * 新增字典类型 + */ + @Operation(summary = "新增字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典数据", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictData dict) { + dict.setCreateBy(getUsername()); + return toAjax(dictDataService.insertDictData(dict)); + } + + /** + * 修改保存字典类型 + */ + @Operation(summary = "修改保存字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:edit')") + @Log(title = "字典数据", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictData dict) { + dict.setUpdateBy(getUsername()); + return toAjax(dictDataService.updateDictData(dict)); + } + + /** + * 删除字典类型 + */ + @Operation(summary = "删除字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictCodes}") + public AjaxResult remove(@PathVariable(name = "dictCodes") Long[] dictCodes) { + dictDataService.deleteDictDataByIds(dictCodes); + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java new file mode 100644 index 0000000..791f2d1 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java @@ -0,0 +1,135 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysDictType; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.service.ISysDictTypeService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 数据字典信息 + * + * @author ruoyi + */ +@Tag(name = "数据字典信息(类型)") +@RestController +@RequestMapping("/system/dict/type") +public class SysDictTypeController extends BaseController { + + @Autowired + private ISysDictTypeService dictTypeService; + + @Operation(summary = "查询字典类型列表") + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictType dictType) { + startPage(); + List list = dictTypeService.selectDictTypeList(dictType); + return getDataTable(list); + } + + @Operation(summary = "导出字典类型列表") + @Log(title = "字典类型", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:dict:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysDictType dictType) { + List list = dictTypeService.selectDictTypeList(dictType); + ExcelUtil util = new ExcelUtil(SysDictType.class); + util.exportExcel(response, list, "字典类型"); + } + + /** + * 查询字典类型详细 + */ + @Operation(summary = "查询字典类型详细") + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictId}") + public AjaxResult getInfo(@PathVariable(name = "dictId") Long dictId) { + return success(dictTypeService.selectDictTypeById(dictId)); + } + + /** + * 新增字典类型 + */ + @Operation(summary = "新增字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典类型", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictType dict) { + if (!dictTypeService.checkDictTypeUnique(dict)) { + return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setCreateBy(getUsername()); + return toAjax(dictTypeService.insertDictType(dict)); + } + + /** + * 修改字典类型 + */ + @Operation(summary = "修改字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:edit')") + @Log(title = "字典类型", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictType dict) { + if (!dictTypeService.checkDictTypeUnique(dict)) { + return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setUpdateBy(getUsername()); + return toAjax(dictTypeService.updateDictType(dict)); + } + + /** + * 删除字典类型 + */ + @Operation(summary = "删除字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictIds}") + public AjaxResult remove(@PathVariable(name = "dictIds") Long[] dictIds) { + dictTypeService.deleteDictTypeByIds(dictIds); + return success(); + } + + /** + * 刷新字典缓存 + */ + @Operation(summary = "刷新字典缓存") + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() { + dictTypeService.resetDictCache(); + return success(); + } + + /** + * 获取字典选择框列表 + */ + @Operation(summary = "获取字典选择框列表") + @GetMapping("/optionselect") + public AjaxResult optionselect() { + List dictTypes = dictTypeService.selectDictTypeAll(); + return success(dictTypes); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java new file mode 100644 index 0000000..98a18b9 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java @@ -0,0 +1,34 @@ +package com.ruoyi.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.StringUtils; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 首页 + * + * @author ruoyi + */ +@Tag(name = "首页") +@RestController +public class SysIndexController { + + /** 系统基础配置 */ + @Autowired + private RuoYiConfig ruoyiConfig; + + /** + * 访问首页,提示语 + */ + @Operation(summary = "访问首页", description = "提示语") + @RequestMapping("/") + public String index() { + return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion()); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java new file mode 100644 index 0000000..615a472 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -0,0 +1,143 @@ +package com.ruoyi.web.controller.system; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginBody; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.file.utils.FileOperateUtils; +import com.ruoyi.framework.web.service.SysLoginService; +import com.ruoyi.framework.web.service.SysPermissionService; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysMenuService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 登录验证 + * + * @author ruoyi + */ +@Tag(name = "登录验证") +@RestController +public class SysLoginController { + + @Autowired + private SysLoginService loginService; + + @Autowired + private ISysMenuService menuService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private TokenService tokenService; + + @Autowired + private ISysConfigService configService; + + /** + * 登录方法 + * + * @param loginBody 登录信息 + * @return 结果 + */ + @Operation(summary = "登录方法") + @PostMapping("/login") + public AjaxResult login(@RequestBody LoginBody loginBody) { + AjaxResult ajax = AjaxResult.success(); + // 生成令牌 + String token = loginService.login( + loginBody.getUsername(), + loginBody.getPassword(), + loginBody.getCode(), + loginBody.getUuid()); + ajax.put(Constants.TOKEN, token); + return ajax; + } + + /** + * 获取用户信息 + * + * @return 用户信息 + */ + @Operation(summary = "获取用户信息") + @GetMapping("getInfo") + public AjaxResult getInfo() { + LoginUser loginUser = SecurityUtils.getLoginUser(); + SysUser user = loginUser.getUser(); + // 角色集合 + Set roles = permissionService.getRolePermission(user); + // 权限集合 + Set permissions = permissionService.getMenuPermission(user); + if (!loginUser.getPermissions().equals(permissions)) { + loginUser.setPermissions(permissions); + tokenService.refreshToken(loginUser); + } + if (user.getAvatar() != null) { + try { + user.setAvatar(FileOperateUtils.getURL(user.getAvatar())); + } catch (Exception e) { + } + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("user", user); + ajax.put("roles", roles); + ajax.put("permissions", permissions); + ajax.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate())); + ajax.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate())); + return ajax; + } + + // 检查初始密码是否提醒修改 + public boolean initPasswordIsModify(Date pwdUpdateDate) { + Integer initPasswordModify = Convert.toInt(configService.selectConfigByKey("sys.account.initPasswordModify")); + return initPasswordModify != null && initPasswordModify == 1 && pwdUpdateDate == null; + } + + // 检查密码是否过期 + public boolean passwordIsExpiration(Date pwdUpdateDate) { + Integer passwordValidateDays = Convert + .toInt(configService.selectConfigByKey("sys.account.passwordValidateDays")); + if (passwordValidateDays != null && passwordValidateDays > 0) { + if (StringUtils.isNull(pwdUpdateDate)) { + // 如果从未修改过初始密码,直接提醒过期 + return true; + } + Date nowDate = DateUtils.getNowDate(); + return DateUtils.differentDaysByMillisecond(nowDate, pwdUpdateDate) > passwordValidateDays; + } + return false; + } + + /** + * 获取路由信息 + * + * @return 路由信息 + */ + @Operation(summary = "获取路由信息") + @GetMapping("getRouters") + public AjaxResult getRouters() { + Long userId = SecurityUtils.getUserId(); + List menus = menuService.selectMenuTreeByUserId(userId); + return AjaxResult.success(menuService.buildMenus(menus)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java new file mode 100644 index 0000000..64e6030 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java @@ -0,0 +1,138 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysMenuService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 菜单信息 + * + * @author ruoyi + */ +@Tag(name = "菜单信息") +@RestController +@RequestMapping("/system/menu") +public class SysMenuController extends BaseController { + + @Autowired + private ISysMenuService menuService; + + /** + * 获取菜单列表 + */ + @Operation(summary = "获取菜单列表") + @PreAuthorize("@ss.hasPermi('system:menu:list')") + @GetMapping("/list") + public AjaxResult list(SysMenu menu) { + List menus = menuService.selectMenuList(menu, getUserId()); + return success(menus); + } + + /** + * 根据菜单编号获取详细信息 + */ + @Operation(summary = "根据菜单编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:menu:query')") + @GetMapping(value = "/{menuId}") + public AjaxResult getInfo(@PathVariable(name = "menuId") Long menuId) { + return success(menuService.selectMenuById(menuId)); + } + + /** + * 获取菜单下拉树列表 + */ + @Operation(summary = "获取菜单下拉树列表") + @GetMapping("/treeselect") + public AjaxResult treeselect(SysMenu menu) { + List menus = menuService.selectMenuList(menu, getUserId()); + return success(menuService.buildMenuTreeSelect(menus)); + } + + /** + * 加载对应角色菜单列表树 + */ + @Operation(summary = "加载对应角色菜单列表树") + @GetMapping(value = "/roleMenuTreeselect/{roleId}") + public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId) { + List menus = menuService.selectMenuList(getUserId()); + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId)); + ajax.put("menus", menuService.buildMenuTreeSelect(menus)); + return ajax; + } + + /** + * 新增菜单 + */ + @Operation(summary = "新增菜单") + @PreAuthorize("@ss.hasPermi('system:menu:add')") + @Log(title = "菜单管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysMenu menu) { + if (!menuService.checkMenuNameUnique(menu)) { + return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { + return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + menu.setCreateBy(getUsername()); + return toAjax(menuService.insertMenu(menu)); + } + + /** + * 修改菜单 + */ + @Operation(summary = "修改菜单") + @PreAuthorize("@ss.hasPermi('system:menu:edit')") + @Log(title = "菜单管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysMenu menu) { + if (!menuService.checkMenuNameUnique(menu)) { + return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { + return error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } else if (menu.getMenuId().equals(menu.getParentId())) { + return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); + } + menu.setUpdateBy(getUsername()); + return toAjax(menuService.updateMenu(menu)); + } + + /** + * 删除菜单 + */ + @Operation(summary = "删除菜单") + @PreAuthorize("@ss.hasPermi('system:menu:remove')") + @Log(title = "菜单管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{menuId}") + public AjaxResult remove(@PathVariable("menuId") Long menuId) { + if (menuService.hasChildByMenuId(menuId)) { + return warn("存在子菜单,不允许删除"); + } + if (menuService.checkMenuExistRole(menuId)) { + return warn("菜单已分配,不允许删除"); + } + return toAjax(menuService.deleteMenuById(menuId)); + } +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java new file mode 100644 index 0000000..ccd9fef --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java @@ -0,0 +1,100 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.system.domain.SysNotice; +import com.ruoyi.system.service.ISysNoticeService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 公告 信息操作处理 + * + * @author ruoyi + */ +@Tag(name = "公告", description = "信息操作处理") +@RestController +@RequestMapping("/system/notice") +public class SysNoticeController extends BaseController { + + @Autowired + private ISysNoticeService noticeService; + + /** + * 获取通知公告列表 + */ + @Operation(summary = "获取通知公告列表") + @GetMapping("/list") + public TableDataInfo list(SysNotice notice) { + startPage(); + if (!SecurityUtils.hasPermi("system:notice:list")) { + notice.setStatus("0"); + } + List list = noticeService.selectNoticeList(notice); + return getDataTable(list); + } + + /** + * 根据通知公告编号获取详细信息 + */ + @Operation(summary = "根据通知公告编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:notice:query')") + @GetMapping(value = "/{noticeId}") + public AjaxResult getInfo(@PathVariable(name = "noticeId") Long noticeId) { + return success(noticeService.selectNoticeById(noticeId)); + } + + /** + * 新增通知公告 + */ + @Operation(summary = "新增通知公告") + @PreAuthorize("@ss.hasPermi('system:notice:add')") + @Log(title = "通知公告", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysNotice notice) { + notice.setCreateBy(getUsername()); + return toAjax(noticeService.insertNotice(notice)); + } + + /** + * 修改通知公告 + */ + @Operation(summary = "修改通知公告") + @PreAuthorize("@ss.hasPermi('system:notice:edit')") + @Log(title = "通知公告", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysNotice notice) { + notice.setUpdateBy(getUsername()); + return toAjax(noticeService.updateNotice(notice)); + } + + /** + * 删除通知公告 + */ + @Operation(summary = "删除通知公告") + @PreAuthorize("@ss.hasPermi('system:notice:remove')") + @Log(title = "通知公告", businessType = BusinessType.DELETE) + @DeleteMapping("/{noticeIds}") + public AjaxResult remove(@PathVariable(name = "noticeIds") Long[] noticeIds) { + return toAjax(noticeService.deleteNoticeByIds(noticeIds)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java new file mode 100644 index 0000000..c45ec37 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java @@ -0,0 +1,129 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.SysPost; +import com.ruoyi.system.service.ISysPostService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 岗位信息操作处理 + * + * @author ruoyi + */ +@Tag(name = "岗位信息操作处理") +@RestController +@RequestMapping("/system/post") +public class SysPostController extends BaseController { + + @Autowired + private ISysPostService postService; + + /** + * 获取岗位列表 + */ + @Operation(summary = "获取岗位列表") + @PreAuthorize("@ss.hasPermi('system:post:list')") + @GetMapping("/list") + public TableDataInfo list(SysPost post) { + startPage(); + List list = postService.selectPostList(post); + return getDataTable(list); + } + + @Operation(summary = "导出岗位列表") + @Log(title = "岗位管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:post:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysPost post) { + List list = postService.selectPostList(post); + ExcelUtil util = new ExcelUtil(SysPost.class); + util.exportExcel(response, list, "岗位数据"); + } + + /** + * 根据岗位编号获取详细信息 + */ + @Operation(summary = "根据岗位编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:post:query')") + @GetMapping(value = "/{postId}") + public AjaxResult getInfo(@PathVariable(name = "postId") Long postId) { + return success(postService.selectPostById(postId)); + } + + /** + * 新增岗位 + */ + @Operation(summary = "新增岗位") + @PreAuthorize("@ss.hasPermi('system:post:add')") + @Log(title = "岗位管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysPost post) { + if (!postService.checkPostNameUnique(post)) { + return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } else if (!postService.checkPostCodeUnique(post)) { + return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setCreateBy(getUsername()); + return toAjax(postService.insertPost(post)); + } + + /** + * 修改岗位 + */ + @Operation(summary = "修改岗位") + @PreAuthorize("@ss.hasPermi('system:post:edit')") + @Log(title = "岗位管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysPost post) { + if (!postService.checkPostNameUnique(post)) { + return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } else if (!postService.checkPostCodeUnique(post)) { + return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setUpdateBy(getUsername()); + return toAjax(postService.updatePost(post)); + } + + /** + * 删除岗位 + */ + @Operation(summary = "删除岗位") + @PreAuthorize("@ss.hasPermi('system:post:remove')") + @Log(title = "岗位管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{postIds}") + public AjaxResult remove(@PathVariable(name = "postIds") Long[] postIds) { + return toAjax(postService.deletePostByIds(postIds)); + } + + /** + * 获取岗位选择框列表 + */ + @Operation(summary = "获取岗位选择框列表") + @GetMapping("/optionselect") + public AjaxResult optionselect() { + List posts = postService.selectPostAll(); + return success(posts); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java new file mode 100644 index 0000000..71fcad6 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java @@ -0,0 +1,144 @@ +package com.ruoyi.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.common.utils.file.MimeTypeUtils; +import com.ruoyi.file.utils.FileOperateUtils; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.service.ISysUserService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 个人信息 业务处理 + * + * @author ruoyi + */ +@Tag(name = "个人信息", description = "业务处理") +@RestController +@RequestMapping("/system/user/profile") +public class SysProfileController extends BaseController { + + @Autowired + private ISysUserService userService; + + @Autowired + private TokenService tokenService; + + /** + * 个人信息 + */ + @Operation(summary = "个人信息") + @GetMapping + public AjaxResult profile() { + LoginUser loginUser = getLoginUser(); + SysUser user = loginUser.getUser(); + if (user.getAvatar() != null) { + try { + user.setAvatar(FileOperateUtils.getURL(user.getAvatar())); + } catch (Exception e) { + } + } + AjaxResult ajax = AjaxResult.success(user); + ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername())); + ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername())); + return ajax; + } + + /** + * 修改用户 + */ + @Operation(summary = "修改用户") + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult updateProfile(@RequestBody SysUser user) { + LoginUser loginUser = getLoginUser(); + SysUser sysUser = loginUser.getUser(); + sysUser.setNickName(user.getNickName()); + sysUser.setEmail(user.getEmail()); + sysUser.setPhonenumber(user.getPhonenumber()); + sysUser.setSex(user.getSex()); + if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + if (userService.updateUserProfile(user) > 0) { + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改个人信息异常,请联系管理员"); + } + + /** + * 重置密码 + */ + @Operation(summary = "重置密码") + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping("/updatePwd") + public AjaxResult updatePwd(String oldPassword, String newPassword) { + LoginUser loginUser = getLoginUser(); + String userName = loginUser.getUsername(); + String password = loginUser.getPassword(); + if (!SecurityUtils.matchesPassword(oldPassword, password)) { + return error("修改密码失败,旧密码错误"); + } + if (SecurityUtils.matchesPassword(newPassword, password)) { + return error("新密码不能与旧密码相同"); + } + newPassword = SecurityUtils.encryptPassword(newPassword); + if (userService.resetUserPwd(userName, newPassword) > 0) { + // 更新缓存用户密码 + loginUser.getUser().setPwdUpdateDate(DateUtils.getNowDate()); + loginUser.getUser().setPassword(newPassword); + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改密码异常,请联系管理员"); + } + + /** + * 头像上传 + */ + @Operation(summary = "头像上传") + @Log(title = "用户头像", businessType = BusinessType.UPDATE) + @PostMapping("/avatar") + public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception { + if (!file.isEmpty()) { + LoginUser loginUser = getLoginUser(); + String extractPath = loginUser.getUsername() + "/" + loginUser.getUserId() + ""; + String fileName = DateUtils.dateTimeNow() + "-avatar." + FileUtils.getExtension(file); + String filePath = "avatar/" + extractPath + "/" + fileName; + String url = FileOperateUtils.upload(filePath, file, MimeTypeUtils.IMAGE_EXTENSION); + if (userService.updateUserAvatar(loginUser.getUsername(), filePath)) { + AjaxResult ajax = AjaxResult.success(); + ajax.put("imgUrl", url); + // 更新缓存用户头像 + loginUser.getUser().setAvatar(filePath); + tokenService.setLoginUser(loginUser); + return ajax; + } + } + return error("上传图片异常,请联系管理员"); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java new file mode 100644 index 0000000..22d1269 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java @@ -0,0 +1,42 @@ +package com.ruoyi.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.RegisterBody; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.SysRegisterService; +import com.ruoyi.system.service.ISysConfigService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 注册验证 + * + * @author ruoyi + */ +@Tag(name = "注册验证") +@RestController +public class SysRegisterController extends BaseController { + + @Autowired + private SysRegisterService registerService; + + @Autowired + private ISysConfigService configService; + + @Operation(summary = "注册方法") + @PostMapping("/register") + public AjaxResult register(@RequestBody RegisterBody user) { + if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) { + return error("当前系统没有开启注册功能!"); + } + String msg = registerService.register(user); + return StringUtils.isEmpty(msg) ? success() : error(msg); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java new file mode 100644 index 0000000..404f4b8 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java @@ -0,0 +1,260 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.framework.web.service.SysPermissionService; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.domain.SysUserRole; +import com.ruoyi.system.service.ISysDeptService; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 角色信息 + * + * @author ruoyi + */ +@Tag(name = "角色信息") +@RestController +@RequestMapping("/system/role") +public class SysRoleController extends BaseController { + + @Autowired + private ISysRoleService roleService; + + @Autowired + private TokenService tokenService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysDeptService deptService; + + @Operation(summary = "获取角色列表") + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/list") + public TableDataInfo list(SysRole role) { + startPage(); + List list = roleService.selectRoleList(role); + return getDataTable(list); + } + + @Operation(summary = "导出角色列表") + @Log(title = "角色管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:role:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysRole role) { + List list = roleService.selectRoleList(role); + ExcelUtil util = new ExcelUtil(SysRole.class); + util.exportExcel(response, list, "角色数据"); + } + + /** + * 根据角色编号获取详细信息 + */ + @Operation(summary = "根据角色编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/{roleId}") + public AjaxResult getInfo(@PathVariable(name = "roleId") Long roleId) { + roleService.checkRoleDataScope(roleId); + return success(roleService.selectRoleById(roleId)); + } + + /** + * 新增角色 + */ + @Operation(summary = "新增角色") + @PreAuthorize("@ss.hasPermi('system:role:add')") + @Log(title = "角色管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysRole role) { + if (!roleService.checkRoleNameUnique(role)) { + return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } else if (!roleService.checkRoleKeyUnique(role)) { + return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setCreateBy(getUsername()); + return toAjax(roleService.insertRole(role)); + + } + + /** + * 修改保存角色 + */ + @Operation(summary = "修改保存角色") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysRole role) { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + if (!roleService.checkRoleNameUnique(role)) { + return error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } else if (!roleService.checkRoleKeyUnique(role)) { + return error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setUpdateBy(getUsername()); + + if (roleService.updateRole(role) > 0) { + // 更新缓存用户权限 + LoginUser loginUser = getLoginUser(); + if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin()) { + loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName())); + loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser())); + tokenService.setLoginUser(loginUser); + } + return success(); + } + return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); + } + + /** + * 修改保存数据权限 + */ + @Operation(summary = "修改保存数据权限") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/dataScope") + public AjaxResult dataScope(@RequestBody SysRole role) { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + return toAjax(roleService.authDataScope(role)); + } + + /** + * 状态修改 + */ + @Operation(summary = "状态修改") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysRole role) { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + role.setUpdateBy(getUsername()); + return toAjax(roleService.updateRoleStatus(role)); + } + + /** + * 删除角色 + */ + @Operation(summary = "删除角色") + @PreAuthorize("@ss.hasPermi('system:role:remove')") + @Log(title = "角色管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{roleIds}") + public AjaxResult remove(@PathVariable(name = "roleIds") Long[] roleIds) { + return toAjax(roleService.deleteRoleByIds(roleIds)); + } + + /** + * 获取角色选择框列表 + */ + @Operation(summary = "获取角色选择框列表") + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping("/optionselect") + public AjaxResult optionselect() { + return success(roleService.selectRoleAll()); + } + + /** + * 查询已分配用户角色列表 + */ + @Operation(summary = "查询已分配用户角色列表") + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/allocatedList") + public TableDataInfo allocatedList(SysUser user) { + startPage(); + List list = userService.selectAllocatedList(user); + return getDataTable(list); + } + + /** + * 查询未分配用户角色列表 + */ + @Operation(summary = "查询未分配用户角色列表") + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/unallocatedList") + public TableDataInfo unallocatedList(SysUser user) { + startPage(); + List list = userService.selectUnallocatedList(user); + return getDataTable(list); + } + + /** + * 取消授权用户 + */ + @Operation(summary = "取消授权用户") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancel") + public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole) { + return toAjax(roleService.deleteAuthUser(userRole)); + } + + /** + * 批量取消授权用户 + */ + @Operation(summary = "批量取消授权用户") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancelAll") + public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds) { + return toAjax(roleService.deleteAuthUsers(roleId, userIds)); + } + + /** + * 批量选择用户授权 + */ + @Operation(summary = "批量选择用户授权") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/selectAll") + public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds) { + roleService.checkRoleDataScope(roleId); + return toAjax(roleService.insertAuthUsers(roleId, userIds)); + } + + /** + * 获取对应角色部门树列表 + */ + @Operation(summary = "获取对应角色部门树列表") + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/deptTree/{roleId}") + public AjaxResult deptTree(@PathVariable("roleId") Long roleId) { + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId)); + ajax.put("depts", deptService.selectDeptTreeList(new SysDept())); + return ajax; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java new file mode 100644 index 0000000..3006c13 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java @@ -0,0 +1,247 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.service.ISysDeptService; +import com.ruoyi.system.service.ISysPostService; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 用户信息 + * + * @author ruoyi + */ +@Tag(name = "用户信息") +@RestController +@RequestMapping("/system/user") +public class SysUserController extends BaseController { + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysDeptService deptService; + + @Autowired + private ISysPostService postService; + + /** + * 获取用户列表 + */ + @Operation(summary = "获取用户列表") + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/list") + public TableDataInfo list(SysUser user) { + startPage(); + List list = userService.selectUserList(user); + return getDataTable(list); + } + + @Operation(summary = "导出用户列表") + @Log(title = "用户管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:user:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysUser user) { + List list = userService.selectUserList(user); + ExcelUtil util = new ExcelUtil(SysUser.class); + util.exportExcel(response, list, "用户数据"); + } + + @Operation(summary = "导入用户列表") + @Log(title = "用户管理", businessType = BusinessType.IMPORT) + @PreAuthorize("@ss.hasPermi('system:user:import')") + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception { + ExcelUtil util = new ExcelUtil(SysUser.class); + List userList = util.importExcel(file.getInputStream()); + String operName = getUsername(); + String message = userService.importUser(userList, updateSupport, operName); + return success(message); + } + + @Operation(summary = "获取导入用户模板") + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) { + ExcelUtil util = new ExcelUtil(SysUser.class); + util.importTemplateExcel(response, "用户数据"); + } + + /** + * 根据用户编号获取详细信息 + */ + @Operation(summary = "根据用户编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping(value = { "/", "/{userId}" }) + public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId) { + AjaxResult ajax = AjaxResult.success(); + if (StringUtils.isNotNull(userId)) { + userService.checkUserDataScope(userId); + SysUser sysUser = userService.selectUserById(userId); + ajax.put(AjaxResult.DATA_TAG, sysUser); + ajax.put("postIds", postService.selectPostListByUserId(userId)); + ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList())); + } + List roles = roleService.selectRoleAll(); + ajax.put("roles", SysUser.isAdmin(userId) ? roles + : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + ajax.put("posts", postService.selectPostAll()); + return ajax; + } + + /** + * 新增用户 + */ + @Operation(summary = "新增用户") + @PreAuthorize("@ss.hasPermi('system:user:add')") + @Log(title = "用户管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysUser user) { + if (!userService.checkUserNameUnique(user)) { + return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); + } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { + return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); + } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { + return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setCreateBy(getUsername()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + return toAjax(userService.insertUser(user)); + } + + /** + * 修改用户 + */ + @Operation(summary = "修改用户") + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysUser user) { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + if (!userService.checkUserNameUnique(user)) { + return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在"); + } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUser(user)); + } + + /** + * 删除用户 + */ + @Operation(summary = "删除用户") + @PreAuthorize("@ss.hasPermi('system:user:remove')") + @Log(title = "用户管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{userIds}") + public AjaxResult remove(@PathVariable(name = "userIds") Long[] userIds) { + if (ArrayUtils.contains(userIds, getUserId())) { + return error("当前用户不能删除"); + } + return toAjax(userService.deleteUserByIds(userIds)); + } + + /** + * 重置密码 + */ + @Operation(summary = "重置密码") + @PreAuthorize("@ss.hasPermi('system:user:resetPwd')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/resetPwd") + public AjaxResult resetPwd(@RequestBody SysUser user) { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + user.setUpdateBy(getUsername()); + return toAjax(userService.resetPwd(user)); + } + + /** + * 状态修改 + */ + @Operation(summary = "状态修改") + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysUser user) { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUserStatus(user)); + } + + /** + * 根据用户编号获取授权角色 + */ + @Operation(summary = "根据用户编号获取授权角色") + @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping("/authRole/{userId}") + public AjaxResult authRole(@PathVariable("userId") Long userId) { + AjaxResult ajax = AjaxResult.success(); + SysUser user = userService.selectUserById(userId); + List roles = roleService.selectRolesByUserId(userId); + ajax.put("user", user); + ajax.put("roles", SysUser.isAdmin(userId) ? roles + : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + return ajax; + } + + /** + * 用户授权角色 + */ + @Operation(summary = "用户授权角色") + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.GRANT) + @PutMapping("/authRole") + public AjaxResult insertAuthRole(Long userId, Long[] roleIds) { + userService.checkUserDataScope(userId); + userService.insertUserAuth(userId, roleIds); + return success(); + } + + /** + * 获取部门树列表 + */ + @Operation(summary = "获取部门树列表") + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/deptTree") + public AjaxResult deptTree(SysDept dept) { + return success(deptService.selectDeptTreeList(dept)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java new file mode 100644 index 0000000..98eacab --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java @@ -0,0 +1,93 @@ +package com.ruoyi.web.core.config; + +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.ExternalDocumentation; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.ObjectSchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; + +/** + * 验证码操作处理 + * + * @author Dftre + */ +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI springShopOpenApi() { + Schema codeSchema = new IntegerSchema().example(HttpStatus.SUCCESS); // 示例状态码 + Schema msgSchema = new StringSchema().example("操作成功"); // 示例消息 + ObjectSchema dataSchema = new ObjectSchema(); // 数据可以是任意类型,这里简单定义为ObjectSchema + + // 定义AjaxResult的Schema + ObjectSchema ajaxResultSchema = new ObjectSchema(); + ajaxResultSchema.addProperty(AjaxResult.CODE_TAG, codeSchema); + ajaxResultSchema.addProperty(AjaxResult.MSG_TAG, msgSchema); + ajaxResultSchema.addProperty(AjaxResult.DATA_TAG, dataSchema); + Components components = new Components(); + components.addSchemas("AjaxResult", ajaxResultSchema); + + return new OpenAPI() + .components(components) + .info(new Info().title("RuoYi Geek") + .description("RuoYi Geek API文档") + .version("v1") + .license(new License().name("Apache 2.0").url("http://springdoc.org"))) + .externalDocs(new ExternalDocumentation() + .description("外部文档") + .url("/doc.html")); + } + + @Bean + public GroupedOpenApi sysApi() { + return GroupedOpenApi.builder() + .group("sys系统模块") + .packagesToScan("com.ruoyi.web.controller.system") + .build(); + } + + @Bean + public GroupedOpenApi commonApi() { + return GroupedOpenApi.builder() + .group("基础模块") + .packagesToScan("com.ruoyi.web.controller.common") + .build(); + } + + @Bean + public GroupedOpenApi payApi() { + return GroupedOpenApi.builder() + .group("支付模块") + .pathsToMatch("/pay/**") + .build(); + } + + @Bean + public GroupedOpenApi fileApi() { + return GroupedOpenApi.builder() + .group("文件模块") + .packagesToScan("com.ruoyi.file.controller") + .build(); + } + + @Bean + public GroupedOpenApi authApi() { + return GroupedOpenApi.builder() + .group("认证模块") + .packagesToScan("com.ruoyi.auth.controller") + .build(); + } + +} diff --git a/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties b/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 0000000..37e7b58 --- /dev/null +++ b/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1 @@ +restart.include.json=/com.alibaba.fastjson2.*.jar \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application-auth.yml b/ruoyi-admin/src/main/resources/application-auth.yml new file mode 100644 index 0000000..2236322 --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-auth.yml @@ -0,0 +1,49 @@ +# 请输入自己的appid和appsecret +oauth: + wx: + miniapp: + appId: appId + appSecret: appSecret + url: https://api.weixin.qq.com/sns/jscode2session + pub: + appId: appId + appSecret: appSecret + url: https://api.weixin.qq.com/sns/oauth2/access_token + +tfa: + phone: + dysms: + # 阿里云 AccessKey ID + accessKeyId: appId + # 阿里云 AccessKey Secret + accessKeySecret: appSecret + # 短信模板 + template: + VerificationCode: + # 短信模板编码 + templateCode: SMS_123456789 + # 短信签名 + signName: 阿里云短信测试 + # 短信模板必需的数据名称,多个key以逗号分隔,此处配置作为校验 + keys: code + +justauth: + sources: + gitee: + clientId: 00636b75552921c3ab87161f146d41a678f1d50f3dc29ca1f0902e0c1507d755 + clientSecret: 4cd38fe8b5e79c39a90548934f294f850763f1123bf86c08448e8d4c46cbeddf + redirectUri: http://127.0.0.1:8080/system/auth/social-login/gitee + github: + clientId: Iv1.1be0cdcd71aca63b + clientSecret: 0d59d28b43152bc8906011624db37b0fed88d154 + redirectUri: http://127.0.0.1:8080/system/auth/social-login/github + + +spring: + mail: + # 邮箱配置 smtp.qq.com smtp.163.com + host: smtp.163.com + # 邮箱地址 + username: email + # 授权码 + password: password diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml new file mode 100644 index 0000000..c403e67 --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -0,0 +1,88 @@ +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver +# driverClassName: org.mariadb.jdbc.Driver + dynamic: + primary: MASTER + xa: true + datasource: + # 主库数据源 + MASTER: + url: jdbc:mysql://192.168.3.246/ruoyi-geek?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true +# url: jdbc:mariadb://192.168.3.154:3306/ruoyi-geek?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai + username: root + password: 4877017Ldy. + # MASTER: + # url: jdbc:postgresql://127.0.0.1/ry + # username: postgres + # password: 123456 + # 从库数据源 + # SLAVE: + # url: jdbc:mysql://127.0.0.1/ruoyi?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + # username: root + # password: 123456 + druid: + # 初始连接数 + initialSize: 5 + # 最小连接池数量 + minIdle: 10 + # 最大连接池数量 + maxActive: 20 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置连接超时时间 + connectTimeout: 30000 + # 配置网络超时时间 + socketTimeout: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 配置一个连接在池中最大生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 + # 配置检测连接是否有效 + validationQuery: SELECT 1 + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + # 配置监控统计拦截的filters stat=>监控统计 wall=>防SQL注入 slf4j log4j2=>日志记录 + filters: stat,wall,slf4j,log4j2 + # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 + connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 + + # 配置spring监控的匹配类 + aop-patterns: com.ruoyi.*.service.*,com.ruoyi.*.mapper.* + + + # Web应用 和 URL监控 配置 + web-stat-filter: + enabled: true + url-pattern: /* + exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" + session-stat-enable: false # 是否开启session统计功能 + + # 监控视图配置 + stat-view-servlet: + enabled: true + url-pattern: /druid/* + reset-enable: true # 是否可以重置日志 + login-username: ruoyi # 用户名 + login-password: 123456 # 密码 + allow: "" # IP白名单 (没有配置或者为空,则允许所有访问) + deny: "" # IP黑名单 (存在共同时,deny优先于allow) + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + +# 是否开启分布式事务,如不开启,请删除atomikos插件,否则atomikos相关驱动虽不生效但仍会启动 +atomikos: + enabled: false \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application-file.yml b/ruoyi-admin/src/main/resources/application-file.yml new file mode 100644 index 0000000..7f4c809 --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-file.yml @@ -0,0 +1,53 @@ +# local配置 +local: + enable: true + primary: MASTER + client: + MASTER: + permission: public + path: /data/files/master + # 建议public权限的api以 /profile开头就不需要再从SecurityConfig配置权限了 + api: /profile/files/master + # SLAVE: + # permission: private + # path: /data/files/slave + # private需要自己编写一个api例如FileController中的/file/resource + # api: /file/resource + +# Minio配置 +minio: + enable: false + primary: MASTER + client: + MASTER: + permission: public + url: http://localhost:9000 + accessKey: + secretKey: + bucketName: ruoyi + # SLAVE: + # permission: private + # url: http://localhost:9000 + # accessKey: minioadmin + # secretKey: minioadmin + # bucketName: ry + +# oss配置 +oss: + enable: false + primary: MASTER + client: + MASTER: + permission: public + accessKeyId: accessKeyId + accessKeySecret: accessKeySecret + bucketName: ruoyi + endpoint: oss-cn-beijing.aliyuncs.com + # SLAVE: + # permission: private + # accessKeyId: + # accessKeySecret: + # bucketName: + # endpoint: + + diff --git a/ruoyi-admin/src/main/resources/application-model.yml b/ruoyi-admin/src/main/resources/application-model.yml new file mode 100644 index 0000000..dd82ee0 --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-model.yml @@ -0,0 +1,17 @@ +# generator(代码生成器)模块: 基础配置 +gen: + # 作者 + author: ruoyi + # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool + packageName: com.ruoyi.system + # 自动去除表前缀,默认是false + autoRemovePre: false + # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) + tablePrefix: sys_ + +# flowable(工作流)模块: 基础配置 +flowable: + # 开启定时任务JOB + async-executor-activate: false + # 在引擎启动时,会自动更新数据库架构 + database-schema-update: true \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application-pay.yml b/ruoyi-admin/src/main/resources/application-pay.yml new file mode 100644 index 0000000..4f4571a --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-pay.yml @@ -0,0 +1,34 @@ +# 回调地址使用的内网穿透https://natapp.cn/ +# 回调地址根据退款还是支付的回调会自动在后面拼接 {订单ID}/{pay|refund}, 部分支付方式可能不支持退款回调 +# 证书文件必须为UTF-8编码,不然会导致报错。 +pay: + # https://doc.shouqianba.com/zh-cn/ + sqb: + enabled: false + appId: "appId" + apiDomain: "apiDomain" + terminalSn: "terminalSn" + terminalKey: "terminalKey" + vendorSn: "vendorSn" + vendorKey: "vendorKey" + publicKey: classpath:pay/sqb/sqb_public_key.pem + notifyUrl: http://e2vca6.natappfree.cc/pay/sqb/notify + # https://opendocs.alipay.com/open/02np95 + alipay: + enabled: false + appId: appid + # 文件内容没有换行符也没有PEM起始标记和结束标记 + appPrivateKey: classpath:pay/alipay/alipay_private_key.pem + # 文件内容没有换行符也没有PEM起始标记和结束标记 + alipayPublicKey: classpath:pay/alipay/alipay_public_key.pem + notifyUrl: http://e2vca6.natappfree.cc/alipay/notify + # https://github.com/wechatpay-apiv3/wechatpay-java + wechat: + enabled: false + appId: appid + apiV3Key: apiV3Key + # 文件内容应该有PEM起始标记和结束标记 + privateKeyPath: classpath:pay/wx/apiclient_key.pem + merchantId: merchantId + merchantSerialNumber: merchantSerialNumber + notifyUrl: http://e2vca6.natappfree.cc/pay/wechat/notify diff --git a/ruoyi-admin/src/main/resources/application-plugins.yml b/ruoyi-admin/src/main/resources/application-plugins.yml new file mode 100644 index 0000000..0d0c1f8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-plugins.yml @@ -0,0 +1,41 @@ +spring: + data: + # redis 配置 + redis: + # 地址 + host: 192.168.3.246 + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 0 + # 密码 + password: 4877017Ldy + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + + #配置rabbitMq 服务器 + rabbitmq: + enable: false + host: 127.0.0.1 + port: 5672 + username: guest + password: guest + + +netty: + websocket: + maxMessageSize: 65536 + bossThreads: 4 + workerThreads: 16 + port: 8081 + enable: true diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml new file mode 100644 index 0000000..0c49282 --- /dev/null +++ b/ruoyi-admin/src/main/resources/application.yml @@ -0,0 +1,163 @@ +# 项目相关配置 +ruoyi: + # 名称 + name: RuoYi + # 版本 + version: 3.8.5 + # 版权年份 + copyrightYear: 2024 + # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) + profile: D:/ruoyi/uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数组计算 char 字符验证 + captchaType: math + # 指定默认文件服务类型(值为local代表使用本地作为文件操作服务,minio代表使用minio作为文件操作服务,oss代表使用oss作为文件操作服务) + fileServer: local + # 指定默认文件上传方法最大文件大小(MB) + fileMaxSize: 50 + +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 9999 + servlet: + # 应用的访问路径 + context-path: / + tomcat: + # tomcat的URI编码 + uri-encoding: UTF-8 + # 连接数满后的排队数,默认为100 + accept-count: 1000 + threads: + # tomcat最大线程数,默认为200 + max: 800 + # Tomcat启动初始化的线程数,默认值10 + min-spare: 100 + +# 日志配置 +logging: + level: + "[com.ruoyi]": DEBUG + "[org]": WARN + "[org.springframework]": WARN + "[org.apache]": WARN + "[org.springframework.context.support.PostProcessorRegistrationDelegate]": ERROR + "[com.alibaba.druid.spring.boot3.autoconfigure.stat.DruidSpringAopConfiguration]": ERROR + "[com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties]": ERROR + +# 用户配置 +user: + password: + # 密码最大错误次数 + maxRetryCount: 5 + # 密码锁定时间(默认10分钟) + lockTime: 10 + ip: + maxRetryCount: 15 + lockTime: 15 + +# Spring配置 +spring: + #给项目来个名字 + application: + name: ruoyi + cache: + # 指定缓存类型 jcache 本地缓存 redis 缓存 + type: redis + redis: + # 指定存活时间(ms) + time-to-live: 86400000 + # 指定前缀 + use-key-prefix: true + # 是否缓存空值,可以防止缓存穿透 + cache-null-values: true + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + profiles: + active: druid,file,auth,pay,plugins,model + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 10MB + # 设置总上传的文件大小 + max-request-size: 20MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认1天) + expireTime: 1440 + +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: /dev-api + +# 防盗链配置 +referer: + # 防盗链开关 + enabled: false + # 允许的域名列表 + allowed-domains: localhost,127.0.0.1,ruoyi.vip,www.ruoyi.vip + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* + +createSqlSessionFactory: + # 选择MyBatis配置方式,mybatis / mybatis-plus + use: mybatis-plus + +datascope: + # default 原生数据范围注解 plus 增强数据范围注解 + type: default + +pageutils: + # default 使用PageHelper分页插件 custom 自研分页插件 + type: default + +# PageHelper分页插件 +pagehelper: + helperDialect: mysql + supportMethodsArguments: true + params: count=countSql + +# MyBatis配置 +mybatis: + # 搜索指定包别名 + typeAliasesPackage: com.ruoyi.**.domain + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 加载全局的配置文件 + configLocation: classpath:mybatis/mybatis-config.xml + +# MyBatis Plus配置 +mybatis-plus: + # 搜索指定包别名 + typeAliasesPackage: com.ruoyi.**.domain + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 加载全局的配置文件 + configLocation: classpath:mybatis/mybatis-config.xml \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/banner.txt b/ruoyi-admin/src/main/resources/banner.txt new file mode 100644 index 0000000..0931cb8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/banner.txt @@ -0,0 +1,24 @@ +Application Version: ${ruoyi.version} +Spring Boot Version: ${spring-boot.version} +//////////////////////////////////////////////////////////////////// +// _ooOoo_ // +// o8888888o // +// 88" . "88 // +// (| ^_^ |) // +// O\ = /O // +// ____/`---'\____ // +// .' \\| |// `. // +// / \\||| : |||// \ // +// / _||||| -:- |||||- \ // +// | | \\\ - /// | | // +// | \_| ''\---/'' | | // +// \ .-\__ `-` ___/-. / // +// ___`. .' /--.--\ `. . ___ // +// ."" '< `.___\_<|>_/___.' >'"". // +// | | : `- \`.;`\ _ /`;.`/ - ` : | | // +// \ \ `-. \_ __\ /__ _/ .-` / / // +// ========`-.____`-.___\_____/___.-`____.-'======== // +// `=---=' // +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // +// 佛祖保佑 永不宕机 永无BUG // +//////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/i18n/messages.properties b/ruoyi-admin/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..93de005 --- /dev/null +++ b/ruoyi-admin/src/main/resources/i18n/messages.properties @@ -0,0 +1,38 @@ +#错误消息 +not.null=* 必须填写 +user.jcaptcha.error=验证码错误 +user.jcaptcha.expire=验证码已失效 +user.not.exists=用户不存在/密码错误 +user.password.not.match=用户不存在/密码错误 +user.password.retry.limit.count=密码输入错误{0}次 +user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟 +user.password.delete=对不起,您的账号已被删除 +user.blocked=用户已封禁,请联系管理员 +role.blocked=角色已封禁,请联系管理员 +login.blocked=很遗憾,访问IP已被列入系统黑名单 +user.logout.success=退出成功 + +length.not.valid=长度必须在{min}到{max}个字符之间 + +user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头 +user.password.not.valid=* 5-50个字符 + +user.email.not.valid=邮箱格式错误 +user.mobile.phone.number.not.valid=手机号格式错误 +user.login.success=登录成功 +user.register.success=注册成功 +user.notfound=请重新登录 +user.forcelogout=管理员强制退出,请重新登录 +user.unknown.error=未知错误,请重新登录 + +##文件上传消息 +upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB! +upload.filename.exceed.length=上传的文件名最长{0}个字符 + +##权限 +no.permission=您没有数据的权限,请联系管理员添加权限 [{0}] +no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}] +no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}] +no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}] +no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}] +no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}] diff --git a/ruoyi-admin/src/main/resources/logback.xml b/ruoyi-admin/src/main/resources/logback.xml new file mode 100644 index 0000000..9d280b4 --- /dev/null +++ b/ruoyi-admin/src/main/resources/logback.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + ${console.pattern} + + + + + + ${log.path}/sys-info.log + + + + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/sys-error.log + + + + ${log.path}/sys-error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log.path}/sys-user.log + + + ${log.path}/sys-user.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml b/ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 0000000..17f49a4 --- /dev/null +++ b/ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ruoyi-admin/src/main/resources/pay/.gitkeep b/ruoyi-admin/src/main/resources/pay/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml new file mode 100644 index 0000000..a7589d4 --- /dev/null +++ b/ruoyi-common/pom.xml @@ -0,0 +1,145 @@ + + + + ruoyi + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-common + + + common通用工具 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.apache.commons + commons-lang3 + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + com.alibaba.fastjson2 + fastjson2 + + + + + commons-io + commons-io + + + + + org.apache.poi + poi-ooxml + + + + + io.jsonwebtoken + jjwt-api + + + + io.jsonwebtoken + jjwt-impl + + + + io.jsonwebtoken + jjwt-jackson + + + + + jakarta.xml.bind + jakarta.xml.bind-api + + + com.sun.xml.bind + jaxb-core + + + + + org.apache.commons + commons-pool2 + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + + eu.bitwalker + UserAgentUtils + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + + + org.projectlombok + lombok + + + + + org.springframework.boot + spring-boot-starter-cache + + + + javax.cache + cache-api + + + + diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java new file mode 100644 index 0000000..1d6d4f4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 匿名访问不鉴权注解 + * + * @author ruoyi + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Anonymous +{ +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java new file mode 100644 index 0000000..be49c80 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java @@ -0,0 +1,33 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据权限过滤注解 + * + * @author ruoyi + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataScope +{ + /** + * 部门表的别名 + */ + public String deptAlias() default ""; + + /** + * 用户表的别名 + */ + public String userAlias() default ""; + + /** + * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来 + */ + public String permission() default ""; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java new file mode 100644 index 0000000..39d9d30 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java @@ -0,0 +1,34 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.ruoyi.common.enums.DataSourceType; + +/** + * 自定义多数据源切换注解 + * + * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 + * + * @author ruoyi + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DataSource { + + /** + * 切换数据源名称 - 枚举方式 + */ + public DataSourceType value() default DataSourceType.MASTER; + + /** + * 切换数据源名称 - 字符串方式 + */ + public String name() default ""; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java new file mode 100644 index 0000000..536c42b --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java @@ -0,0 +1,206 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.math.RoundingMode; + +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; + +import com.ruoyi.common.utils.poi.ExcelHandlerAdapter; + +/** + * 自定义导出Excel数据注解 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Excel { + /** + * 导出时在excel中排序 + */ + public int sort() default Integer.MAX_VALUE; + + /** + * 导出到Excel中的名字. + */ + public String name() default ""; + + /** + * 日期格式, 如: yyyy-MM-dd + */ + public String dateFormat() default ""; + + /** + * 如果是字典类型,请设置字典的type值 (如: sys_user_sex) + */ + public String dictType() default ""; + + /** + * 读取内容转表达式 (如: 0=男,1=女,2=未知) + */ + public String readConverterExp() default ""; + + /** + * 分隔符,读取字符串组内容 + */ + public String separator() default ","; + + /** + * BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化) + */ + public int scale() default -1; + + /** + * BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN + */ + public RoundingMode roundingMode() default RoundingMode.HALF_EVEN; + + /** + * 导出时在excel中每个列的高度 + */ + public double height() default 14; + + /** + * 导出时在excel中每个列的宽度 + */ + public double width() default 16; + + /** + * 文字后缀,如% 90 变成90% + */ + public String suffix() default ""; + + /** + * 当值为空时,字段的默认值 + */ + public String defaultValue() default ""; + + /** + * 提示信息 + */ + public String prompt() default ""; + + /** + * 是否允许内容换行 + */ + public boolean wrapText() default false; + + /** + * 设置只能选择不能输入的列内容. + */ + public String[] combo() default {}; + + /** + * 是否从字典读数据到combo,默认不读取,如读取需要设置dictType注解. + */ + public boolean comboReadDict() default false; + + /** + * 是否需要纵向合并单元格,应对需求:含有list集合单元格) + */ + public boolean needMerge() default false; + + /** + * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. + */ + public boolean isExport() default true; + + /** + * 另一个类中的属性名称,支持多级获取,以小数点隔开 + */ + public String targetAttr() default ""; + + /** + * 是否自动统计数据,在最后追加一行统计数据总和 + */ + public boolean isStatistics() default false; + + /** + * 导出类型(0数字 1字符串 2图片) + */ + public ColumnType cellType() default ColumnType.STRING; + + /** + * 导出列头背景颜色 + */ + public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT; + + /** + * 导出列头字体颜色 + */ + public IndexedColors headerColor() default IndexedColors.WHITE; + + /** + * 导出单元格背景颜色 + */ + public IndexedColors backgroundColor() default IndexedColors.WHITE; + + /** + * 导出单元格字体颜色 + */ + public IndexedColors color() default IndexedColors.BLACK; + + /** + * 导出字段对齐方式 + */ + public HorizontalAlignment align() default HorizontalAlignment.CENTER; + + /** + * 自定义数据处理器 + */ + public Class handler() default ExcelHandlerAdapter.class; + + /** + * 自定义数据处理器参数 + */ + public String[] args() default {}; + + /** + * 字段类型(0:导出导入;1:仅导出;2:仅导入) + */ + Type type() default Type.ALL; + + public enum Type { + /** 导出或导入 */ + ALL(0), + /** 仅导出 */ + EXPORT(1), + /** 仅导入 */ + IMPORT(2); + + private final int value; + + Type(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + } + + public enum ColumnType { + /** 数字 */ + NUMERIC(0), + /** 字符串 */ + STRING(1), + /** 图片 */ + IMAGE(2), + /** 文本 */ + TEXT(3); + + private final int value; + + ColumnType(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java new file mode 100644 index 0000000..1f1cc81 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excel注解集 + * + * @author ruoyi + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Excels +{ + public Excel[] value(); +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java new file mode 100644 index 0000000..1eb8e49 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java @@ -0,0 +1,51 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.enums.OperatorType; + +/** + * 自定义操作日志记录注解 + * + * @author ruoyi + * + */ +@Target({ ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Log +{ + /** + * 模块 + */ + public String title() default ""; + + /** + * 功能 + */ + public BusinessType businessType() default BusinessType.OTHER; + + /** + * 操作人类别 + */ + public OperatorType operatorType() default OperatorType.MANAGE; + + /** + * 是否保存请求的参数 + */ + public boolean isSaveRequestData() default true; + + /** + * 是否保存响应的参数 + */ + public boolean isSaveResponseData() default true; + + /** + * 排除指定的请求参数 + */ + public String[] excludeParamNames() default {}; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java new file mode 100644 index 0000000..b769748 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java @@ -0,0 +1,31 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义注解防止表单重复提交 + * + * @author ruoyi + * + */ +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit +{ + /** + * 间隔时间(ms),小于此时间视为重复提交 + */ + public int interval() default 5000; + + /** + * 提示消息 + */ + public String message() default "不允许重复提交,请稍候再试"; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java new file mode 100644 index 0000000..76d2ee4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java @@ -0,0 +1,25 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.ruoyi.common.config.serializer.SensitiveJsonSerializer; +import com.ruoyi.common.enums.DesensitizedType; + +/** + * 数据脱敏注解 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@JacksonAnnotationsInside +@JsonSerialize(using = SensitiveJsonSerializer.class) +public @interface Sensitive +{ + DesensitizedType desensitizedType(); +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java new file mode 100644 index 0000000..24abfe2 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java @@ -0,0 +1,135 @@ +package com.ruoyi.common.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 读取项目相关配置 + * + * @author ruoyi + */ +@Component +@ConfigurationProperties(prefix = "ruoyi") +public class RuoYiConfig +{ + /** 项目名称 */ + private String name; + + /** 版本 */ + private String version; + + /** 版权年份 */ + private String copyrightYear; + + /** 上传路径 */ + private static String profile; + + + + private static String fileServer; + + /** 获取地址开关 */ + private static boolean addressEnabled; + + /** 验证码类型 */ + private static String captchaType; + + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getCopyrightYear() + { + return copyrightYear; + } + + public void setCopyrightYear(String copyrightYear) + { + this.copyrightYear = copyrightYear; + } + + public static String getProfile() + { + return profile; + } + + public void setProfile(String profile) + { + RuoYiConfig.profile = profile; + } + + public static boolean isAddressEnabled() + { + return addressEnabled; + } + + public void setAddressEnabled(boolean addressEnabled) + { + RuoYiConfig.addressEnabled = addressEnabled; + } + + public static String getCaptchaType() { + return captchaType; + } + + public void setCaptchaType(String captchaType) { + RuoYiConfig.captchaType = captchaType; + } + + public static String getFileServer() { + return fileServer; + } + + public void setFileServer(String fileServer) { + RuoYiConfig.fileServer = fileServer; + } + + /** + * 获取导入上传路径 + */ + public static String getImportPath() + { + return getProfile() + "/import"; + } + + /** + * 获取头像上传路径 + */ + public static String getAvatarPath() + { + return getProfile() + "/avatar"; + } + + /** + * 获取下载路径 + */ + public static String getDownloadPath() + { + return getProfile() + "/download/"; + } + + /** + * 获取上传路径 + */ + public static String getUploadPath() + { + return getProfile() + "/upload"; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.java new file mode 100644 index 0000000..35083e6 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.java @@ -0,0 +1,68 @@ +package com.ruoyi.common.config.serializer; + +import java.io.IOException; +import java.util.Objects; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import com.ruoyi.common.annotation.Sensitive; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.DesensitizedType; +import com.ruoyi.common.utils.SecurityUtils; + +/** + * 数据脱敏序列化过滤 + * + * @author ruoyi + */ +public class SensitiveJsonSerializer extends JsonSerializer implements ContextualSerializer +{ + private DesensitizedType desensitizedType; + + @Override + public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException + { + if (desensitization()) + { + gen.writeString(desensitizedType.desensitizer().apply(value)); + } + else + { + gen.writeString(value); + } + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) + throws JsonMappingException + { + Sensitive annotation = property.getAnnotation(Sensitive.class); + if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) + { + this.desensitizedType = annotation.desensitizedType(); + return this; + } + return prov.findValueSerializer(property.getType(), property); + } + + /** + * 是否需要脱敏处理 + */ + private boolean desensitization() + { + try + { + LoginUser securityUser = SecurityUtils.getLoginUser(); + // 管理员不脱敏 + return !securityUser.getUser().isAdmin(); + } + catch (Exception e) + { + return true; + } + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java new file mode 100644 index 0000000..8b7d60a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java @@ -0,0 +1,68 @@ +package com.ruoyi.common.constant; + +/** + * 缓存的key 常量 + * + * @author ruoyi + */ +public class CacheConstants { + /** + * 登录用户 redis key + */ + public static final String LOGIN_TOKEN_KEY = "login_tokens"; + + /** + * 验证码 redis key + */ + public static final String CAPTCHA_CODE_KEY = "captcha_codes"; + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config"; + + /** + * 字典管理 cache key + */ + public static final String SYS_DICT_KEY = "sys_dict"; + + /** + * 防重提交 redis key + */ + public static final String REPEAT_SUBMIT_KEY = "repeat_submit"; + + /** + * 限流 redis key + */ + public static final String RATE_LIMIT_KEY = "rate_limit"; + + /** + * 登录账户密码错误次数 redis key + */ + public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt"; + + /** + * 登录ip错误次数 redis key + */ + public static final String IP_ERR_CNT_KEY = "ip_err_cnt_key"; + + /** + * 手机号验证码 phone codes + */ + public static final String PHONE_CODES = "phone_codes"; + + /** + * 邮箱验证码 + */ + public static final String EMAIL_CODES = "email_codes"; + + /** + * 文件的md5 redis key + */ + public static final String FILE_MD5_PATH_KEY = "file_md5_path"; + + /** + * 文件路径 redis key + */ + public static final String FILE_PATH_MD5_KEY = "file_path_md5"; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java new file mode 100644 index 0000000..f8e802a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java @@ -0,0 +1,175 @@ +package com.ruoyi.common.constant; + +import java.util.Locale; + +import io.jsonwebtoken.Claims; + +/** + * 通用常量信息 + * + * @author ruoyi + */ +public class Constants { + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * 系统语言 + */ + public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE; + + /** + * www主域 + */ + public static final String WWW = "www."; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 通用成功标识 + */ + public static final String SUCCESS = "0"; + + /** + * 通用失败标识 + */ + public static final String FAIL = "1"; + + /** + * 登录成功 + */ + public static final String LOGIN_SUCCESS = "Success"; + + /** + * 注销 + */ + public static final String LOGOUT = "Logout"; + + /** + * 注册 + */ + public static final String REGISTER = "Register"; + + /** + * 登录失败 + */ + public static final String LOGIN_FAIL = "Error"; + + /** + * 所有权限标识 + */ + public static final String ALL_PERMISSION = "*:*:*"; + + /** + * 管理员角色权限标识 + */ + public static final String SUPER_ADMIN = "admin"; + + /** + * 角色权限分隔符 + */ + public static final String ROLE_DELIMETER = ","; + + /** + * 权限标识分隔符 + */ + public static final String PERMISSION_DELIMETER = ","; + + /** + * 验证码有效期(分钟) + */ + public static final Integer CAPTCHA_EXPIRATION = 2; + + /** + * 令牌 + */ + public static final String TOKEN = "token"; + + /** + * 令牌前缀 + */ + public static final String TOKEN_PREFIX = "Bearer "; + + /** + * 令牌前缀 + */ + public static final String LOGIN_USER_KEY = "login_user_key"; + + /** + * 用户ID + */ + public static final String JWT_USERID = "userid"; + + /** + * 用户名称 + */ + public static final String JWT_USERNAME = Claims.SUBJECT; + + /** + * 用户头像 + */ + public static final String JWT_AVATAR = "avatar"; + + /** + * 创建时间 + */ + public static final String JWT_CREATED = "created"; + + /** + * 用户权限 + */ + public static final String JWT_AUTHORITIES = "authorities"; + + /** + * 资源映射路径 前缀 + */ + public static final String RESOURCE_PREFIX = "/profile"; + + /** + * RMI 远程方法调用 + */ + public static final String LOOKUP_RMI = "rmi:"; + + /** + * LDAP 远程方法调用 + */ + public static final String LOOKUP_LDAP = "ldap:"; + + /** + * LDAPS 远程方法调用 + */ + public static final String LOOKUP_LDAPS = "ldaps:"; + + /** + * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) + */ + public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" }; + + /** + * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) + */ + public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" }; + + /** + * 定时任务违规的字符 + */ + public static final String[] JOB_ERROR_STR = { + "java.net.URL", "javax.naming.InitialContext", + "org.yaml.snakeyaml", "org.springframework", "org.apache", + "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" }; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java new file mode 100644 index 0000000..a983c77 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java @@ -0,0 +1,94 @@ +package com.ruoyi.common.constant; + +/** + * 返回状态码 + * + * @author ruoyi + */ +public class HttpStatus +{ + /** + * 操作成功 + */ + public static final int SUCCESS = 200; + + /** + * 对象创建成功 + */ + public static final int CREATED = 201; + + /** + * 请求已经被接受 + */ + public static final int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + public static final int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + public static final int MOVED_PERM = 301; + + /** + * 重定向 + */ + public static final int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + public static final int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + public static final int BAD_REQUEST = 400; + + /** + * 未授权 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + public static final int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + public static final int NOT_FOUND = 404; + + /** + * 不允许的http方法 + */ + public static final int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + public static final int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + public static final int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + public static final int ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; + + /** + * 系统警告消息 + */ + public static final int WARN = 601; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java new file mode 100644 index 0000000..62ad815 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java @@ -0,0 +1,50 @@ +package com.ruoyi.common.constant; + +/** + * 任务调度通用常量 + * + * @author ruoyi + */ +public class ScheduleConstants +{ + public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME"; + + /** 执行目标key */ + public static final String TASK_PROPERTIES = "TASK_PROPERTIES"; + + /** 默认 */ + public static final String MISFIRE_DEFAULT = "0"; + + /** 立即触发执行 */ + public static final String MISFIRE_IGNORE_MISFIRES = "1"; + + /** 触发一次执行 */ + public static final String MISFIRE_FIRE_AND_PROCEED = "2"; + + /** 不触发立即执行 */ + public static final String MISFIRE_DO_NOTHING = "3"; + + public enum Status + { + /** + * 正常 + */ + NORMAL("0"), + /** + * 暂停 + */ + PAUSE("1"); + + private String value; + + private Status(String value) + { + this.value = value; + } + + public String getValue() + { + return value; + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java new file mode 100644 index 0000000..2e5b887 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java @@ -0,0 +1,80 @@ +package com.ruoyi.common.constant; + +/** + * 用户常量信息 + * + * @author ruoyi + */ +public class UserConstants { + /** + * 平台内系统用户的唯一标志 + */ + public static final String SYS_USER = "SYS_USER"; + + /** 正常状态 */ + public static final String NORMAL = "0"; + + /** 异常状态 */ + public static final String EXCEPTION = "1"; + + /** 用户封禁状态 */ + public static final String USER_DISABLE = "1"; + + /** 角色正常状态 */ + public static final String ROLE_NORMAL = "0"; + + /** 角色封禁状态 */ + public static final String ROLE_DISABLE = "1"; + + /** 部门正常状态 */ + public static final String DEPT_NORMAL = "0"; + + /** 部门停用状态 */ + public static final String DEPT_DISABLE = "1"; + + /** 字典正常状态 */ + public static final String DICT_NORMAL = "0"; + + /** 是否为系统默认(是) */ + public static final String YES = "Y"; + + /** 是否菜单外链(是) */ + public static final String YES_FRAME = "0"; + + /** 是否菜单外链(否) */ + public static final String NO_FRAME = "1"; + + /** 菜单类型(目录) */ + public static final String TYPE_DIR = "M"; + + /** 菜单类型(菜单) */ + public static final String TYPE_MENU = "C"; + + /** 菜单类型(按钮) */ + public static final String TYPE_BUTTON = "F"; + + /** Layout组件标识 */ + public final static String LAYOUT = "Layout"; + + /** ParentView组件标识 */ + public final static String PARENT_VIEW = "ParentView"; + + /** InnerLink组件标识 */ + public final static String INNER_LINK = "InnerLink"; + + /** 校验是否唯一的返回标识 */ + public final static boolean UNIQUE = true; + public final static boolean NOT_UNIQUE = false; + + /** + * 用户名长度限制 + */ + public static final int USERNAME_MIN_LENGTH = 2; + public static final int USERNAME_MAX_LENGTH = 20; + + /** + * 密码长度限制 + */ + public static final int PASSWORD_MIN_LENGTH = 5; + public static final int PASSWORD_MAX_LENGTH = 20; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java new file mode 100644 index 0000000..cb22501 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java @@ -0,0 +1,201 @@ +package com.ruoyi.common.core.controller; + +import java.beans.PropertyEditorSupport; +import java.util.Date; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; + +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.page.PageDomain; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.page.TableSupport; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.PageUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.sql.SqlUtil; + +/** + * web层通用数据处理 + * + * @author ruoyi + */ +public class BaseController +{ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * 将前台传递过来的日期格式的字符串,自动转化为Date类型 + */ + @InitBinder + public void initBinder(WebDataBinder binder) + { + // Date 类型转换 + binder.registerCustomEditor(Date.class, new PropertyEditorSupport() + { + @Override + public void setAsText(String text) + { + setValue(DateUtils.parseDate(text)); + } + }); + } + + /** + * 设置请求分页数据 + */ + protected void startPage() + { + PageUtils.startPage(); + } + + /** + * 设置请求排序数据 + */ + protected void startOrderBy() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) + { + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + PageUtils.orderBy(orderBy); + } + } + + /** + * 清理分页的线程变量 + */ + protected void clearPage() + { + PageUtils.clearPage(); + } + + /** + * 响应请求分页数据 + */ + protected TableDataInfo getDataTable(List list) + { + TableDataInfo rspData = new TableDataInfo(); + rspData.setCode(HttpStatus.SUCCESS); + rspData.setMsg("查询成功"); + rspData.setRows(list); + rspData.setTotal(PageUtils.getTotal(list)); + return rspData; + } + + /** + * 返回成功 + */ + public AjaxResult success() + { + return AjaxResult.success(); + } + + /** + * 返回失败消息 + */ + public AjaxResult error() + { + return AjaxResult.error(); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(String message) + { + return AjaxResult.success(message); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(Object data) + { + return AjaxResult.success(data); + } + + /** + * 返回失败消息 + */ + public AjaxResult error(String message) + { + return AjaxResult.error(message); + } + + /** + * 返回警告消息 + */ + public AjaxResult warn(String message) + { + return AjaxResult.warn(message); + } + + /** + * 响应返回结果 + * + * @param rows 影响行数 + * @return 操作结果 + */ + protected AjaxResult toAjax(int rows) + { + return rows > 0 ? AjaxResult.success() : AjaxResult.error(); + } + + /** + * 响应返回结果 + * + * @param result 结果 + * @return 操作结果 + */ + protected AjaxResult toAjax(boolean result) + { + return result ? success() : error(); + } + + /** + * 页面跳转 + */ + public String redirect(String url) + { + return StringUtils.format("redirect:{}", url); + } + + /** + * 获取用户缓存信息 + */ + public LoginUser getLoginUser() + { + return SecurityUtils.getLoginUser(); + } + + /** + * 获取登录用户id + */ + public Long getUserId() + { + return getLoginUser().getUserId(); + } + + /** + * 获取登录部门id + */ + public Long getDeptId() + { + return getLoginUser().getDeptId(); + } + + /** + * 获取登录用户名 + */ + public String getUsername() + { + return getLoginUser().getUsername(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java new file mode 100644 index 0000000..42fbd9c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java @@ -0,0 +1,185 @@ +package com.ruoyi.common.core.domain; + +import java.util.HashMap; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.utils.StringUtils; + +/** + * 操作消息提醒 + * + * @author ruoyi + */ +public class AjaxResult extends HashMap +{ + private static final long serialVersionUID = 1L; + + /** 状态码 */ + public static final String CODE_TAG = "code"; + + /** 返回内容 */ + public static final String MSG_TAG = "msg"; + + /** 数据对象 */ + public static final String DATA_TAG = "data"; + + /** + * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 + */ + public AjaxResult() + { + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public AjaxResult(int code, String msg) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public AjaxResult(int code, String msg, Object data) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + if (StringUtils.isNotNull(data)) + { + super.put(DATA_TAG, data); + } + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static AjaxResult success() + { + return AjaxResult.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static AjaxResult success(Object data) + { + return AjaxResult.success("操作成功", data); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @return 成功消息 + */ + public static AjaxResult success(String msg) + { + return AjaxResult.success(msg, null); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 成功消息 + */ + public static AjaxResult success(String msg, Object data) + { + return new AjaxResult(HttpStatus.SUCCESS, msg, data); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult warn(String msg) + { + return AjaxResult.warn(msg, null); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static AjaxResult warn(String msg, Object data) + { + return new AjaxResult(HttpStatus.WARN, msg, data); + } + + /** + * 返回错误消息 + * + * @return 错误消息 + */ + public static AjaxResult error() + { + return AjaxResult.error("操作失败"); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @return 错误消息 + */ + public static AjaxResult error(String msg) + { + return AjaxResult.error(msg, null); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 错误消息 + */ + public static AjaxResult error(String msg, Object data) + { + return new AjaxResult(HttpStatus.ERROR, msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 错误消息 + */ + public static AjaxResult error(int code, String msg) + { + return new AjaxResult(code, msg, null); + } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public AjaxResult put(String key, Object value) + { + super.put(key, value); + return this; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java new file mode 100644 index 0000000..ae75994 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java @@ -0,0 +1,129 @@ +package com.ruoyi.common.core.domain; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * Entity基类 + * + * @author ruoyi + */ +@Schema(title = "基类") +public class BaseEntity implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 搜索值 */ + @Schema(title = "搜索值") + @JsonIgnore + private String searchValue; + + /** 创建者 */ + @Schema(title = "创建者") + private String createBy; + + /** 创建时间 */ + @Schema(title = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** 更新者 */ + @Schema(title = "更新者") + private String updateBy; + + /** 更新时间 */ + @Schema(title = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** 备注 */ + @Schema(title = "备注") + private String remark; + + /** 请求参数 */ + @Schema(title = "请求参数") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map params; + + public String getSearchValue() + { + return searchValue; + } + + public void setSearchValue(String searchValue) + { + this.searchValue = searchValue; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } + + public Map getParams() + { + if (params == null) + { + params = new HashMap<>(); + } + return params; + } + + public void setParams(Map params) + { + this.params = params; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/Message.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/Message.java new file mode 100644 index 0000000..8b5137a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/Message.java @@ -0,0 +1,186 @@ +package com.ruoyi.common.core.domain; + +import java.time.Instant; +import java.util.Map; +import java.util.UUID; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.enums.MessageType; + +public class Message { + + /** 消息唯一标识符 */ + private String messageId; + /** 发送者标识 */ + private String sender; + /** 接收者标识 */ + private String receiver; + /** 消息时间戳 */ + private Instant timestamp; + /** 消息类型(如命令、聊天、日志、事件等) */ + private MessageType type; + /** 消息主题或事件名称 */ + private String subject; + /** 消息数据内容 */ + private String content; + /** 消息数据负载 */ + private Map payload; + /** 元数据,用于存储额外的信息 */ + private Map metadata; + /** 消息状态(如成功、失败、重试等) */ + private String status; + /** 重试次数 */ + private int retryCount; + /** 最大重试次数 */ + private int maxRetries; + /** 重试间隔 */ + private String retryInterval; + + // 构造函数 + public Message() { + this.messageId = UUID.randomUUID().toString(); + this.timestamp = Instant.now(); + } + + public static Message create() { + return new Message(); + } + + public String getMessageId() { + return messageId; + } + + public Message setMessageId(String messageId) { + this.messageId = messageId; + return this; + } + + public String getSender() { + return sender; + } + + public Message setSender(String sender) { + this.sender = sender; + return this; + } + + public String getReceiver() { + return receiver; + } + + public Message setReceiver(String receiver) { + this.receiver = receiver; + return this; + } + + public Instant getTimestamp() { + return timestamp; + } + + public Message setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + return this; + } + + public MessageType getType() { + return type; + } + + public Message setType(MessageType type) { + this.type = type; + return this; + } + + public String getSubject() { + return subject; + } + + public Message setSubject(String subject) { + this.subject = subject; + return this; + } + + public Map getPayload() { + return payload; + } + + public Message setPayload(Map payload) { + this.payload = payload; + return this; + } + + public Map getMetadata() { + return metadata; + } + + public Message setMetadata(Map metadata) { + this.metadata = metadata; + return this; + } + + public String getStatus() { + return status; + } + + public Message setStatus(String status) { + this.status = status; + return this; + } + + public int getRetryCount() { + return retryCount; + } + + public Message setRetryCount(int retryCount) { + this.retryCount = retryCount; + return this; + } + + public int getMaxRetries() { + return maxRetries; + } + + public Message setMaxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + public String getRetryInterval() { + return retryInterval; + } + + public Message setRetryInterval(String retryInterval) { + this.retryInterval = retryInterval; + return this; + } + + public String getContent() { + return content; + } + + public Message setContent(String content) { + this.content = content; + return this; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("messageId", getMessageId()) + .append("sender", getSender()) + .append("receiver", getReceiver()) + .append("timestamp", getTimestamp()) + .append("type", getType()) + .append("subject", getSubject()) + .append("content", getContent()) + .append("payload", getPayload()) + .append("metadata", getMetadata()) + .append("status", getStatus()) + .append("retryCount", getRetryCount()) + .append("maxRetries", getMaxRetries()) + .append("retryInterval", getRetryInterval()) + .toString(); + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java new file mode 100644 index 0000000..e6af133 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java @@ -0,0 +1,122 @@ +package com.ruoyi.common.core.domain; + +import java.io.Serializable; + +import com.ruoyi.common.constant.HttpStatus; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 响应信息主体 + * + * @author ruoyi + */ +@Schema(title = "响应信息主体") +public class R implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 成功 */ + public static final int SUCCESS = HttpStatus.SUCCESS; + + /** 失败 */ + public static final int FAIL = HttpStatus.ERROR; + + @Schema(title = "响应码") + private int code; + + @Schema(title = "响应信息") + private String msg; + + @Schema(title = "响应数据") + private T data; + + public static R ok() + { + return restResult(null, SUCCESS, "操作成功"); + } + + public static R ok(T data) + { + return restResult(data, SUCCESS, "操作成功"); + } + + public static R ok(T data, String msg) + { + return restResult(data, SUCCESS, msg); + } + + public static R fail() + { + return restResult(null, FAIL, "操作失败"); + } + + public static R fail(String msg) + { + return restResult(null, FAIL, msg); + } + + public static R fail(T data) + { + return restResult(data, FAIL, "操作失败"); + } + + public static R fail(T data, String msg) + { + return restResult(data, FAIL, msg); + } + + public static R fail(int code, String msg) + { + return restResult(null, code, msg); + } + + private static R restResult(T data, int code, String msg) + { + R apiResult = new R<>(); + apiResult.setCode(code); + apiResult.setData(data); + apiResult.setMsg(msg); + return apiResult; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } + + public T getData() + { + return data; + } + + public void setData(T data) + { + this.data = data; + } + + public static Boolean isError(R ret) + { + return !isSuccess(ret); + } + + public static Boolean isSuccess(R ret) + { + return R.SUCCESS == ret.getCode(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java new file mode 100644 index 0000000..a180a18 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java @@ -0,0 +1,79 @@ +package com.ruoyi.common.core.domain; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tree基类 + * + * @author ruoyi + */ +public class TreeEntity extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 父菜单名称 */ + private String parentName; + + /** 父菜单ID */ + private Long parentId; + + /** 显示顺序 */ + private Integer orderNum; + + /** 祖级列表 */ + private String ancestors; + + /** 子部门 */ + private List children = new ArrayList<>(); + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public String getAncestors() + { + return ancestors; + } + + public void setAncestors(String ancestors) + { + this.ancestors = ancestors; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java new file mode 100644 index 0000000..bd835db --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java @@ -0,0 +1,77 @@ +package com.ruoyi.common.core.domain; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.core.domain.entity.SysMenu; + +/** + * Treeselect树结构实体类 + * + * @author ruoyi + */ +public class TreeSelect implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 节点ID */ + private Long id; + + /** 节点名称 */ + private String label; + + /** 子节点 */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List children; + + public TreeSelect() + { + + } + + public TreeSelect(SysDept dept) + { + this.id = dept.getDeptId(); + this.label = dept.getDeptName(); + this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public TreeSelect(SysMenu menu) + { + this.id = menu.getMenuId(); + this.label = menu.getMenuName(); + this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getLabel() + { + return label; + } + + public void setLabel(String label) + { + this.label = label; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java new file mode 100644 index 0000000..d489c35 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java @@ -0,0 +1,220 @@ +package com.ruoyi.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * 部门表 sys_dept + * + * @author ruoyi + */ +@Schema(title = "部门") +public class SysDept extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 部门ID */ + @Schema(title = "部门ID") + private Long deptId; + + /** 父部门ID */ + @Schema(title = "父部门ID") + private Long parentId; + + /** 祖级列表 */ + @Schema(title = "祖级列表") + private String ancestors; + + /** 部门名称 */ + @Schema(title = "部门名称") + private String deptName; + + /** 显示顺序 */ + @Schema(title = "显示顺序") + private Integer orderNum; + + /** 负责人 */ + @Schema(title = "负责人") + private String leader; + + /** 联系电话 */ + @Schema(title = "联系电话") + private String phone; + + /** 邮箱 */ + @Schema(title = "邮箱") + private String email; + + /** 部门状态:0正常,1停用 */ + @Schema(title = "部门表",description = "0正常,1停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + @Schema(title = "删除标志",description = "0代表存在 2代表删除") + private String delFlag; + + /** 父部门名称 */ + @Schema(title = "父部门名称") + private String parentName; + + /** 子部门 */ + @Schema(title = "子部门") + private List children = new ArrayList(); + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public String getAncestors() + { + return ancestors; + } + + public void setAncestors(String ancestors) + { + this.ancestors = ancestors; + } + + @NotBlank(message = "部门名称不能为空") + @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符") + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public String getLeader() + { + return leader; + } + + public void setLeader(String leader) + { + this.leader = leader; + } + + @Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符") + public String getPhone() + { + return phone; + } + + public void setPhone(String phone) + { + this.phone = phone; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("deptId", getDeptId()) + .append("parentId", getParentId()) + .append("ancestors", getAncestors()) + .append("deptName", getDeptName()) + .append("orderNum", getOrderNum()) + .append("leader", getLeader()) + .append("phone", getPhone()) + .append("email", getEmail()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java new file mode 100644 index 0000000..80311b9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java @@ -0,0 +1,189 @@ +package com.ruoyi.common.core.domain.entity; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 字典数据表 sys_dict_data + * + * @author ruoyi + */ +@Schema(title = "字典数据") +public class SysDictData extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典编码 */ + @Schema(title = "字典编码") + @Excel(name = "字典编码", cellType = ColumnType.NUMERIC) + private Long dictCode; + + /** 字典排序 */ + @Schema(title = "字典排序") + @Excel(name = "字典排序", cellType = ColumnType.NUMERIC) + private Long dictSort; + + /** 字典标签 */ + @Schema(title = "字典标签") + @Excel(name = "字典标签") + private String dictLabel; + + /** 字典键值 */ + @Schema(title = "字典键值") + @Excel(name = "字典键值") + private String dictValue; + + /** 字典类型 */ + @Schema(title = "字典类型") + @Excel(name = "字典类型") + private String dictType; + + /** 样式属性(其他样式扩展) */ + @Schema(title = "样式属性", description = "其他样式扩展") + private String cssClass; + + /** 表格字典样式 */ + @Schema(title = "表格字典样式") + private String listClass; + + /** 是否默认(Y是 N否) */ + @Schema(title = "是否默认", description = "Y=是,N=否") + @Excel(name = "是否默认", readConverterExp = "Y=是,N=否") + private String isDefault; + + /** 状态(0正常 1停用) */ + @Schema(title = "状态", description = "0=正常,1=停用") + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + public Long getDictCode() + { + return dictCode; + } + + public void setDictCode(Long dictCode) + { + this.dictCode = dictCode; + } + + public Long getDictSort() + { + return dictSort; + } + + public void setDictSort(Long dictSort) + { + this.dictSort = dictSort; + } + + @NotBlank(message = "字典标签不能为空") + @Size(min = 0, max = 100, message = "字典标签长度不能超过100个字符") + public String getDictLabel() + { + return dictLabel; + } + + public void setDictLabel(String dictLabel) + { + this.dictLabel = dictLabel; + } + + @NotBlank(message = "字典键值不能为空") + @Size(min = 0, max = 100, message = "字典键值长度不能超过100个字符") + public String getDictValue() + { + return dictValue; + } + + public void setDictValue(String dictValue) + { + this.dictValue = dictValue; + } + + @NotBlank(message = "字典类型不能为空") + @Size(min = 0, max = 100, message = "字典类型长度不能超过100个字符") + public String getDictType() + { + return dictType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + @Size(min = 0, max = 100, message = "样式属性长度不能超过100个字符") + public String getCssClass() + { + return cssClass; + } + + public void setCssClass(String cssClass) + { + this.cssClass = cssClass; + } + + public String getListClass() + { + return listClass; + } + + public void setListClass(String listClass) + { + this.listClass = listClass; + } + + public boolean getDefault() + { + return UserConstants.YES.equals(this.isDefault); + } + + public String getIsDefault() + { + return isDefault; + } + + public void setIsDefault(String isDefault) + { + this.isDefault = isDefault; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("dictCode", getDictCode()) + .append("dictSort", getDictSort()) + .append("dictLabel", getDictLabel()) + .append("dictValue", getDictValue()) + .append("dictType", getDictType()) + .append("cssClass", getCssClass()) + .append("listClass", getListClass()) + .append("isDefault", getIsDefault()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java new file mode 100644 index 0000000..82a6b45 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java @@ -0,0 +1,104 @@ +package com.ruoyi.common.core.domain.entity; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +/** + * 字典类型表 sys_dict_type + * + * @author ruoyi + */ +@Schema(title = "字典类型") +public class SysDictType extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典主键 */ + @Schema(title = "字典主键") + @Excel(name = "字典主键", cellType = ColumnType.NUMERIC) + private Long dictId; + + /** 字典名称 */ + @Schema(title = "字典名称") + @Excel(name = "字典名称") + private String dictName; + + /** 字典类型 */ + @Schema(title = "字典类型") + @Excel(name = "字典类型") + private String dictType; + + /** 状态(0正常 1停用) */ + @Schema(title = "状态", description = "0正常 1停用") + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + public Long getDictId() + { + return dictId; + } + + public void setDictId(Long dictId) + { + this.dictId = dictId; + } + + @NotBlank(message = "字典名称不能为空") + @Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符") + public String getDictName() + { + return dictName; + } + + public void setDictName(String dictName) + { + this.dictName = dictName; + } + + @NotBlank(message = "字典类型不能为空") + @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符") + @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)") + public String getDictType() + { + return dictType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("dictId", getDictId()) + .append("dictName", getDictName()) + .append("dictType", getDictType()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java new file mode 100644 index 0000000..de86bd7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java @@ -0,0 +1,260 @@ +package com.ruoyi.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * 菜单权限表 sys_menu + * + * @author ruoyi + */ +@Schema(title = "菜单权限") +public class SysMenu extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 菜单ID */ + @Schema(title = "菜单ID") + private Long menuId; + + /** 菜单名称 */ + @Schema(title = "菜单名称") + private String menuName; + + /** 父菜单名称 */ + @Schema(title = "父菜单名称") + private String parentName; + + /** 父菜单ID */ + @Schema(title = "父菜单ID") + private Long parentId; + + /** 显示顺序 */ + @Schema(title = "显示顺序") + private Integer orderNum; + + /** 路由地址 */ + @Schema(title = "路由地址") + private String path; + + /** 组件路径 */ + @Schema(title = "组件路径") + private String component; + + /** 路由参数 */ + @Schema(title = "路由参数") + private String query; + + /** 路由名称,默认和路由地址相同的驼峰格式(注意:因为vue3版本的router会删除名称相同路由,为避免名字的冲突,特殊情况可以自定义) */ + private String routeName; + + /** 是否为外链(0是 1否) */ + @Schema(title = "是否为外链", description = "0是 1否") + private String isFrame; + + /** 是否缓存(0缓存 1不缓存) */ + @Schema(title = "是否缓存", description = "0缓存 1不缓存") + private String isCache; + + /** 类型(M目录 C菜单 F按钮) */ + @Schema(title = "类型", description = "M目录 C菜单 F按钮") + private String menuType; + + /** 显示状态(0显示 1隐藏) */ + @Schema(title = "显示状态", description = "0显示 1隐藏") + private String visible; + + /** 菜单状态(0正常 1停用) */ + @Schema(title = "菜单状态", description = "0正常 1停用") + private String status; + + /** 权限字符串 */ + @Schema(title = "权限字符串") + private String perms; + + /** 菜单图标 */ + @Schema(title = "菜单图标") + private String icon; + + /** 子菜单 */ + @Schema(title = "子菜单") + private List children = new ArrayList(); + + public Long getMenuId() { + return menuId; + } + + public void setMenuId(Long menuId) { + this.menuId = menuId; + } + + @NotBlank(message = "菜单名称不能为空") + @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符") + public String getMenuName() { + return menuName; + } + + public void setMenuName(String menuName) { + this.menuName = menuName; + } + + public String getParentName() { + return parentName; + } + + public void setParentName(String parentName) { + this.parentName = parentName; + } + + public Long getParentId() { + return parentId; + } + + public void setParentId(Long parentId) { + this.parentId = parentId; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() { + return orderNum; + } + + public void setOrderNum(Integer orderNum) { + this.orderNum = orderNum; + } + + @Size(min = 0, max = 200, message = "路由地址不能超过200个字符") + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + @Size(min = 0, max = 200, message = "组件路径不能超过255个字符") + public String getComponent() { + return component; + } + + public void setComponent(String component) { + this.component = component; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } + + public String getRouteName() { + return routeName; + } + + public void setRouteName(String routeName) { + this.routeName = routeName; + } + + public String getIsFrame() { + return isFrame; + } + + public void setIsFrame(String isFrame) { + this.isFrame = isFrame; + } + + public String getIsCache() { + return isCache; + } + + public void setIsCache(String isCache) { + this.isCache = isCache; + } + + @NotBlank(message = "菜单类型不能为空") + public String getMenuType() { + return menuType; + } + + public void setMenuType(String menuType) { + this.menuType = menuType; + } + + public String getVisible() { + return visible; + } + + public void setVisible(String visible) { + this.visible = visible; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符") + public String getPerms() { + return perms; + } + + public void setPerms(String perms) { + this.perms = perms; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("menuId", getMenuId()) + .append("menuName", getMenuName()) + .append("parentId", getParentId()) + .append("orderNum", getOrderNum()) + .append("path", getPath()) + .append("component", getComponent()) + .append("query", getQuery()) + .append("routeName", getRouteName()) + .append("isFrame", getIsFrame()) + .append("IsCache", getIsCache()) + .append("menuType", getMenuType()) + .append("visible", getVisible()) + .append("status ", getStatus()) + .append("perms", getPerms()) + .append("icon", getIcon()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java new file mode 100644 index 0000000..dc3f17c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java @@ -0,0 +1,259 @@ +package com.ruoyi.common.core.domain.entity; + +import java.util.Set; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * 角色表 sys_role + * + * @author ruoyi + */ +@Schema(title = "角色表") +public class SysRole extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 角色ID */ + @Schema(title = "角色ID") + @Excel(name = "角色序号", cellType = ColumnType.NUMERIC) + private Long roleId; + + /** 角色名称 */ + @Schema(title = "角色名称") + @Excel(name = "角色名称") + private String roleName; + + /** 角色权限 */ + @Schema(title = "角色权限") + @Excel(name = "角色权限") + private String roleKey; + + /** 角色排序 */ + @Schema(title = "角色排序") + @Excel(name = "角色排序") + private Integer roleSort; + + /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ + @Schema(title = "数据范围", description = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") + @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") + private String dataScope; + + /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */ + @Schema(title = "菜单树选择项是否关联显示", description = "0:父子不互相关联显示 1:父子互相关联显示") + private boolean menuCheckStrictly; + + /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */ + @Schema(title = "部门树选择项是否关联显示", description = "0:父子不互相关联显示 1:父子互相关联显示 ") + private boolean deptCheckStrictly; + + /** 角色状态(0正常 1停用) */ + @Schema(title = "角色状态", description = "0正常 1停用") + @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + @Schema(title = "删除标志", description = "0代表存在 2代表删除") + private String delFlag; + + /** 用户是否存在此角色标识 默认不存在 */ + @Schema(title = "用户是否存在此角色标识", description = "默认不存在") + private boolean flag = false; + + /** 菜单组 */ + @Schema(title = "菜单组") + private Long[] menuIds; + + /** 部门组(数据权限) */ + @Schema(title = "部门组", description = "数据权限") + private Long[] deptIds; + + /** 角色菜单权限 */ + @Schema(title = "角色菜单权限") + private Set permissions; + + public SysRole() + { + + } + + public SysRole(Long roleId) + { + this.roleId = roleId; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public boolean isAdmin() + { + return isAdmin(this.roleId); + } + + public static boolean isAdmin(Long roleId) + { + return roleId != null && 1L == roleId; + } + + @NotBlank(message = "角色名称不能为空") + @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符") + public String getRoleName() + { + return roleName; + } + + public void setRoleName(String roleName) + { + this.roleName = roleName; + } + + @NotBlank(message = "权限字符不能为空") + @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符") + public String getRoleKey() + { + return roleKey; + } + + public void setRoleKey(String roleKey) + { + this.roleKey = roleKey; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getRoleSort() + { + return roleSort; + } + + public void setRoleSort(Integer roleSort) + { + this.roleSort = roleSort; + } + + public String getDataScope() + { + return dataScope; + } + + public void setDataScope(String dataScope) + { + this.dataScope = dataScope; + } + + public boolean isMenuCheckStrictly() + { + return menuCheckStrictly; + } + + public void setMenuCheckStrictly(boolean menuCheckStrictly) + { + this.menuCheckStrictly = menuCheckStrictly; + } + + public boolean isDeptCheckStrictly() + { + return deptCheckStrictly; + } + + public void setDeptCheckStrictly(boolean deptCheckStrictly) + { + this.deptCheckStrictly = deptCheckStrictly; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public boolean isFlag() + { + return flag; + } + + public void setFlag(boolean flag) + { + this.flag = flag; + } + + public Long[] getMenuIds() + { + return menuIds; + } + + public void setMenuIds(Long[] menuIds) + { + this.menuIds = menuIds; + } + + public Long[] getDeptIds() + { + return deptIds; + } + + public void setDeptIds(Long[] deptIds) + { + this.deptIds = deptIds; + } + + public Set getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("roleName", getRoleName()) + .append("roleKey", getRoleKey()) + .append("roleSort", getRoleSort()) + .append("dataScope", getDataScope()) + .append("menuCheckStrictly", isMenuCheckStrictly()) + .append("deptCheckStrictly", isDeptCheckStrictly()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java new file mode 100644 index 0000000..8a56b1f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java @@ -0,0 +1,363 @@ +package com.ruoyi.common.core.domain.entity; + +import java.util.Date; +import java.util.List; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.annotation.Excel.Type; +import com.ruoyi.common.annotation.Excels; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.xss.Xss; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 用户对象 sys_user + * + * @author ruoyi + */ +@Schema(title = "用户") +public class SysUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 用户ID */ + @Schema(title = "用户序号") + @Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号") + private Long userId; + + /** 部门ID */ + @Schema(title = "部门编号") + @Excel(name = "部门编号", type = Type.IMPORT) + private Long deptId; + + /** 用户账号 */ + @Schema(title = "登录名称") + @Excel(name = "登录名称") + private String userName; + + /** 用户昵称 */ + @Schema(title = "用户名称") + @Excel(name = "用户名称") + private String nickName; + + /** 用户邮箱 */ + @Schema(title = "用户邮箱") + @Excel(name = "用户邮箱") + private String email; + + /** 手机号码 */ + @Schema(title = "手机号码") + @Excel(name = "手机号码") + private String phonenumber; + + /** 用户性别 */ + @Schema(title = "用户性别", description = "0=男,1=女,2=未知") + @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") + private String sex; + + /** 用户头像 */ + @Schema(title = "用户头像") + private String avatar; + + /** 密码 */ + @Schema(title = "密码") + private String password; + + /** 账号状态(0正常 1停用) */ + @Schema(title = "账号状态", description = "0正常 1停用") + @Excel(name = "账号状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + @Schema(title = "删除标志", description = "0代表存在 2代表删除") + private String delFlag; + + /** 最后登录IP */ + @Schema(title = "最后登录IP") + @Excel(name = "最后登录IP", type = Type.EXPORT) + private String loginIp; + + /** 最后登录时间 */ + @Schema(title = "最后登录时间") + @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) + private Date loginDate; + + /** 密码最后更新时间 */ + private Date pwdUpdateDate; + + /** 部门对象 */ + @Schema(title = "部门对象") + @Excels({ + @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT), + @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT) + }) + private SysDept dept; + + /** 角色对象 */ + @Schema(title = "角色对象") + private List roles; + + /** 角色组 */ + @Schema(title = "角色组") + private Long[] roleIds; + + /** 岗位组 */ + @Schema(title = "岗位组") + private Long[] postIds; + + /** 角色ID */ + @Schema(title = "角色ID") + private Long roleId; + + public SysUser() + { + + } + + public SysUser(Long userId) + { + this.userId = userId; + } + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public boolean isAdmin() + { + return isAdmin(this.userId); + } + + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + @Xss(message = "用户昵称不能包含脚本字符") + @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") + public String getNickName() + { + return nickName; + } + + public void setNickName(String nickName) + { + this.nickName = nickName; + } + + @Xss(message = "用户账号不能包含脚本字符") + @NotBlank(message = "用户账号不能为空") + @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符") + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符") + public String getPhonenumber() + { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) + { + this.phonenumber = phonenumber; + } + + public String getSex() + { + return sex; + } + + public void setSex(String sex) + { + this.sex = sex; + } + + public String getAvatar() + { + return avatar; + } + + public void setAvatar(String avatar) + { + this.avatar = avatar; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getLoginIp() + { + return loginIp; + } + + public void setLoginIp(String loginIp) + { + this.loginIp = loginIp; + } + + public Date getLoginDate() + { + return loginDate; + } + + public void setLoginDate(Date loginDate) + { + this.loginDate = loginDate; + } + + public Date getPwdUpdateDate() + { + return pwdUpdateDate; + } + + public void setPwdUpdateDate(Date pwdUpdateDate) + { + this.pwdUpdateDate = pwdUpdateDate; + } + + public SysDept getDept() + { + return dept; + } + + public void setDept(SysDept dept) + { + this.dept = dept; + } + + public List getRoles() + { + return roles; + } + + public void setRoles(List roles) + { + this.roles = roles; + } + + public Long[] getRoleIds() + { + return roleIds; + } + + public void setRoleIds(Long[] roleIds) + { + this.roleIds = roleIds; + } + + public Long[] getPostIds() + { + return postIds; + } + + public void setPostIds(Long[] postIds) + { + this.postIds = postIds; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("deptId", getDeptId()) + .append("userName", getUserName()) + .append("nickName", getNickName()) + .append("email", getEmail()) + .append("phonenumber", getPhonenumber()) + .append("sex", getSex()) + .append("avatar", getAvatar()) + .append("password", getPassword()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("loginIp", getLoginIp()) + .append("loginDate", getLoginDate()) + .append("pwdUpdateDate", getPwdUpdateDate()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("dept", getDept()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java new file mode 100644 index 0000000..0f68ff4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java @@ -0,0 +1,97 @@ +package com.ruoyi.common.core.domain.model; + +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户登录对象 + * + * @author ruoyi + */ +@Schema(title = "用户登录对象") +public class LoginBody extends BaseEntity { + /** + * 用户名 + */ + @Schema(title = "用户名") + private String username; + + /** + * 用户密码 + */ + @Schema(title = "用户密码") + private String password; + + /** + * 手机号码 + */ + @Schema(title = "手机号码") + private String phonenumber; + + /** + * 邮箱 + */ + @Schema(title = "邮箱") + private String email; + + /** + * 验证码 + */ + @Schema(title = "验证码") + private String code; + + /** + * 唯一标识 + */ + @Schema(title = "唯一标识") + private String uuid; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getPhonenumber() { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) { + this.phonenumber = phonenumber; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java new file mode 100644 index 0000000..e485384 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java @@ -0,0 +1,266 @@ +package com.ruoyi.common.core.domain.model; + +import java.util.Collection; +import java.util.Set; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import com.alibaba.fastjson2.annotation.JSONField; +import com.ruoyi.common.core.domain.entity.SysUser; + +/** + * 登录用户身份权限 + * + * @author ruoyi + */ +public class LoginUser implements UserDetails +{ + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户唯一标识 + */ + private String token; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 权限列表 + */ + private Set permissions; + + /** + * 用户信息 + */ + private SysUser user; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public String getToken() + { + return token; + } + + public void setToken(String token) + { + this.token = token; + } + + public LoginUser() + { + } + + public LoginUser(SysUser user, Set permissions) + { + this.user = user; + this.permissions = permissions; + } + + public LoginUser(Long userId, Long deptId, SysUser user, Set permissions) + { + this.userId = userId; + this.deptId = deptId; + this.user = user; + this.permissions = permissions; + } + + @JSONField(serialize = false) + @Override + public String getPassword() + { + return user.getPassword(); + } + + @Override + public String getUsername() + { + return user.getUserName(); + } + + /** + * 账户是否未过期,过期无法验证 + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonExpired() + { + return true; + } + + /** + * 指定用户是否解锁,锁定的用户无法进行身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonLocked() + { + return true; + } + + /** + * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isCredentialsNonExpired() + { + return true; + } + + /** + * 是否可用 ,禁用的用户不能身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isEnabled() + { + return true; + } + + public Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public Long getExpireTime() + { + return expireTime; + } + + public void setExpireTime(Long expireTime) + { + this.expireTime = expireTime; + } + + public Set getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + public SysUser getUser() + { + return user; + } + + public void setUser(SysUser user) + { + this.user = user; + } + + @Override + public Collection getAuthorities() + { + return null; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java new file mode 100644 index 0000000..1d32cc4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java @@ -0,0 +1,14 @@ +package com.ruoyi.common.core.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户注册对象 + * + * @author ruoyi + */ +@Schema(title = "用户注册对象") +public class RegisterBody extends LoginBody +{ + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java new file mode 100644 index 0000000..8966cb4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java @@ -0,0 +1,101 @@ +package com.ruoyi.common.core.page; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 分页数据 + * + * @author ruoyi + */ +public class PageDomain +{ + /** 当前记录起始索引 */ + private Integer pageNum; + + /** 每页显示记录数 */ + private Integer pageSize; + + /** 排序列 */ + private String orderByColumn; + + /** 排序的方向desc或者asc */ + private String isAsc = "asc"; + + /** 分页参数合理化 */ + private Boolean reasonable = true; + + public String getOrderBy() + { + if (StringUtils.isEmpty(orderByColumn)) + { + return ""; + } + return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc; + } + + public Integer getPageNum() + { + return pageNum; + } + + public void setPageNum(Integer pageNum) + { + this.pageNum = pageNum; + } + + public Integer getPageSize() + { + return pageSize; + } + + public void setPageSize(Integer pageSize) + { + this.pageSize = pageSize; + } + + public String getOrderByColumn() + { + return orderByColumn; + } + + public void setOrderByColumn(String orderByColumn) + { + this.orderByColumn = orderByColumn; + } + + public String getIsAsc() + { + return isAsc; + } + + public void setIsAsc(String isAsc) + { + if (StringUtils.isNotEmpty(isAsc)) + { + // 兼容前端排序类型 + if ("ascending".equals(isAsc)) + { + isAsc = "asc"; + } + else if ("descending".equals(isAsc)) + { + isAsc = "desc"; + } + this.isAsc = isAsc; + } + } + + public Boolean getReasonable() + { + if (StringUtils.isNull(reasonable)) + { + return Boolean.TRUE; + } + return reasonable; + } + + public void setReasonable(Boolean reasonable) + { + this.reasonable = reasonable; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java new file mode 100644 index 0000000..54e14d7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java @@ -0,0 +1,92 @@ +package com.ruoyi.common.core.page; + +import java.io.Serializable; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 表格分页数据对象 + * + * @author ruoyi + */ +@Schema(title = "表格分页数据对象") +public class TableDataInfo implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 总记录数 */ + @Schema(title = "总记录数") + private long total; + + /** 列表数据 */ + @Schema(title = "列表数据") + private List rows; + + /** 消息状态码 */ + @Schema(title = "消息状态码") + private int code; + + /** 消息内容 */ + @Schema(title = "消息内容") + private String msg; + + /** + * 表格数据对象 + */ + public TableDataInfo() + { + } + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableDataInfo(List list, int total) + { + this.rows = list; + this.total = total; + } + + public long getTotal() + { + return total; + } + + public void setTotal(long total) + { + this.total = total; + } + + public List getRows() + { + return rows; + } + + public void setRows(List rows) + { + this.rows = rows; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java new file mode 100644 index 0000000..a120c30 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java @@ -0,0 +1,56 @@ +package com.ruoyi.common.core.page; + +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.ServletUtils; + +/** + * 表格数据处理 + * + * @author ruoyi + */ +public class TableSupport +{ + /** + * 当前记录起始索引 + */ + public static final String PAGE_NUM = "pageNum"; + + /** + * 每页显示记录数 + */ + public static final String PAGE_SIZE = "pageSize"; + + /** + * 排序列 + */ + public static final String ORDER_BY_COLUMN = "orderByColumn"; + + /** + * 排序的方向 "desc" 或者 "asc". + */ + public static final String IS_ASC = "isAsc"; + + /** + * 分页参数合理化 + */ + public static final String REASONABLE = "reasonable"; + + /** + * 封装分页对象 + */ + public static PageDomain getPageDomain() + { + PageDomain pageDomain = new PageDomain(); + pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1)); + pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10)); + pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN)); + pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC)); + pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE)); + return pageDomain; + } + + public static PageDomain buildPageRequest() + { + return getPageDomain(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/security/service/IPermissionService.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/security/service/IPermissionService.java new file mode 100644 index 0000000..3d5ec22 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/security/service/IPermissionService.java @@ -0,0 +1,56 @@ +package com.ruoyi.common.core.security.service; + +public interface IPermissionService { + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + boolean hasPermi(String permission); + + /** + * 验证用户是否不具备某权限,与 hasPermi 逻辑相反 + * + * @param permission 权限字符串 + * @return 用户是否不具备某权限 + */ + default boolean lacksPermi(String permission) { + return !hasPermi(permission); + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + boolean hasAnyPermi(String permissions); + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色字符串 + * @return 用户是否具备某角色 + */ + boolean hasRole(String role); + + /** + * 验证用户是否不具备某角色,与 hasRole 逻辑相反。 + * + * @param role 角色名称 + * @return 用户是否不具备某角色 + */ + default boolean lacksRole(String role) { + return !hasRole(role); + } + + /** + * 验证用户是否具有以下任意一个角色 + * + * @param roles 以 ROLE_DELIMETER 为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + boolean hasAnyRoles(String roles); +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java new file mode 100644 index 0000000..84124aa --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java @@ -0,0 +1,86 @@ +package com.ruoyi.common.core.text; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.ruoyi.common.utils.StringUtils; + +/** + * 字符集工具类 + * + * @author ruoyi + */ +public class CharsetKit +{ + /** ISO-8859-1 */ + public static final String ISO_8859_1 = "ISO-8859-1"; + /** UTF-8 */ + public static final String UTF_8 = "UTF-8"; + /** GBK */ + public static final String GBK = "GBK"; + + /** ISO-8859-1 */ + public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1); + /** UTF-8 */ + public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8); + /** GBK */ + public static final Charset CHARSET_GBK = Charset.forName(GBK); + + /** + * 转换为Charset对象 + * + * @param charset 字符集,为空则返回默认字符集 + * @return Charset + */ + public static Charset charset(String charset) + { + return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, String srcCharset, String destCharset) + { + return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, Charset srcCharset, Charset destCharset) + { + if (null == srcCharset) + { + srcCharset = StandardCharsets.ISO_8859_1; + } + + if (null == destCharset) + { + destCharset = StandardCharsets.UTF_8; + } + + if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) + { + return source; + } + return new String(source.getBytes(srcCharset), destCharset); + } + + /** + * @return 系统字符集编码 + */ + public static String systemCharset() + { + return Charset.defaultCharset().name(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java new file mode 100644 index 0000000..9502f95 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java @@ -0,0 +1,858 @@ +package com.ruoyi.common.core.text; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.text.NumberFormat; +import java.util.Set; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 类型转换器 + * + * @author ruoyi + */ +public class Convert { + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static String toStr(Object value, String defaultValue) { + if (null == value) { + return defaultValue; + } + if (value instanceof String) { + return (String) value; + } + return value.toString(); + } + + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static String toStr(Object value) { + return toStr(value, null); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Character toChar(Object value, Character defaultValue) { + if (null == value) { + return defaultValue; + } + if (value instanceof Character) { + return (Character) value; + } + + final String valueStr = toStr(value, null); + return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Character toChar(Object value) { + return toChar(value, null); + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Byte toByte(Object value, Byte defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Byte) { + return (Byte) value; + } + if (value instanceof Number) { + return ((Number) value).byteValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return Byte.parseByte(valueStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Byte toByte(Object value) { + return toByte(value, null); + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Short toShort(Object value, Short defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Short) { + return (Short) value; + } + if (value instanceof Number) { + return ((Number) value).shortValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return Short.parseShort(valueStr.trim()); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Short toShort(Object value) { + return toShort(value, null); + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Number toNumber(Object value, Number defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Number) { + return (Number) value; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return NumberFormat.getInstance().parse(valueStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Number toNumber(Object value) { + return toNumber(value, null); + } + + /** + * 转换为int
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Integer toInt(Object value, Integer defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Integer) { + return (Integer) value; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return Integer.parseInt(valueStr.trim()); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为int
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Integer toInt(Object value) { + return toInt(value, null); + } + + /** + * 转换为Integer数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String str) { + return toIntArray(",", str); + } + + /** + * 转换为Long数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String str) { + return toLongArray(",", str); + } + + /** + * 转换为Integer数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String split, String str) { + if (StringUtils.isEmpty(str)) { + return new Integer[] {}; + } + String[] arr = str.split(split); + final Integer[] ints = new Integer[arr.length]; + for (int i = 0; i < arr.length; i++) { + final Integer v = toInt(arr[i], 0); + ints[i] = v; + } + return ints; + } + + /** + * 转换为Long数组
+ * + * @param split 分隔符 + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String split, String str) { + if (StringUtils.isEmpty(str)) { + return new Long[] {}; + } + String[] arr = str.split(split); + final Long[] longs = new Long[arr.length]; + for (int i = 0; i < arr.length; i++) { + final Long v = toLong(arr[i], null); + longs[i] = v; + } + return longs; + } + + /** + * 转换为String数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String str) { + if (StringUtils.isEmpty(str)) { + return new String[] {}; + } + return toStrArray(",", str); + } + + /** + * 转换为String数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String split, String str) { + return str.split(split); + } + + /** + * 转换为long
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Long toLong(Object value, Long defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Long) { + return (Long) value; + } + if (value instanceof Number) { + return ((Number) value).longValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).longValue(); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为long
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Long toLong(Object value) { + return toLong(value, null); + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Double toDouble(Object value, Double defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Double) { + return (Double) value; + } + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).doubleValue(); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Double toDouble(Object value) { + return toDouble(value, null); + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Float toFloat(Object value, Float defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Float) { + return (Float) value; + } + if (value instanceof Number) { + return ((Number) value).floatValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return Float.parseFloat(valueStr.trim()); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Float toFloat(Object value) { + return toFloat(value, null); + } + + /** + * 转换为boolean
+ * String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Boolean toBool(Object value, Boolean defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Boolean) { + return (Boolean) value; + } + String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + valueStr = valueStr.trim().toLowerCase(); + switch (valueStr) { + case "true": + case "yes": + case "ok": + case "1": + return true; + case "false": + case "no": + case "0": + return false; + default: + return defaultValue; + } + } + + /** + * 转换为boolean
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Boolean toBool(Object value) { + return toBool(value, null); + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * + * @param clazz Enum的Class + * @param value 值 + * @param defaultValue 默认值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value, E defaultValue) { + if (value == null) { + return defaultValue; + } + if (clazz.isAssignableFrom(value.getClass())) { + @SuppressWarnings("unchecked") + E myE = (E) value; + return myE; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return Enum.valueOf(clazz, valueStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * + * @param clazz Enum的Class + * @param value 值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value) { + return toEnum(clazz, value, null); + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value, BigInteger defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof BigInteger) { + return (BigInteger) value; + } + if (value instanceof Long) { + return BigInteger.valueOf((Long) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return new BigInteger(valueStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value) { + return toBigInteger(value, null); + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof BigDecimal) { + return (BigDecimal) value; + } + if (value instanceof Long) { + return new BigDecimal((Long) value); + } + if (value instanceof Double) { + return BigDecimal.valueOf((Double) value); + } + if (value instanceof Integer) { + return new BigDecimal((Integer) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) { + return defaultValue; + } + try { + return new BigDecimal(valueStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value) { + return toBigDecimal(value, null); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @return 字符串 + */ + public static String utf8Str(Object obj) { + return str(obj, CharsetKit.CHARSET_UTF_8); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charsetName 字符集 + * @return 字符串 + */ + public static String str(Object obj, String charsetName) { + return str(obj, Charset.forName(charsetName)); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(Object obj, Charset charset) { + if (null == obj) { + return null; + } + + if (obj instanceof String) { + return (String) obj; + } else if (obj instanceof byte[] || obj instanceof Byte[]) { + if (obj instanceof byte[]) { + return str((byte[]) obj, charset); + } else { + Byte[] bytes = (Byte[]) obj; + int length = bytes.length; + byte[] dest = new byte[length]; + for (int i = 0; i < length; i++) { + dest[i] = bytes[i]; + } + return str(dest, charset); + } + } else if (obj instanceof ByteBuffer) { + return str((ByteBuffer) obj, charset); + } + return obj.toString(); + } + + /** + * 将byte数组转为字符串 + * + * @param bytes byte数组 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(byte[] bytes, String charset) { + return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset)); + } + + /** + * 解码字节码 + * + * @param data 字符串 + * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 + * @return 解码后的字符串 + */ + public static String str(byte[] data, Charset charset) { + if (data == null) { + return null; + } + + if (null == charset) { + return new String(data); + } + return new String(data, charset); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, String charset) { + if (data == null) { + return null; + } + + return str(data, Charset.forName(charset)); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, Charset charset) { + if (null == charset) { + charset = Charset.defaultCharset(); + } + return charset.decode(data).toString(); + } + + // ----------------------------------------------------------------------- + // 全角半角转换 + /** + * 半角转全角 + * + * @param input String. + * @return 全角字符串. + */ + public static String toSBC(String input) { + return toSBC(input, null); + } + + /** + * 半角转全角 + * + * @param input String + * @param notConvertSet 不替换的字符集合 + * @return 全角字符串. + */ + public static String toSBC(String input, Set notConvertSet) { + char[] c = input.toCharArray(); + for (int i = 0; i < c.length; i++) { + if (null != notConvertSet && notConvertSet.contains(c[i])) { + // 跳过不替换的字符 + continue; + } + + if (c[i] == ' ') { + c[i] = '\u3000'; + } else if (c[i] < '\177') { + c[i] = (char) (c[i] + 65248); + + } + } + return new String(c); + } + + /** + * 全角转半角 + * + * @param input String. + * @return 半角字符串 + */ + public static String toDBC(String input) { + return toDBC(input, null); + } + + /** + * 替换全角为半角 + * + * @param text 文本 + * @param notConvertSet 不替换的字符集合 + * @return 替换后的字符 + */ + public static String toDBC(String text, Set notConvertSet) { + char[] c = text.toCharArray(); + for (int i = 0; i < c.length; i++) { + if (null != notConvertSet && notConvertSet.contains(c[i])) { + // 跳过不替换的字符 + continue; + } + + if (c[i] == '\u3000') { + c[i] = ' '; + } else if (c[i] > '\uFF00' && c[i] < '\uFF5F') { + c[i] = (char) (c[i] - 65248); + } + } + + return new String(c); + } + + /** + * 数字金额大写转换 先写个完整的然后将如零拾替换成零 + * + * @param n 数字 + * @return 中文大写数字 + */ + public static String digitUppercase(double n) { + String[] fraction = { "角", "分" }; + String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" }; + String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } }; + + String head = n < 0 ? "负" : ""; + n = Math.abs(n); + + String s = ""; + for (int i = 0; i < fraction.length; i++) { + s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); + } + if (s.length() < 1) { + s = "整"; + } + int integerPart = (int) Math.floor(n); + + for (int i = 0; i < unit[0].length && integerPart > 0; i++) { + String p = ""; + for (int j = 0; j < unit[1].length && n > 0; j++) { + p = digit[integerPart % 10] + unit[1][j] + p; + integerPart = integerPart / 10; + } + s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s; + } + return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", + "零元整"); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java new file mode 100644 index 0000000..c78ac77 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java @@ -0,0 +1,92 @@ +package com.ruoyi.common.core.text; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 字符串格式化 + * + * @author ruoyi + */ +public class StrFormatter +{ + public static final String EMPTY_JSON = "{}"; + public static final char C_BACKSLASH = '\\'; + public static final char C_DELIM_START = '{'; + public static final char C_DELIM_END = '}'; + + /** + * 格式化字符串
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param strPattern 字符串模板 + * @param argArray 参数列表 + * @return 结果 + */ + public static String format(final String strPattern, final Object... argArray) + { + if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) + { + return strPattern; + } + final int strPatternLength = strPattern.length(); + + // 初始化定义好的长度以获得更好的性能 + StringBuilder sbuf = new StringBuilder(strPatternLength + 50); + + int handledPosition = 0; + int delimIndex;// 占位符所在位置 + for (int argIndex = 0; argIndex < argArray.length; argIndex++) + { + delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition); + if (delimIndex == -1) + { + if (handledPosition == 0) + { + return strPattern; + } + else + { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 + sbuf.append(strPattern, handledPosition, strPatternLength); + return sbuf.toString(); + } + } + else + { + if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) + { + if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) + { + // 转义符之前还有一个转义符,占位符依旧有效 + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + else + { + // 占位符被转义 + argIndex--; + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(C_DELIM_START); + handledPosition = delimIndex + 1; + } + } + else + { + // 正常占位符 + sbuf.append(strPattern, handledPosition, delimIndex); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + } + } + // 加入最后一个占位符后所有的字符 + sbuf.append(strPattern, handledPosition, strPattern.length()); + + return sbuf.toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java new file mode 100644 index 0000000..10b7306 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java @@ -0,0 +1,20 @@ +package com.ruoyi.common.enums; + +/** + * 操作状态 + * + * @author ruoyi + * + */ +public enum BusinessStatus +{ + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java new file mode 100644 index 0000000..2e17c4a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java @@ -0,0 +1,59 @@ +package com.ruoyi.common.enums; + +/** + * 业务操作类型 + * + * @author ruoyi + */ +public enum BusinessType +{ + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空数据 + */ + CLEAN, +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java new file mode 100644 index 0000000..61ba373 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.enums; + +/** + * 数据源 + * + * @author ruoyi + */ +public enum DataSourceType { + /** + * 主库 + */ + MASTER, + + /** + * 从库 + */ + SLAVE, +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/DesensitizedType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DesensitizedType.java new file mode 100644 index 0000000..e2ad050 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DesensitizedType.java @@ -0,0 +1,60 @@ +package com.ruoyi.common.enums; + +import java.util.function.Function; + +import com.ruoyi.common.utils.DesensitizedUtil; + +/** + * 脱敏类型 + * + * @author ruoyi + */ +public enum DesensitizedType +{ + /** + * 姓名,第2位星号替换 + */ + USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")), + + /** + * 密码,全部字符都用*代替 + */ + PASSWORD(DesensitizedUtil::password), + + /** + * 身份证,中间10位星号替换 + */ + ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\d{3}[Xx]|\\d{4})", "$1** **** ****$2")), + + /** + * 手机号,中间4位星号替换 + */ + PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")), + + /** + * 电子邮箱,仅显示第一个字母和@后面的地址显示,其他星号替换 + */ + EMAIL(s -> s.replaceAll("(^.)[^@]*(@.*$)", "$1****$2")), + + /** + * 银行卡号,保留最后4位,其他星号替换 + */ + BANK_CARD(s -> s.replaceAll("\\d{15}(\\d{3})", "**** **** **** **** $1")), + + /** + * 车牌号码,包含普通车辆、新能源车辆 + */ + CAR_LICENSE(DesensitizedUtil::carLicense); + + private final Function desensitizer; + + DesensitizedType(Function desensitizer) + { + this.desensitizer = desensitizer; + } + + public Function desensitizer() + { + return desensitizer; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java new file mode 100644 index 0000000..8d8eca3 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java @@ -0,0 +1,52 @@ +package com.ruoyi.common.enums; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.lang.Nullable; + +/** + * 请求方式 + * + * @author ruoyi + */ +public enum HttpMethod +{ + /** GET 请求 */ + GET, + /** POST 请求 */ + HEAD, + /** HEAD 请求 */ + POST, + /** PUT 请求 */ + PUT, + /** PATCH 请求 */ + PATCH, + /** DELETE 请求 */ + DELETE, + /** OPTIONS 请求 */ + OPTIONS, + /** TRACE 请求 */ + TRACE; + + private static final Map mappings = new HashMap<>(16); + + static + { + for (HttpMethod httpMethod : values()) + { + mappings.put(httpMethod.name(), httpMethod); + } + } + + @Nullable + public static HttpMethod resolve(@Nullable String method) + { + return (method != null ? mappings.get(method) : null); + } + + public boolean matches(String method) + { + return (this == resolve(method)); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java new file mode 100644 index 0000000..9ae87f6 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java @@ -0,0 +1,29 @@ +package com.ruoyi.common.enums; + +/** + * 限流类型 + * + * @author ruoyi + */ + +public enum LimitType { + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP, + + /** + * 根据请求者的用户ID进行限流 + */ + USER, + + /** + * 根据请求者的部门进行限流 + */ + DEPT, +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/MessageType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/MessageType.java new file mode 100644 index 0000000..240981a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/MessageType.java @@ -0,0 +1,29 @@ +package com.ruoyi.common.enums; + +public enum MessageType { + + /** + * 事件 + */ + EVENT("event"), + + /** + * 普通消息 + */ + MESSAGE("message"), + + /** + * 异步消息 + */ + ASYNC_MESSAGE("asyncMessage"); + + private final String type; + + MessageType(String type) { + this.type = type; + } + + public String getType() { + return type; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java new file mode 100644 index 0000000..bdd143c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.enums; + +/** + * 操作人类别 + * + * @author ruoyi + */ +public enum OperatorType +{ + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java new file mode 100644 index 0000000..28a43db --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java @@ -0,0 +1,35 @@ +package com.ruoyi.common.enums; + +/** + * 用户状态 + * + * @author ruoyi + */ +public enum UserStatus +{ + /** 正常 */ + OK("0", "正常"), + /** 停用 */ + DISABLE("1", "停用"), + /** 删除 */ + DELETED("2", "删除"); + + private final String code; + private final String info; + + UserStatus(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java new file mode 100644 index 0000000..f6ad2ab --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java @@ -0,0 +1,15 @@ +package com.ruoyi.common.exception; + +/** + * 演示模式异常 + * + * @author ruoyi + */ +public class DemoModeException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public DemoModeException() + { + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java new file mode 100644 index 0000000..81a71b5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java @@ -0,0 +1,58 @@ +package com.ruoyi.common.exception; + +/** + * 全局异常 + * + * @author ruoyi + */ +public class GlobalException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public GlobalException() + { + } + + public GlobalException(String message) + { + this.message = message; + } + + public String getDetailMessage() + { + return detailMessage; + } + + public GlobalException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } + + @Override + public String getMessage() + { + return message; + } + + public GlobalException setMessage(String message) + { + this.message = message; + return this; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java new file mode 100644 index 0000000..fcc7ab6 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java @@ -0,0 +1,74 @@ +package com.ruoyi.common.exception; + +/** + * 业务异常 + * + * @author ruoyi + */ +public final class ServiceException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() + { + } + + public ServiceException(String message) + { + this.message = message; + } + + public ServiceException(String message, Integer code) + { + this.message = message; + this.code = code; + } + + public String getDetailMessage() + { + return detailMessage; + } + + @Override + public String getMessage() + { + return message; + } + + public Integer getCode() + { + return code; + } + + public ServiceException setMessage(String message) + { + this.message = message; + return this; + } + + public ServiceException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java new file mode 100644 index 0000000..980fa46 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java @@ -0,0 +1,26 @@ +package com.ruoyi.common.exception; + +/** + * 工具类异常 + * + * @author ruoyi + */ +public class UtilException extends RuntimeException +{ + private static final long serialVersionUID = 8247610319171014183L; + + public UtilException(Throwable e) + { + super(e.getMessage(), e); + } + + public UtilException(String message) + { + super(message); + } + + public UtilException(String message, Throwable throwable) + { + super(message, throwable); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java new file mode 100644 index 0000000..b55d72e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java @@ -0,0 +1,97 @@ +package com.ruoyi.common.exception.base; + +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * 基础异常 + * + * @author ruoyi + */ +public class BaseException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 所属模块 + */ + private String module; + + /** + * 错误码 + */ + private String code; + + /** + * 错误码对应的参数 + */ + private Object[] args; + + /** + * 错误消息 + */ + private String defaultMessage; + + public BaseException(String module, String code, Object[] args, String defaultMessage) + { + this.module = module; + this.code = code; + this.args = args; + this.defaultMessage = defaultMessage; + } + + public BaseException(String module, String code, Object[] args) + { + this(module, code, args, null); + } + + public BaseException(String module, String defaultMessage) + { + this(module, null, null, defaultMessage); + } + + public BaseException(String code, Object[] args) + { + this(null, code, args, null); + } + + public BaseException(String defaultMessage) + { + this(null, null, null, defaultMessage); + } + + @Override + public String getMessage() + { + String message = null; + if (!StringUtils.isEmpty(code)) + { + message = MessageUtils.message(code, args); + } + if (message == null) + { + message = defaultMessage; + } + return message; + } + + public String getModule() + { + return module; + } + + public String getCode() + { + return code; + } + + public Object[] getArgs() + { + return args; + } + + public String getDefaultMessage() + { + return defaultMessage; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java new file mode 100644 index 0000000..8fac7ce --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.exception.file; + +import com.ruoyi.common.exception.base.BaseException; + +/** + * 文件异常类 + * + * @author ruoyi + */ +public class FileException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public FileException(String code, Object[] args) + { + super("file", code, args, null); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 0000000..70e0ec9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.file; + +/** + * 文件名称超长限制异常类 + * + * @author ruoyi + */ +public class FileNameLengthLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileNameLengthLimitExceededException(int defaultFileNameLength) + { + super("upload.filename.exceed.length", new Object[] { defaultFileNameLength }); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 0000000..ec6ab05 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.file; + +/** + * 文件名大小限制异常类 + * + * @author ruoyi + */ +public class FileSizeLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileSizeLimitExceededException(long defaultMaxSize) + { + super("upload.exceed.maxSize", new Object[] { defaultMaxSize }); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java new file mode 100644 index 0000000..f45e7ef --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java @@ -0,0 +1,61 @@ +package com.ruoyi.common.exception.file; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * 文件上传异常类 + * + * @author ruoyi + */ +public class FileUploadException extends Exception +{ + + private static final long serialVersionUID = 1L; + + private final Throwable cause; + + public FileUploadException() + { + this(null, null); + } + + public FileUploadException(final String msg) + { + this(msg, null); + } + + public FileUploadException(String msg, Throwable cause) + { + super(msg); + this.cause = cause; + } + + @Override + public void printStackTrace(PrintStream stream) + { + super.printStackTrace(stream); + if (cause != null) + { + stream.println("Caused by:"); + cause.printStackTrace(stream); + } + } + + @Override + public void printStackTrace(PrintWriter writer) + { + super.printStackTrace(writer); + if (cause != null) + { + writer.println("Caused by:"); + cause.printStackTrace(writer); + } + } + + @Override + public Throwable getCause() + { + return cause; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java new file mode 100644 index 0000000..011f308 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java @@ -0,0 +1,80 @@ +package com.ruoyi.common.exception.file; + +import java.util.Arrays; + +/** + * 文件上传 误异常类 + * + * @author ruoyi + */ +public class InvalidExtensionException extends FileUploadException +{ + private static final long serialVersionUID = 1L; + + private String[] allowedExtension; + private String extension; + private String filename; + + public InvalidExtensionException(String[] allowedExtension, String extension, String filename) + { + super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式"); + this.allowedExtension = allowedExtension; + this.extension = extension; + this.filename = filename; + } + + public String[] getAllowedExtension() + { + return allowedExtension; + } + + public String getExtension() + { + return extension; + } + + public String getFilename() + { + return filename; + } + + public static class InvalidImageExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidFlashExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidMediaExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidVideoExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java new file mode 100644 index 0000000..7975243 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java @@ -0,0 +1,40 @@ +package com.ruoyi.common.exception.job; + +/** + * 计划策略异常 + * + * @author ruoyi + */ +public class TaskException extends Exception { + private static final long serialVersionUID = 1L; + + private Code code; + + public TaskException(String msg, Code code) { + this(msg, code, null); + } + + public TaskException(String msg, Code code, Exception nestedEx) { + super(msg, nestedEx); + this.code = code; + } + + public Code getCode() { + return code; + } + + public enum Code { + /** 任务存在 */ + TASK_EXISTS, + /** 任务不存在 */ + NO_TASK_EXISTS, + /** 任务已经开始 */ + TASK_ALREADY_STARTED, + /** 未知 */ + UNKNOWN, + /** 配置错误 */ + CONFIG_ERROR, + /** 任务节点不可用 */ + TASK_NODE_NOT_AVAILABLE + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java new file mode 100644 index 0000000..2bf5038 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 黑名单IP异常类 + * + * @author ruoyi + */ +public class BlackListException extends UserException +{ + private static final long serialVersionUID = 1L; + + public BlackListException() + { + super("login.blocked", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java new file mode 100644 index 0000000..389dbc7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 验证码错误异常类 + * + * @author ruoyi + */ +public class CaptchaException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaException() + { + super("user.jcaptcha.error", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java new file mode 100644 index 0000000..85f9486 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 验证码失效异常类 + * + * @author ruoyi + */ +public class CaptchaExpireException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaExpireException() + { + super("user.jcaptcha.expire", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/IpRetryLimitExceedException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/IpRetryLimitExceedException.java new file mode 100644 index 0000000..8eafd6d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/IpRetryLimitExceedException.java @@ -0,0 +1,15 @@ +package com.ruoyi.common.exception.user; + +/** + * IP 登录重试次数超限异常类 + * + */ +public class IpRetryLimitExceedException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public IpRetryLimitExceedException(int retryLimitCount, int lockTime) + { + super("失败次数过多,你的ip暂时被限制登录."); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java new file mode 100644 index 0000000..c292d70 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.exception.user; + +import com.ruoyi.common.exception.base.BaseException; + +/** + * 用户信息异常类 + * + * @author ruoyi + */ +public class UserException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public UserException(String code, Object[] args) + { + super("user", code, args, null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java new file mode 100644 index 0000000..eff8181 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 用户不存在异常类 + * + * @author ruoyi + */ +public class UserNotExistsException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserNotExistsException() + { + super("user.not.exists", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java new file mode 100644 index 0000000..a7f3e5f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 用户密码不正确或不符合规范异常类 + * + * @author ruoyi + */ +public class UserPasswordNotMatchException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordNotMatchException() + { + super("user.password.not.match", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java new file mode 100644 index 0000000..c887cf1 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 用户错误最大次数异常类 + * + * @author ruoyi + */ +public class UserPasswordRetryLimitExceedException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime) + { + super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime }); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java new file mode 100644 index 0000000..e1e431b --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.filter; + +import com.alibaba.fastjson2.filter.SimplePropertyPreFilter; + +/** + * 排除JSON敏感属性 + * + * @author ruoyi + */ +public class PropertyPreExcludeFilter extends SimplePropertyPreFilter +{ + public PropertyPreExcludeFilter() + { + } + + public PropertyPreExcludeFilter addExcludes(String... filters) + { + for (int i = 0; i < filters.length; i++) + { + this.getExcludes().add(filters[i]); + } + return this; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RefererFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RefererFilter.java new file mode 100644 index 0000000..3bbdd07 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RefererFilter.java @@ -0,0 +1,68 @@ +package com.ruoyi.common.filter; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 防盗链过滤器 + * + * @author ruoyi + */ +public class RefererFilter implements Filter { + /** + * 允许的域名列表 + */ + public List allowedDomains; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String domains = filterConfig.getInitParameter("allowedDomains"); + this.allowedDomains = Arrays.asList(domains.split(",")); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + String referer = req.getHeader("Referer"); + + // 如果Referer为空,拒绝访问 + if (referer == null || referer.isEmpty()) { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied: Referer header is required"); + return; + } + + // 检查Referer是否在允许的域名列表中 + boolean allowed = false; + for (String domain : allowedDomains) { + if (referer.contains(domain)) { + allowed = true; + break; + } + } + + // 根据检查结果决定是否放行 + if (allowed) { + chain.doFilter(request, response); + } else { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied: Referer '" + referer + "' is not allowed"); + } + } + + @Override + public void destroy() { + + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java new file mode 100644 index 0000000..42fcd29 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java @@ -0,0 +1,52 @@ +package com.ruoyi.common.filter; + +import java.io.IOException; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.MediaType; +import com.ruoyi.common.utils.StringUtils; + +/** + * Repeatable 过滤器 + * + * @author ruoyi + */ +public class RepeatableFilter implements Filter +{ + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest + && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) + { + requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); + } + if (null == requestWrapper) + { + chain.doFilter(request, response); + } + else + { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() + { + + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 0000000..5c6ee08 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,76 @@ +package com.ruoyi.common.filter; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import com.ruoyi.common.utils.http.HttpHelper; +import com.ruoyi.common.constant.Constants; + +/** + * 构建可重复读取inputStream的request + * + * @author ruoyi + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper +{ + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException + { + super(request); + request.setCharacterEncoding(Constants.UTF8); + response.setCharacterEncoding(Constants.UTF8); + + body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8); + } + + @Override + public BufferedReader getReader() throws IOException + { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() + { + @Override + public int read() throws IOException + { + return bais.read(); + } + + @Override + public int available() throws IOException + { + return body.length; + } + + @Override + public boolean isFinished() + { + return false; + } + + @Override + public boolean isReady() + { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) + { + + } + }; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java new file mode 100644 index 0000000..da76913 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java @@ -0,0 +1,75 @@ +package com.ruoyi.common.filter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.enums.HttpMethod; + +/** + * 防止XSS攻击的过滤器 + * + * @author ruoyi + */ +public class XssFilter implements Filter +{ + /** + * 排除链接 + */ + public List excludes = new ArrayList<>(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + String tempExcludes = filterConfig.getInitParameter("excludes"); + if (StringUtils.isNotEmpty(tempExcludes)) + { + String[] url = tempExcludes.split(","); + for (int i = 0; url != null && i < url.length; i++) + { + excludes.add(url[i]); + } + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + if (handleExcludeURL(req, resp)) + { + chain.doFilter(request, response); + return; + } + XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); + chain.doFilter(xssRequest, response); + } + + private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) + { + String url = request.getServletPath(); + String method = request.getMethod(); + // GET DELETE 不过滤 + if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) + { + return true; + } + return StringUtils.matches(url, excludes); + } + + @Override + public void destroy() + { + + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..bf7837b --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,111 @@ +package com.ruoyi.common.filter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.html.EscapeUtil; + +/** + * XSS过滤处理 + * + * @author ruoyi + */ +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper +{ + /** + * @param request + */ + public XssHttpServletRequestWrapper(HttpServletRequest request) + { + super(request); + } + + @Override + public String[] getParameterValues(String name) + { + String[] values = super.getParameterValues(name); + if (values != null) + { + int length = values.length; + String[] escapesValues = new String[length]; + for (int i = 0; i < length; i++) + { + // 防xss攻击和过滤前后空格 + escapesValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapesValues; + } + return super.getParameterValues(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + // 非json类型,直接返回 + if (!isJsonRequest()) + { + return super.getInputStream(); + } + + // 为空,直接返回 + String json = IOUtils.toString(super.getInputStream(), "utf-8"); + if (StringUtils.isEmpty(json)) + { + return super.getInputStream(); + } + + // xss过滤 + json = EscapeUtil.clean(json).trim(); + byte[] jsonBytes = json.getBytes("utf-8"); + final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes); + return new ServletInputStream() + { + @Override + public boolean isFinished() + { + return true; + } + + @Override + public boolean isReady() + { + return true; + } + + @Override + public int available() throws IOException + { + return jsonBytes.length; + } + + @Override + public void setReadListener(ReadListener readListener) + { + } + + @Override + public int read() throws IOException + { + return bis.read(); + } + }; + } + + /** + * 是否是Json请求 + * + * @param request + */ + public boolean isJsonRequest() + { + String header = super.getHeader(HttpHeaders.CONTENT_TYPE); + return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/handler/GenericListTypeHandler.java b/ruoyi-common/src/main/java/com/ruoyi/common/handler/GenericListTypeHandler.java new file mode 100644 index 0000000..0d85203 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/handler/GenericListTypeHandler.java @@ -0,0 +1,131 @@ +package com.ruoyi.common.handler; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 通用的List类型处理器,可处理任何元素类型 + * + * 用法示例: + * 1. 在XML中配置: + * {@code } + * + * 2. 在字段上使用注解: + * {@code @Result(column="tags", property="tags", + * typeHandler=GenericListTypeHandler.StringList.class)} + * + * 3. 在xml插值语法中使用 + * {@code #{tags,typeHandler=com.ruoyi.common.handler.GenericListTypeHandler$StringList} + * } + * + * @param 列表元素类型 + */ +public class GenericListTypeHandler extends BaseTypeHandler> { + + private final Function converter; + + /** + * 构造函数 + * + * @param converter 字符串到元素类型T的转换器 + */ + protected GenericListTypeHandler(Function converter) { + this.converter = converter; + } + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, List parameter, JdbcType jdbcType) + throws SQLException { + String value = StringUtils.join(parameter, ","); + ps.setString(i, value); + } + + @Override + public List getNullableResult(ResultSet rs, String columnName) throws SQLException { + return convertToList(rs.getString(columnName)); + } + + @Override + public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + return convertToList(rs.getString(columnIndex)); + } + + @Override + public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + return convertToList(cs.getString(columnIndex)); + } + + private List convertToList(String value) { + if (StringUtils.isBlank(value)) { + return null; + } + + List list = new ArrayList<>(); + String[] items = value.split(","); + for (String item : items) { + if (StringUtils.isNotBlank(item)) { + list.add(converter.apply(item.trim())); + } + } + return list; + } + + // 内部类 - 常用类型的TypeHandler实现 + + /** + * String类型列表的TypeHandler + */ + public static class StringList extends GenericListTypeHandler { + public StringList() { + super(String::valueOf); + } + } + + /** + * Integer类型列表的TypeHandler + */ + public static class IntegerList extends GenericListTypeHandler { + public IntegerList() { + super(Integer::valueOf); + } + } + + /** + * Long类型列表的TypeHandler + */ + public static class LongList extends GenericListTypeHandler { + public LongList() { + super(Long::valueOf); + } + } + + /** + * Double类型列表的TypeHandler + */ + public static class DoubleList extends GenericListTypeHandler { + public DoubleList() { + super(Double::valueOf); + } + } + + /** + * Boolean类型列表的TypeHandler + */ + public static class BooleanList extends GenericListTypeHandler { + public BooleanList() { + super(Boolean::valueOf); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/service/cache/CacheKeys.java b/ruoyi-common/src/main/java/com/ruoyi/common/service/cache/CacheKeys.java new file mode 100644 index 0000000..989825e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/service/cache/CacheKeys.java @@ -0,0 +1,10 @@ +package com.ruoyi.common.service.cache; + +import java.util.Set; + +import org.springframework.cache.Cache; + +public interface CacheKeys { + + public Set getCachekeys(final Cache cache); +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/service/cache/CacheNoTimeOut.java b/ruoyi-common/src/main/java/com/ruoyi/common/service/cache/CacheNoTimeOut.java new file mode 100644 index 0000000..825178c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/service/cache/CacheNoTimeOut.java @@ -0,0 +1,7 @@ +package com.ruoyi.common.service.cache; + +public interface CacheNoTimeOut { + + public void setCacheObject(final String cacheName,final String key, final T value); + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/service/cache/CacheTimeOut.java b/ruoyi-common/src/main/java/com/ruoyi/common/service/cache/CacheTimeOut.java new file mode 100644 index 0000000..58d9a74 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/service/cache/CacheTimeOut.java @@ -0,0 +1,10 @@ +package com.ruoyi.common.service.cache; + +import java.util.concurrent.TimeUnit; + +public interface CacheTimeOut extends CacheNoTimeOut { + + public void setCacheObject(final String cacheName, final String key, final T value, final long timeout, + final TimeUnit timeUnit); + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/service/datasource/AfterCreateDataSource.java b/ruoyi-common/src/main/java/com/ruoyi/common/service/datasource/AfterCreateDataSource.java new file mode 100644 index 0000000..5636f56 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/service/datasource/AfterCreateDataSource.java @@ -0,0 +1,10 @@ +package com.ruoyi.common.service.datasource; + +import java.util.Properties; + +import javax.sql.CommonDataSource; +import javax.sql.DataSource; + +public interface AfterCreateDataSource { + DataSource afterCreateDataSource(String name,Properties prop, CommonDataSource dataSource); +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/service/datasource/CreateDataSource.java b/ruoyi-common/src/main/java/com/ruoyi/common/service/datasource/CreateDataSource.java new file mode 100644 index 0000000..0814d84 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/service/datasource/CreateDataSource.java @@ -0,0 +1,9 @@ +package com.ruoyi.common.service.datasource; + +import java.util.Properties; + +import javax.sql.CommonDataSource; + +public interface CreateDataSource { + CommonDataSource createDataSource(String name, Properties prop); +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/service/mybatis/CreateSqlSessionFactory.java b/ruoyi-common/src/main/java/com/ruoyi/common/service/mybatis/CreateSqlSessionFactory.java new file mode 100644 index 0000000..cee120c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/service/mybatis/CreateSqlSessionFactory.java @@ -0,0 +1,10 @@ +package com.ruoyi.common.service.mybatis; + +import javax.sql.DataSource; + +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.core.env.Environment; + +public interface CreateSqlSessionFactory { + public SqlSessionFactory createSqlSessionFactory(Environment env, DataSource dataSource) throws Exception; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java new file mode 100644 index 0000000..b6326c2 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java @@ -0,0 +1,114 @@ +package com.ruoyi.common.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 精确的浮点数运算 + * + * @author ruoyi + */ +public class Arith +{ + + /** 默认除法运算精度 */ + private static final int DEF_DIV_SCALE = 10; + + /** 这个类不能实例化 */ + private Arith() + { + } + + /** + * 提供精确的加法运算。 + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + public static double add(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.add(b2).doubleValue(); + } + + /** + * 提供精确的减法运算。 + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static double sub(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.subtract(b2).doubleValue(); + } + + /** + * 提供精确的乘法运算。 + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static double mul(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.multiply(b2).doubleValue(); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 + * 小数点以后10位,以后的数字四舍五入。 + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的商 + */ + public static double div(double v1, double v2) + { + return div(v1, v2, DEF_DIV_SCALE); + } + + /** + * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 + * 定精度,以后的数字四舍五入。 + * @param v1 被除数 + * @param v2 除数 + * @param scale 表示表示需要精确到小数点以后几位。 + * @return 两个参数的商 + */ + public static double div(double v1, double v2, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + if (b1.compareTo(BigDecimal.ZERO) == 0) + { + return BigDecimal.ZERO.doubleValue(); + } + return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); + } + + /** + * 提供精确的小数位四舍五入处理。 + * @param v 需要四舍五入的数字 + * @param scale 小数点后保留几位 + * @return 四舍五入后的结果 + */ + public static double round(double v, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b = new BigDecimal(Double.toString(v)); + BigDecimal one = BigDecimal.ONE; + return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java new file mode 100644 index 0000000..c7240da --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java @@ -0,0 +1,161 @@ +package com.ruoyi.common.utils; + +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.jcache.JCacheCache; +import org.springframework.cache.transaction.TransactionAwareCacheDecorator; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +import com.ruoyi.common.service.cache.CacheKeys; +import com.ruoyi.common.service.cache.CacheTimeOut; +import com.ruoyi.common.utils.spring.SpringUtils; + +public class CacheUtils { + + /** + * 获取CacheManager + * + * @return + */ + public static CacheManager getCacheManager() { + return SpringUtils.getBean(CacheManager.class); + } + + /** + * 根据cacheName从CacheManager中获取cache + * + * @param cacheName + * @return + */ + public static Cache getCache(String cacheName) { + return getCacheManager().getCache(cacheName); + } + + /** + * 获取缓存的所有key值(由于springcache不支持获取所有key,只能根据cache类型来单独获取) + * + * @param cacheName + * @return + */ + public static Set getkeys(String cacheName) { + Cache cache = getCache(cacheName); + CacheKeys cacheGetKets = SpringUtils.getBean(CacheKeys.class); + return cacheGetKets.getCachekeys(cache); + } + + /** + * 根据cacheName,key缓存数据 + * + * @param cacheName + * @param key + * @param value + * @param + */ + public static void put(String cacheName, String key, T value) { + put(cacheName, key, value, 0, null); + } + + /** + * 如果没有则进行缓存,根据cacheName,key缓存数据 + * + * @param cacheName + * @param key + * @param value + * @param + */ + public static void putIfAbsent(String cacheName, String key, T value) { + if (ObjectUtils.isEmpty(get(cacheName, key))) { + put(cacheName, key, value, 0, null); + } + } + + public static boolean hasKey(String cacheName, String key) { + return !ObjectUtils.isEmpty(get(cacheName, key)); + } + + /** + * 根据cacheName,key和缓存过期时间进行缓存数据,使用各种不同缓存可以单独进行操作 + * + * @param cacheName + * @param key + * @param value + * @param timeout + * @param unit + * @param + */ + public static void put(String cacheName, String key, T value, long timeout, TimeUnit unit) { + Cache cache = getCache(cacheName); + if (cache instanceof JCacheCache) { + JCacheCache ehcache = (JCacheCache) cache; + ehcache.put(key, value); + } else if (cache instanceof TransactionAwareCacheDecorator) { + CacheTimeOut cacheTimeOut = SpringUtils.getBean(CacheTimeOut.class); + if (timeout != 0 && unit != null) { + cacheTimeOut.setCacheObject(cacheName, key, value, timeout, unit); + } else { + cacheTimeOut.setCacheObject(cacheName, key, value); + } + } else { + cache.put(key, value); + } + } + + /** + * 获取数据 + * + * @param cacheName + * @param key + * @return + */ + public static Cache.ValueWrapper get(String cacheName, String key) { + return getCache(cacheName).get(key); + } + + /** + * 根据类型获取数据 + * + * @param cacheName + * @param key + * @param type + * @param + * @return + */ + public static T get(String cacheName, String key, @Nullable Class type) { + return getCache(cacheName).get(key, type); + } + + /** + * 移除缓存数据 + * + * @param cacheName + * @param key + */ + public static void remove(String cacheName, String key) { + getCache(cacheName).evict(key); + } + + /** + * 如果存在则移除缓存数据 + * + * @param cacheName + * @param key + * @return + */ + public static boolean removeIfPresent(String cacheName, String key) { + remove(cacheName, key); + return false; + } + + /** + * 清除缓存名称为cacheName的所有缓存数据 + * + * @param cacheName + */ + public static void clear(String cacheName) { + getCache(cacheName).clear(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java new file mode 100644 index 0000000..1cee5ba --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java @@ -0,0 +1,174 @@ +package com.ruoyi.common.utils; + +import java.lang.management.ManagementFactory; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; + +import org.apache.commons.lang3.time.DateFormatUtils; + +/** + * 时间工具类 + * + * @author ruoyi + */ +public class DateUtils extends org.apache.commons.lang3.time.DateUtils { + + public static String YYYY = "yyyy"; + + public static String YYYY_MM = "yyyy-MM"; + + public static String YYYY_MM_DD = "yyyy-MM-dd"; + + public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; + + public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + private static String[] parsePatterns = { + "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", + "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", + "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM" }; + + /** + * 获取当前Date型日期 + * + * @return Date() 当前日期 + */ + public static Date getNowDate() { + return new Date(); + } + + /** + * 获取当前日期, 默认格式为yyyy-MM-dd + * + * @return String + */ + public static String getDate() { + return dateTimeNow(YYYY_MM_DD); + } + + public static final String getTime() { + return dateTimeNow(YYYY_MM_DD_HH_MM_SS); + } + + public static final String dateTimeNow() { + return dateTimeNow(YYYYMMDDHHMMSS); + } + + public static final String dateTimeNow(final String format) { + return parseDateToStr(format, new Date()); + } + + public static final String dateTime(final Date date) { + return parseDateToStr(YYYY_MM_DD, date); + } + + public static final String parseDateToStr(final String format, final Date date) { + return new SimpleDateFormat(format).format(date); + } + + public static final Date dateTime(final String format, final String ts) { + try { + return new SimpleDateFormat(format).parse(ts); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * 日期路径 即年/月/日 如2018/08/08 + */ + public static final String datePath() { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyy/MM/dd"); + } + + /** + * 日期路径 即年/月/日 如20180808 + */ + public static final String dateTime() { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyyMMdd"); + } + + /** + * 日期型字符串转化为日期 格式 + */ + public static Date parseDate(Object str) { + if (str == null) { + return null; + } + try { + return parseDate(str.toString(), parsePatterns); + } catch (ParseException e) { + return null; + } + } + + /** + * 获取服务器启动时间 + */ + public static Date getServerStartDate() { + long time = ManagementFactory.getRuntimeMXBean().getStartTime(); + return new Date(time); + } + + /** + * 计算相差天数 + */ + public static int differentDaysByMillisecond(Date date1, Date date2) { + return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); + } + + /** + * 计算时间差 + * + * @param endTime 最后时间 + * @param startTime 开始时间 + * @return 时间差(天/小时/分钟) + */ + public static String timeDistance(Date endDate, Date startTime) { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + // long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - startTime.getTime(); + // 计算差多少天 + long day = diff / nd; + // 计算差多少小时 + long hour = diff % nd / nh; + // 计算差多少分钟 + long min = diff % nd % nh / nm; + // 计算差多少秒//输出结果 + // long sec = diff % nd % nh % nm / ns; + return day + "天" + hour + "小时" + min + "分钟"; + } + + /** + * 增加 LocalDateTime ==> Date + */ + public static Date toDate(LocalDateTime temporalAccessor) { + ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + /** + * 增加 LocalDate ==> Date + */ + public static Date toDate(LocalDate temporalAccessor) { + LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); + ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + /** @deprecated */ + @Deprecated + public DateUtils() { + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DesensitizedUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DesensitizedUtil.java new file mode 100644 index 0000000..96fd397 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DesensitizedUtil.java @@ -0,0 +1,49 @@ +package com.ruoyi.common.utils; + +/** + * 脱敏工具类 + * + * @author ruoyi + */ +public class DesensitizedUtil +{ + /** + * 密码的全部字符都用*代替,比如:****** + * + * @param password 密码 + * @return 脱敏后的密码 + */ + public static String password(String password) + { + if (StringUtils.isBlank(password)) + { + return StringUtils.EMPTY; + } + return StringUtils.repeat('*', password.length()); + } + + /** + * 车牌中间用*代替,如果是错误的车牌,不处理 + * + * @param carLicense 完整的车牌号 + * @return 脱敏后的车牌 + */ + public static String carLicense(String carLicense) + { + if (StringUtils.isBlank(carLicense)) + { + return StringUtils.EMPTY; + } + // 普通车牌 + if (carLicense.length() == 7) + { + carLicense = StringUtils.hide(carLicense, 3, 6); + } + else if (carLicense.length() == 8) + { + // 新能源车牌 + carLicense = StringUtils.hide(carLicense, 3, 7); + } + return carLicense; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java new file mode 100644 index 0000000..f17939d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java @@ -0,0 +1,204 @@ +package com.ruoyi.common.utils; + +import java.util.List; + +import org.springframework.cache.Cache; + +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.core.domain.entity.SysDictData; + +/** + * 字典工具类 + * + * @author ruoyi + */ +public class DictUtils +{ + /** + * 分隔符 + */ + public static final String SEPARATOR = ","; + + /** + * 设置字典缓存 + * + * @param key 参数键 + * @param dictDatas 字典数据列表 + */ + public static void setDictCache(String key, List dictDatas) + { + getDictCacheKey().put(key, dictDatas); + } + + /** + * 获取字典缓存 + * + * @param key 参数键 + * @return dictDatas 字典数据列表 + */ + @SuppressWarnings("unchecked") + public static List getDictCache(String key) + { + List arrayCache = (List) getDictCacheKey().get(key, List.class); + if (StringUtils.isNotNull(arrayCache)) + { + return arrayCache; + } + return null; + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue) + { + return getDictLabel(dictType, dictValue, SEPARATOR); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel) + { + return getDictValue(dictType, dictLabel, SEPARATOR); + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @param separator 分隔符 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue, String separator) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + if (StringUtils.isNotNull(datas)) + { + if (StringUtils.containsAny(separator, dictValue)) + { + for (SysDictData dict : datas) + { + for (String value : dictValue.split(separator)) + { + if (value.equals(dict.getDictValue())) + { + propertyString.append(dict.getDictLabel()).append(separator); + break; + } + } + } + } + else + { + for (SysDictData dict : datas) + { + if (dictValue.equals(dict.getDictValue())) + { + return dict.getDictLabel(); + } + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @param separator 分隔符 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel, String separator) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas)) + { + for (SysDictData dict : datas) + { + for (String label : dictLabel.split(separator)) + { + if (label.equals(dict.getDictLabel())) + { + propertyString.append(dict.getDictValue()).append(separator); + break; + } + } + } + } + else + { + for (SysDictData dict : datas) + { + if (dictLabel.equals(dict.getDictLabel())) + { + return dict.getDictValue(); + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + + /** + * 根据字典类型获取字典所有标签 + * + * @param dictType 字典类型 + * @return 字典值 + */ + public static String getDictLabels(String dictType) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + if (StringUtils.isNull(datas)) + { + return StringUtils.EMPTY; + } + for (SysDictData dict : datas) + { + propertyString.append(dict.getDictLabel()).append(SEPARATOR); + } + return StringUtils.stripEnd(propertyString.toString(), SEPARATOR); + } + + /** + * 删除指定字典缓存 + * + * @param key 字典键 + */ + public static void removeDictCache(String key) + { + getDictCacheKey().evict(key); + } + + /** + * 清空字典缓存 + */ + public static void clearDictCache() + { + getDictCacheKey().clear(); + } + + /** + * 获取dict缓存 + * + * @return 缓存Cache + */ + public static Cache getDictCacheKey() + { + return CacheUtils.getCache(CacheConstants.SYS_DICT_KEY); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java new file mode 100644 index 0000000..214e4a0 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java @@ -0,0 +1,39 @@ +package com.ruoyi.common.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import org.apache.commons.lang3.exception.ExceptionUtils; + +/** + * 错误信息处理类。 + * + * @author ruoyi + */ +public class ExceptionUtil +{ + /** + * 获取exception的详细错误信息。 + */ + public static String getExceptionMessage(Throwable e) + { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw, true)); + return sw.toString(); + } + + public static String getRootErrorMessage(Exception e) + { + Throwable root = ExceptionUtils.getRootCause(e); + root = (root == null ? e : root); + if (root == null) + { + return ""; + } + String msg = root.getMessage(); + if (msg == null) + { + return "null"; + } + return StringUtils.defaultString(msg); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java new file mode 100644 index 0000000..0de30c6 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.utils; + +/** + * 处理并记录日志文件 + * + * @author ruoyi + */ +public class LogUtils +{ + public static String getBlock(Object msg) + { + if (msg == null) + { + msg = ""; + } + return "[" + msg.toString() + "]"; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java new file mode 100644 index 0000000..7dac75a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java @@ -0,0 +1,26 @@ +package com.ruoyi.common.utils; + +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import com.ruoyi.common.utils.spring.SpringUtils; + +/** + * 获取i18n资源文件 + * + * @author ruoyi + */ +public class MessageUtils +{ + /** + * 根据消息键和参数 获取消息 委托给spring messageSource + * + * @param code 消息键 + * @param args 参数 + * @return 获取国际化翻译值 + */ + public static String message(String code, Object... args) + { + MessageSource messageSource = SpringUtils.getBean(MessageSource.class); + return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/MybatisUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MybatisUtils.java new file mode 100644 index 0000000..e5de3d2 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MybatisUtils.java @@ -0,0 +1,77 @@ +package com.ruoyi.common.utils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.util.ClassUtils; + +public class MybatisUtils { + static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; + + public static String setTypeAliasesPackage(String typeAliasesPackage) { + ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver(); + MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver); + List allResult = new ArrayList(); + try { + for (String aliasesPackage : typeAliasesPackage.split(",")) { + List result = new ArrayList(); + aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + + DEFAULT_RESOURCE_PATTERN; + Resource[] resources = resolver.getResources(aliasesPackage); + if (resources != null && resources.length > 0) { + MetadataReader metadataReader = null; + for (Resource resource : resources) { + if (resource.isReadable()) { + metadataReader = metadataReaderFactory.getMetadataReader(resource); + try { + result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage() + .getName()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + } + if (result.size() > 0) { + HashSet hashResult = new HashSet(result); + allResult.addAll(hashResult); + } + } + if (allResult.size() > 0) { + typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0])); + } else { + throw new RuntimeException( + "mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包"); + } + } catch (IOException e) { + e.printStackTrace(); + } + return typeAliasesPackage; + } + + public static Resource[] resolveMapperLocations(String[] mapperLocations) { + ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); + List resources = new ArrayList(); + if (mapperLocations != null) { + for (String mapperLocation : mapperLocations) { + try { + Resource[] mappers = resourceResolver.getResources(mapperLocation); + resources.addAll(Arrays.asList(mappers)); + } catch (IOException e) { + // ignore + } + } + } + return resources.toArray(new Resource[resources.size()]); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java new file mode 100644 index 0000000..391f760 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java @@ -0,0 +1,121 @@ +package com.ruoyi.common.utils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.core.page.PageDomain; +import com.ruoyi.common.core.page.TableSupport; +import com.ruoyi.common.utils.sql.SqlUtil; + +import jakarta.annotation.PostConstruct; + +/** + * 分页工具类 + * + * @author ruoyi + */ +@Component +public class PageUtils { + // 开关:通过系统属性 ruoyi.paging 控制,custom=自研,helper=PageHelper。默认 custom + private static boolean USE_CUSTOM = false; + + // Provider 选择:根据 USE_CUSTOM 决定使用 PageContextHolder 或 PageHelper + private static Method START_2; + private static Method START_3; + private static Method ORDER_BY; + private static Method CLEAR; + private static Method SET_REASONABLE; + private static Method PI_GET_TOTAL; + private static Constructor PI_CTOR; + + @Value("${pageutils.type:default}") + private String pageUtilsType; + + @PostConstruct + private void init() throws Exception { + Class PROVIDER_CLS = null; + if ("custom".equals(pageUtilsType)) { + PROVIDER_CLS = Class.forName("com.ruoyi.mybatisinterceptor.context.page.PageContextHolder"); + SET_REASONABLE = PROVIDER_CLS.getMethod("setReasonable", Boolean.class); + Class PageInfoClz = Class.forName("com.ruoyi.mybatisinterceptor.context.page.model.TableInfo"); + PI_GET_TOTAL = PageInfoClz.getMethod("getTotal"); + USE_CUSTOM = true; + } else { + PROVIDER_CLS = Class.forName("com.github.pagehelper.PageHelper"); + Class PageClz = Class.forName("com.github.pagehelper.Page"); + SET_REASONABLE = PageClz.getMethod("setReasonable", Boolean.class); + Class PageInfoClz = Class.forName("com.github.pagehelper.PageInfo"); + PI_GET_TOTAL = PageInfoClz.getMethod("getTotal"); + PI_CTOR = PageInfoClz.getConstructor(java.util.List.class); + USE_CUSTOM = false; + } + START_2 = PROVIDER_CLS.getMethod("startPage", int.class, int.class); + START_3 = PROVIDER_CLS.getMethod("startPage", int.class, int.class, String.class); + ORDER_BY = PROVIDER_CLS.getMethod("orderBy", String.class); + CLEAR = PROVIDER_CLS.getMethod("clearPage"); + } + + /** + * 设置请求分页数据 + */ + public static void startPage() { + PageDomain pageDomain = TableSupport.buildPageRequest(); + Integer pageNum = pageDomain.getPageNum(); + Integer pageSize = pageDomain.getPageSize(); + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + Boolean reasonable = pageDomain.getReasonable(); + Object ret = invoke(START_3, null, pageNum, pageSize, orderBy); + invoke(SET_REASONABLE, USE_CUSTOM ? null : ret, reasonable); + } + + /** + * 清理分页的线程变量 + */ + public static void clearPage() { + invoke(CLEAR, null); + } + + /** + * 显式页码与页大小 + */ + public static void startPage(Integer pageNum, Integer pageSize) { + invoke(START_2, null, pageNum, pageSize); + } + + /** + * 设置排序 + */ + public static void orderBy(String orderBy) { + String safeOrderBy = SqlUtil.escapeOrderBySql(orderBy); + invoke(ORDER_BY, null, safeOrderBy); + } + + public static long getTotal(java.util.List list) { + if (list == null) + return 0L; + try { + if (USE_CUSTOM) { + Object total = PI_GET_TOTAL.invoke(list); + return ((Number) total).longValue(); + } else { + Object pageInfo = PI_CTOR.newInstance(list); + Object total = PI_GET_TOTAL.invoke(pageInfo); + return ((Number) total).longValue(); + } + } catch (Throwable e) { + throw new IllegalStateException("Failed to obtain total count from PageInfo", e); + } + } + + private static Object invoke(Method m, Object target, Object... args) { + try { + return m == null ? null : m.invoke(target, args); + } catch (Throwable e) { + return null; + } + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java new file mode 100644 index 0000000..0d3ac5f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java @@ -0,0 +1,178 @@ +package com.ruoyi.common.utils; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.util.PatternMatchUtils; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.exception.ServiceException; + +/** + * 安全服务工具类 + * + * @author ruoyi + */ +public class SecurityUtils +{ + + /** + * 用户ID + **/ + public static Long getUserId() + { + try + { + return getLoginUser().getUserId(); + } + catch (Exception e) + { + throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取部门ID + **/ + public static Long getDeptId() + { + try + { + return getLoginUser().getDeptId(); + } + catch (Exception e) + { + throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户账户 + **/ + public static String getUsername() + { + try + { + return getLoginUser().getUsername(); + } + catch (Exception e) + { + throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户 + **/ + public static LoginUser getLoginUser() + { + try + { + return (LoginUser) getAuthentication().getPrincipal(); + } + catch (Exception e) + { + throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取Authentication + */ + public static Authentication getAuthentication() + { + return SecurityContextHolder.getContext().getAuthentication(); + } + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * @return 加密字符串 + */ + public static String encryptPassword(String password) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * @return 结果 + */ + public static boolean matchesPassword(String rawPassword, String encodedPassword) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 是否为管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public static boolean hasPermi(String permission) + { + return hasPermi(getLoginUser().getPermissions(), permission); + } + + /** + * 判断是否包含权限 + * + * @param authorities 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public static boolean hasPermi(Collection authorities, String permission) + { + return authorities.stream().filter(StringUtils::hasText) + .anyMatch(x -> Constants.ALL_PERMISSION.equals(x) || PatternMatchUtils.simpleMatch(x, permission)); + } + + /** + * 验证用户是否拥有某个角色 + * + * @param role 角色标识 + * @return 用户是否具备某角色 + */ + public static boolean hasRole(String role) + { + List roleList = getLoginUser().getUser().getRoles(); + Collection roles = roleList.stream().map(SysRole::getRoleKey).collect(Collectors.toSet()); + return hasRole(roles, role); + } + + /** + * 判断是否包含角色 + * + * @param roles 角色列表 + * @param role 角色 + * @return 用户是否具备某角色权限 + */ + public static boolean hasRole(Collection roles, String role) + { + return roles.stream().filter(StringUtils::hasText) + .anyMatch(x -> Constants.SUPER_ADMIN.equals(x) || PatternMatchUtils.simpleMatch(x, role)); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java new file mode 100644 index 0000000..5635db7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java @@ -0,0 +1,218 @@ +package com.ruoyi.common.utils; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.text.Convert; + +/** + * 客户端工具类 + * + * @author ruoyi + */ +public class ServletUtils +{ + /** + * 获取String参数 + */ + public static String getParameter(String name) + { + return getRequest().getParameter(name); + } + + /** + * 获取String参数 + */ + public static String getParameter(String name, String defaultValue) + { + return Convert.toStr(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name) + { + return Convert.toInt(getRequest().getParameter(name)); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name, Integer defaultValue) + { + return Convert.toInt(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name) + { + return Convert.toBool(getRequest().getParameter(name)); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name, Boolean defaultValue) + { + return Convert.toBool(getRequest().getParameter(name), defaultValue); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParams(ServletRequest request) + { + final Map map = request.getParameterMap(); + return Collections.unmodifiableMap(map); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParamMap(ServletRequest request) + { + Map params = new HashMap<>(); + for (Map.Entry entry : getParams(request).entrySet()) + { + params.put(entry.getKey(), StringUtils.join(entry.getValue(), ",")); + } + return params; + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() + { + return getRequestAttributes().getRequest(); + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() + { + return getRequestAttributes().getResponse(); + } + + /** + * 获取session + */ + public static HttpSession getSession() + { + return getRequest().getSession(); + } + + public static ServletRequestAttributes getRequestAttributes() + { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + */ + public static void renderString(HttpServletResponse response, String string) + { + try + { + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(string); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + public static boolean isAjaxRequest(HttpServletRequest request) + { + String accept = request.getHeader("accept"); + if (accept != null && accept.contains("application/json")) + { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) + { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) + { + return true; + } + + String ajax = request.getParameter("__ajax"); + return StringUtils.inStringIgnoreCase(ajax, "json", "xml"); + } + + /** + * 内容编码 + * + * @param str 内容 + * @return 编码后的内容 + */ + public static String urlEncode(String str) + { + try + { + return URLEncoder.encode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } + + /** + * 内容解码 + * + * @param str 内容 + * @return 解码后的内容 + */ + public static String urlDecode(String str) + { + try + { + return URLDecoder.decode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java new file mode 100644 index 0000000..d4dcd9e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java @@ -0,0 +1,599 @@ +package com.ruoyi.common.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.util.AntPathMatcher; + +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.text.StrFormatter; + +/** + * 字符串工具类 + * + * @author ruoyi + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils { + + /** 空字符串 */ + private static final String NULLSTR = ""; + + /** 下划线 */ + private static final char SEPARATOR = '_'; + + /** 星号 */ + private static final char ASTERISK = '*'; + + /** + * 获取参数不为空值 + * + * @param value defaultValue 要判断的value + * @return value 返回值 + */ + public static T nvl(T value, T defaultValue) { + return value != null ? value : defaultValue; + } + + /** + * * 判断一个Collection是否为空, 包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Collection coll) { + return isNull(coll) || coll.isEmpty(); + } + + /** + * * 判断一个Collection是否非空,包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Collection coll) { + return !isEmpty(coll); + } + + /** + * * 判断一个对象数组是否为空 + * + * @param objects 要判断的对象数组 + ** @return true:为空 false:非空 + */ + public static boolean isEmpty(Object[] objects) { + return isNull(objects) || (objects.length == 0); + } + + /** + * * 判断一个对象数组是否非空 + * + * @param objects 要判断的对象数组 + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Object[] objects) { + return !isEmpty(objects); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Map map) { + return isNull(map) || map.isEmpty(); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Map map) { + return !isEmpty(map); + } + + /** + * * 判断一个字符串是否为空串 + * + * @param str String + * @return true:为空 false:非空 + */ + public static boolean isEmpty(String str) { + return isNull(str) || NULLSTR.equals(str.trim()); + } + + /** + * * 判断一个字符串是否为非空串 + * + * @param str String + * @return true:非空串 false:空串 + */ + public static boolean isNotEmpty(String str) { + return !isEmpty(str); + } + + /** + * * 判断一个对象是否为空 + * + * @param object Object + * @return true:为空 false:非空 + */ + public static boolean isNull(Object object) { + return object == null; + } + + /** + * * 判断一个对象是否非空 + * + * @param object Object + * @return true:非空 false:空 + */ + public static boolean isNotNull(Object object) { + return !isNull(object); + } + + /** + * * 判断一个对象是否是数组类型(Java基本型别的数组) + * + * @param object 对象 + * @return true:是数组 false:不是数组 + */ + public static boolean isArray(Object object) { + return isNotNull(object) && object.getClass().isArray(); + } + + /** + * 去空格 + */ + public static String trim(String str) { + return (str == null ? "" : str.trim()); + } + + /** + * 替换指定字符串的指定区间内字符为"*" + * + * @param str 字符串 + * @param startInclude 开始位置(包含) + * @param endExclude 结束位置(不包含) + * @return 替换后的字符串 + */ + public static String hide(CharSequence str, int startInclude, int endExclude) { + if (isEmpty(str)) { + return NULLSTR; + } + final int strLength = str.length(); + if (startInclude > strLength) { + return NULLSTR; + } + if (endExclude > strLength) { + endExclude = strLength; + } + if (startInclude > endExclude) { + // 如果起始位置大于结束位置,不替换 + return NULLSTR; + } + final char[] chars = new char[strLength]; + for (int i = 0; i < strLength; i++) { + if (i >= startInclude && i < endExclude) { + chars[i] = ASTERISK; + } else { + chars[i] = str.charAt(i); + } + } + return new String(chars); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @return 结果 + */ + public static String substring(final String str, int start) { + if (str == null) { + return NULLSTR; + } + + if (start < 0) { + start = str.length() + start; + } + + if (start < 0) { + start = 0; + } + if (start > str.length()) { + return NULLSTR; + } + + return str.substring(start); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @param end 结束 + * @return 结果 + */ + public static String substring(final String str, int start, int end) { + if (str == null) { + return NULLSTR; + } + + if (end < 0) { + end = str.length() + end; + } + if (start < 0) { + start = str.length() + start; + } + + if (end > str.length()) { + end = str.length(); + } + + if (start > end) { + return NULLSTR; + } + + if (start < 0) { + start = 0; + } + if (end < 0) { + end = 0; + } + + return str.substring(start, end); + } + + /** + * 判断是否为空,并且不是空白字符 + * + * @param str 要判断的value + * @return 结果 + */ + public static boolean hasText(String str) { + return (str != null && !str.isEmpty() && containsText(str)); + } + + private static boolean containsText(CharSequence str) { + int strLen = str.length(); + for (int i = 0; i < strLen; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return true; + } + } + return false; + } + + /** + * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param params 参数值 + * @return 格式化后的文本 + */ + public static String format(String template, Object... params) { + if (isEmpty(params) || isEmpty(template)) { + return template; + } + return StrFormatter.format(template, params); + } + + /** + * 是否为http(s)://开头 + * + * @param link 链接 + * @return 结果 + */ + public static boolean ishttp(String link) { + return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); + } + + /** + * 字符串转set + * + * @param str 字符串 + * @param sep 分隔符 + * @return set集合 + */ + public static final Set str2Set(String str, String sep) { + return new HashSet(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @return list集合 + */ + public static final List str2List(String str, String sep) { + return str2List(str, sep, true, false); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) { + List list = new ArrayList(); + if (StringUtils.isEmpty(str)) { + return list; + } + + // 过滤空白字符串 + if (filterBlank && StringUtils.isBlank(str)) { + return list; + } + String[] split = str.split(sep); + for (String string : split) { + if (filterBlank && StringUtils.isBlank(string)) { + continue; + } + if (trim) { + string = string.trim(); + } + list.add(string); + } + + return list; + } + + /** + * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value + * + * @param collection 给定的集合 + * @param array 给定的数组 + * @return boolean 结果 + */ + public static boolean containsAny(Collection collection, String... array) { + if (isEmpty(collection) || isEmpty(array)) { + return false; + } else { + for (String str : array) { + if (collection.contains(str)) { + return true; + } + } + return false; + } + } + + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + * + * @param cs 指定字符串 + * @param searchCharSequences 需要检查的字符串数组 + * @return 是否包含任意一个字符串 + */ + public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) { + if (isEmpty(cs) || isEmpty(searchCharSequences)) { + return false; + } + for (CharSequence testStr : searchCharSequences) { + if (containsIgnoreCase(cs, testStr)) { + return true; + } + } + return false; + } + + /** + * 驼峰转下划线命名 + */ + public static String toUnderScoreCase(String str) { + if (str == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + // 前置字符是否大写 + boolean preCharIsUpperCase = true; + // 当前字符是否大写 + boolean curreCharIsUpperCase = true; + // 下一字符是否大写 + boolean nexteCharIsUpperCase = true; + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (i > 0) { + preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); + } else { + preCharIsUpperCase = false; + } + + curreCharIsUpperCase = Character.isUpperCase(c); + + if (i < (str.length() - 1)) { + nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); + } + + if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) { + sb.append(SEPARATOR); + } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) { + sb.append(SEPARATOR); + } + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 是否包含字符串 + * + * @param str 验证字符串 + * @param strs 字符串组 + * @return 包含返回true + */ + public static boolean inStringIgnoreCase(String str, String... strs) { + if (str != null && strs != null) { + for (String s : strs) { + if (str.equalsIgnoreCase(trim(s))) { + return true; + } + } + } + return false; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 + * 例如:HELLO_WORLD->HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) { + StringBuilder result = new StringBuilder(); + // 快速检查 + if (name == null || name.isEmpty()) { + // 没必要转换 + return ""; + } else if (!name.contains("_")) { + // 不含下划线,仅将首字母大写 + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + // 用下划线将原始字符串分割 + String[] camels = name.split("_"); + for (String camel : camels) { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) { + continue; + } + // 首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + return result.toString(); + } + + /** + * 驼峰式命名法 + * 例如:user_name->userName + */ + public static String toCamelCase(String s) { + if (s == null) { + return null; + } + if (s.indexOf(SEPARATOR) == -1) { + return s; + } + s = s.toLowerCase(); + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + if (c == SEPARATOR) { + upperCase = true; + } else if (upperCase) { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } else { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 + * + * @param str 指定字符串 + * @param strs 需要检查的字符串数组 + * @return 是否匹配 + */ + public static boolean matches(String str, List strs) { + if (isEmpty(str) || isEmpty(strs)) { + return false; + } + for (String pattern : strs) { + if (isMatch(pattern, str)) { + return true; + } + } + return false; + } + + /** + * 判断url是否与规则配置: + * ? 表示单个字符; + * * 表示一层路径内的任意字符串,不可跨层级; + * ** 表示任意层路径; + * + * @param pattern 匹配规则 + * @param url 需要匹配的url + * @return + */ + public static boolean isMatch(String pattern, String url) { + AntPathMatcher matcher = new AntPathMatcher(); + return matcher.match(pattern, url); + } + + @SuppressWarnings("unchecked") + public static T cast(Object obj) { + return (T) obj; + } + + /** + * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 + * + * @param num 数字对象 + * @param size 字符串指定长度 + * @return 返回数字的字符串格式,该字符串为指定长度。 + */ + public static final String padl(final Number num, final int size) { + return padl(num.toString(), size, '0'); + } + + /** + * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 + * + * @param s 原始字符串 + * @param size 字符串指定长度 + * @param c 用于补齐的字符 + * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 + */ + public static final String padl(final String s, final int size, final char c) { + final StringBuilder sb = new StringBuilder(size); + if (s != null) { + final int len = s.length(); + if (s.length() <= size) { + for (int i = size - len; i > 0; i--) { + sb.append(c); + } + sb.append(s); + } else { + return s.substring(len - size, len); + } + } else { + for (int i = size; i > 0; i--) { + sb.append(c); + } + } + return sb.toString(); + } + + /** @deprecated */ + @Deprecated + public StringUtils() { + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java new file mode 100644 index 0000000..71fe6d5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java @@ -0,0 +1,99 @@ +package com.ruoyi.common.utils; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 线程相关工具类. + * + * @author ruoyi + */ +public class Threads +{ + private static final Logger logger = LoggerFactory.getLogger(Threads.class); + + /** + * sleep等待,单位为毫秒 + */ + public static void sleep(long milliseconds) + { + try + { + Thread.sleep(milliseconds); + } + catch (InterruptedException e) + { + return; + } + } + + /** + * 停止线程池 + * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. + * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. + * 如果仍然超時,則強制退出. + * 另对在shutdown时线程本身被调用中断做了处理. + */ + public static void shutdownAndAwaitTermination(ExecutorService pool) + { + if (pool != null && !pool.isShutdown()) + { + pool.shutdown(); + try + { + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) + { + pool.shutdownNow(); + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) + { + logger.info("Pool did not terminate"); + } + } + } + catch (InterruptedException ie) + { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + + /** + * 打印线程异常信息 + */ + public static void printException(Runnable r, Throwable t) + { + if (t == null && r instanceof Future) + { + try + { + Future future = (Future) r; + if (future.isDone()) + { + future.get(); + } + } + catch (CancellationException ce) + { + t = ce; + } + catch (ExecutionException ee) + { + t = ee.getCause(); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + } + } + if (t != null) + { + logger.error(t.getMessage(), t); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java new file mode 100644 index 0000000..4463662 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java @@ -0,0 +1,110 @@ +package com.ruoyi.common.utils.bean; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Bean 工具类 + * + * @author ruoyi + */ +public class BeanUtils extends org.springframework.beans.BeanUtils +{ + /** Bean方法名中属性名开始的下标 */ + private static final int BEAN_METHOD_PROP_INDEX = 3; + + /** * 匹配getter方法的正则表达式 */ + private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)"); + + /** * 匹配setter方法的正则表达式 */ + private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)"); + + /** + * Bean属性复制工具方法。 + * + * @param dest 目标对象 + * @param src 源对象 + */ + public static void copyBeanProp(Object dest, Object src) + { + try + { + copyProperties(src, dest); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * 获取对象的setter方法。 + * + * @param obj 对象 + * @return 对象的setter方法列表 + */ + public static List getSetterMethods(Object obj) + { + // setter方法列表 + List setterMethods = new ArrayList(); + + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + + // 查找setter方法 + + for (Method method : methods) + { + Matcher m = SET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 1)) + { + setterMethods.add(method); + } + } + // 返回setter方法列表 + return setterMethods; + } + + /** + * 获取对象的getter方法。 + * + * @param obj 对象 + * @return 对象的getter方法列表 + */ + + public static List getGetterMethods(Object obj) + { + // getter方法列表 + List getterMethods = new ArrayList(); + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + // 查找getter方法 + for (Method method : methods) + { + Matcher m = GET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 0)) + { + getterMethods.add(method); + } + } + // 返回getter方法列表 + return getterMethods; + } + + /** + * 检查Bean方法名中的属性名是否相等。
+ * 如getName()和setName()属性名一样,getName()和setAge()属性名不一样。 + * + * @param m1 方法名1 + * @param m2 方法名2 + * @return 属性名一样返回true,否则返回false + */ + + public static boolean isMethodPropEquals(String m1, String m2) + { + return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX)); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java new file mode 100644 index 0000000..898aca5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.utils.bean; + +import java.util.Set; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validator; + +/** + * bean对象属性验证 + * + * @author ruoyi + */ +public class BeanValidators +{ + public static void validateWithException(Validator validator, Object object, Class... groups) + throws ConstraintViolationException + { + Set> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) + { + throw new ConstraintViolationException(constraintViolations); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java new file mode 100644 index 0000000..3e77f6d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java @@ -0,0 +1,77 @@ +package com.ruoyi.common.utils.file; + +import org.apache.commons.lang3.StringUtils; + +import java.io.File; + +/** + * 文件类型工具类 + * + * @author ruoyi + */ +public class FileTypeUtils +{ + /** + * 获取文件类型 + *

+ * 例如: ruoyi.txt, 返回: txt + * + * @param file 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(File file) + { + if (null == file) + { + return StringUtils.EMPTY; + } + return getFileType(file.getName()); + } + + /** + * 获取文件类型 + *

+ * 例如: ruoyi.txt, 返回: txt + * + * @param fileName 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(String fileName) + { + int separatorIndex = fileName.lastIndexOf("."); + if (separatorIndex < 0) + { + return ""; + } + return fileName.substring(separatorIndex + 1).toLowerCase(); + } + + /** + * 获取文件类型 + * + * @param photoByte 文件字节码 + * @return 后缀(不含".") + */ + public static String getFileExtendName(byte[] photoByte) + { + String strFileExtendName = "JPG"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) + { + strFileExtendName = "GIF"; + } + else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) + { + strFileExtendName = "JPG"; + } + else if ((photoByte[0] == 66) && (photoByte[1] == 77)) + { + strFileExtendName = "BMP"; + } + else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) + { + strFileExtendName = "PNG"; + } + return strFileExtendName; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java new file mode 100644 index 0000000..23f368a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java @@ -0,0 +1,405 @@ +package com.ruoyi.common.utils.file; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.web.multipart.MultipartFile; + +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.exception.file.FileSizeLimitExceededException; +import com.ruoyi.common.exception.file.InvalidExtensionException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.common.utils.uuid.IdUtils; +import com.ruoyi.common.utils.uuid.Seq; +import com.ruoyi.common.utils.uuid.UUID; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 文件处理工具类 + * + * @author ruoyi + */ +public class FileUtils { + public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; + + /** + * 默认的文件名最大长度 100 + */ + public static final int DEFAULT_FILE_NAME_LENGTH = 100; + public static final long DEFAULT_MAX_SIZE = Long.valueOf(SpringUtils.getRequiredProperty("ruoyi.fileMaxSize")) + * 1024 * 1024; + + /** + * 输出指定文件的byte数组 + * + * @param os 输出流 + * @return + */ + public static void writeBytes(InputStream inputStream, OutputStream os) throws IOException { + + try { + + byte[] b = new byte[1024]; + int length; + while ((length = inputStream.read(b)) > 0) { + os.write(b, 0, length); + } + } catch (IOException e) { + throw e; + } finally { + IOUtils.close(os); + IOUtils.close(inputStream); + } + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeImportBytes(byte[] data) throws IOException { + return writeBytes(data, RuoYiConfig.getImportPath()); + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @param uploadDir 目标文件 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeBytes(byte[] data, String uploadDir) throws IOException { + FileOutputStream fos = null; + String pathName = ""; + try { + String extension = getFileExtendName(data); + pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; + File file = FileUtils.getAbsoluteFile(uploadDir, pathName); + fos = new FileOutputStream(file); + fos.write(data); + } finally { + IOUtils.close(fos); + } + return FileUtils.getPathFileName(uploadDir, pathName); + } + + /** + * 删除文件 + * + * @param filePath 文件 + * @return + */ + public static boolean deleteFile(String filePath) { + boolean flag = false; + File file = new File(filePath); + // 路径为文件且不为空则进行删除 + if (file.isFile() && file.exists()) { + flag = file.delete(); + } + return flag; + } + + /** + * 文件名称验证 + * + * @param filename 文件名称 + * @return true 正常 false 非法 + */ + public static boolean isValidFilename(String filename) { + return filename.matches(FILENAME_PATTERN); + } + + /** + * 检查文件是否可下载 + * + * @param resource 需要下载的文件 + * @return true 正常 false 非法 + */ + public static boolean checkAllowDownload(String resource) { + // 禁止目录上跳级别 + if (StringUtils.contains(resource, "..")) { + return false; + } + + // 检查允许下载的文件规则 + if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) { + return true; + } + + // 不在允许下载的文件规则 + return false; + } + + /** + * 下载文件名重新编码 + * + * @param request 请求对象 + * @param fileName 文件名 + * @return 编码后的文件名 + */ + public static String setFileDownloadHeader(HttpServletRequest request, String fileName) + throws UnsupportedEncodingException { + final String agent = request.getHeader("USER-AGENT"); + String filename = fileName; + if (agent.contains("MSIE")) { + // IE浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + filename = filename.replace("+", " "); + } else if (agent.contains("Firefox")) { + // 火狐浏览器 + filename = new String(fileName.getBytes(), "ISO8859-1"); + } else if (agent.contains("Chrome")) { + // google浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } else { + // 其它浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + return filename; + } + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) + throws UnsupportedEncodingException { + String percentEncodedFileName = percentEncode(realFileName); + + StringBuilder contentDispositionValue = new StringBuilder(); + contentDispositionValue.append("attachment; filename=") + .append(percentEncodedFileName) + .append(";") + .append("filename*=") + .append("utf-8''") + .append(percentEncodedFileName); + + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); + response.setHeader("Content-disposition", contentDispositionValue.toString()); + response.setHeader("download-filename", percentEncodedFileName); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public static String percentEncode(String s) throws UnsupportedEncodingException { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); + return encode.replaceAll("\\+", "%20"); + } + + /** + * 获取图像后缀 + * + * @param photoByte 图像数据 + * @return 后缀名 + */ + public static String getFileExtendName(byte[] photoByte) { + String strFileExtendName = "jpg"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) { + strFileExtendName = "gif"; + } else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) { + strFileExtendName = "jpg"; + } else if ((photoByte[0] == 66) && (photoByte[1] == 77)) { + strFileExtendName = "bmp"; + } else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) { + strFileExtendName = "png"; + } + return strFileExtendName; + } + + /** + * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png + * + * @param fileName 路径名称 + * @return 没有文件路径的名称 + */ + public static String getName(String fileName) { + if (fileName == null) { + return null; + } + int lastUnixPos = fileName.lastIndexOf('/'); + int lastWindowsPos = fileName.lastIndexOf('\\'); + int index = Math.max(lastUnixPos, lastWindowsPos); + return fileName.substring(index + 1); + } + + /** + * 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi + * + * @param fileName 路径名称 + * @return 没有文件路径和后缀的名称 + */ + public static String getNameNotSuffix(String fileName) { + if (fileName == null) { + return null; + } + String baseName = FilenameUtils.getBaseName(fileName); + return baseName; + } + + /** + * 编码文件名 + */ + public static final String extractFilename(MultipartFile file) { + return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), + FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), + getExtension(file)); + } + + public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException { + File desc = new File(uploadDir + File.separator + fileName); + + if (!desc.exists()) { + if (!desc.getParentFile().exists()) { + desc.getParentFile().mkdirs(); + } + } + return desc; + } + + public static final File getAbsoluteFile(String filePath) throws IOException { + File desc = new File(filePath); + if (!desc.exists()) { + if (!desc.getParentFile().exists()) { + desc.getParentFile().mkdirs(); + } + } + return desc; + } + + public static final String getPathFileName(String uploadDir, String fileName) throws IOException { + int dirLastIndex = RuoYiConfig.getProfile().length() + 1; + String currentDir = StringUtils.substring(uploadDir, dirLastIndex); + StringBuilder sb = new StringBuilder(); + sb.append(Constants.RESOURCE_PREFIX) + .append("/").append(currentDir) + .append("/").append(fileName); + return sb.toString().replace("\\", "/"); + } + + /** + * 文件大小校验 + * + * @param file 上传的文件 + * @return + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws InvalidExtensionException + */ + public static final void assertAllowed(MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, InvalidExtensionException { + long size = file.getSize(); + if (size > DEFAULT_MAX_SIZE) { + throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); + } + + String fileName = file.getOriginalFilename(); + String extension = getExtension(file); + if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) { + if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) { + throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, + fileName); + } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) { + throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, + fileName); + } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) { + throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, + fileName); + } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) { + throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, + fileName); + } else { + throw new InvalidExtensionException(allowedExtension, extension, fileName); + } + } + } + + /** + * 判断MIME类型是否是允许的MIME类型 + * + * @param extension + * @param allowedExtension + * @return + */ + public static final boolean isAllowedExtension(String extension, String[] allowedExtension) { + for (String str : allowedExtension) { + if (str.equalsIgnoreCase(extension)) { + return true; + } + } + return false; + } + + /** + * 获取文件名的后缀 + * + * @param file 表单文件 + * @return 后缀名 + */ + public static final String getExtension(MultipartFile file) { + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + if (StringUtils.isEmpty(extension)) { + extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); + } + return extension; + } + + /** + * 获取相对路径 + * + * @param filePath 文件路径,可以是绝对路径或相对路径 + * @return 相对路径,将反斜杠替换为斜杠,如果输入是绝对路径则去除根路径部分。 + */ + public static final String getRelativePath(String filePath) { + Path absolute = Paths.get(filePath); + if (!absolute.isAbsolute()) { + if (filePath.startsWith("/") || filePath.startsWith("\\")) { + return filePath.replace("\\", "/").substring(1); + } else { + return filePath.replace("\\", "/"); + } + } + Path root = absolute.getRoot(); + Path normalize = absolute.normalize(); + String relativePath = normalize.subpath(root.getNameCount(), normalize.getNameCount()).toString(); + return relativePath.replace("\\", "/"); + } + + public static final boolean isAbsolutePath(String path) { + Path filePath = Paths.get(path); + return filePath.isAbsolute(); + } + + public static final String fastFilePath(MultipartFile file) { + return new StringBuilder(DateUtils.datePath()) + .append(File.separatorChar).append(DateUtils.dateTimeNow()) + .append("_").append(UUID.fastUUID().toString().substring(0, 4)) + .append(".").append(FileUtils.getExtension(file)).toString(); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java new file mode 100644 index 0000000..7406463 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java @@ -0,0 +1,101 @@ +package com.ruoyi.common.utils.file; + +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.StringUtils; +import org.apache.poi.util.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; + +/** + * 图片处理工具类 + * + * @author ruoyi + */ +public class ImageUtils +{ + private static final Logger log = LoggerFactory.getLogger(ImageUtils.class); + + public static byte[] getImage(String imagePath) + { + InputStream is = getFile(imagePath); + try + { + return IOUtils.toByteArray(is); + } + catch (Exception e) + { + log.error("图片加载异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(is); + } + } + + public static InputStream getFile(String imagePath) + { + try + { + byte[] result = readFile(imagePath); + result = Arrays.copyOf(result, result.length); + return new ByteArrayInputStream(result); + } + catch (Exception e) + { + log.error("获取图片异常 {}", e); + } + return null; + } + + /** + * 读取文件为字节数据 + * + * @param url 地址 + * @return 字节数据 + */ + public static byte[] readFile(String url) + { + InputStream in = null; + try + { + if (url.startsWith("http")) + { + // 网络地址 + URI uriObj = new URI(url); + URL urlObj = uriObj.toURL(); + URLConnection urlConnection = urlObj.openConnection(); + urlConnection.setConnectTimeout(30 * 1000); + urlConnection.setReadTimeout(60 * 1000); + urlConnection.setDoInput(true); + in = urlConnection.getInputStream(); + } + else + { + // 本机地址 + String localPath = RuoYiConfig.getProfile(); + String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX); + in = new FileInputStream(downloadPath); + } + return IOUtils.toByteArray(in); + } + catch (Exception e) + { + log.error("获取文件路径异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(in); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java new file mode 100644 index 0000000..255ed68 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java @@ -0,0 +1,61 @@ +package com.ruoyi.common.utils.file; + +/** + * 媒体类型工具类 + * + * @author ruoyi + */ +public class MimeTypeUtils +{ + public static final String IMAGE_PNG = "image/png"; + + public static final String IMAGE_JPG = "image/jpg"; + + public static final String IMAGE_JPEG = "image/jpeg"; + + public static final String IMAGE_BMP = "image/bmp"; + + public static final String IMAGE_GIF = "image/gif"; + + public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" }; + + public static final String[] FLASH_EXTENSION = { "swf", "flv" }; + + public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", + "asf", "rm", "rmvb" }; + + public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" }; + + public static final String[] DEFAULT_ALLOWED_EXTENSION = { + // 图片 + "bmp", "gif", "jpg", "jpeg", "png", + // word excel powerpoint + "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", + // 压缩文件 + "rar", "zip", "gz", "bz2", + // 音频格式 + "mp3", "wav", + // 视频格式 + "mp4", "avi", "rmvb", + // pdf + "pdf" }; + + public static String getExtension(String prefix) + { + switch (prefix) + { + case IMAGE_PNG: + return "png"; + case IMAGE_JPG: + return "jpg"; + case IMAGE_JPEG: + return "jpeg"; + case IMAGE_BMP: + return "bmp"; + case IMAGE_GIF: + return "gif"; + default: + return ""; + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java new file mode 100644 index 0000000..f52e83e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java @@ -0,0 +1,167 @@ +package com.ruoyi.common.utils.html; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 转义和反转义工具类 + * + * @author ruoyi + */ +public class EscapeUtil +{ + public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; + + private static final char[][] TEXT = new char[64][]; + + static + { + for (int i = 0; i < 64; i++) + { + TEXT[i] = new char[] { (char) i }; + } + + // special HTML characters + TEXT['\''] = "'".toCharArray(); // 单引号 + TEXT['"'] = """.toCharArray(); // 双引号 + TEXT['&'] = "&".toCharArray(); // &符 + TEXT['<'] = "<".toCharArray(); // 小于号 + TEXT['>'] = ">".toCharArray(); // 大于号 + } + + /** + * 转义文本中的HTML字符为安全的字符 + * + * @param text 被转义的文本 + * @return 转义后的文本 + */ + public static String escape(String text) + { + return encode(text); + } + + /** + * 还原被转义的HTML特殊字符 + * + * @param content 包含转义符的HTML内容 + * @return 转换后的字符串 + */ + public static String unescape(String content) + { + return decode(content); + } + + /** + * 清除所有HTML标签,但是不删除标签内的内容 + * + * @param content 文本 + * @return 清除标签后的文本 + */ + public static String clean(String content) + { + return new HTMLFilter().filter(content); + } + + /** + * Escape编码 + * + * @param text 被编码的文本 + * @return 编码后的字符 + */ + private static String encode(String text) + { + if (StringUtils.isEmpty(text)) + { + return StringUtils.EMPTY; + } + + final StringBuilder tmp = new StringBuilder(text.length() * 6); + char c; + for (int i = 0; i < text.length(); i++) + { + c = text.charAt(i); + if (c < 256) + { + tmp.append("%"); + if (c < 16) + { + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + else + { + tmp.append("%u"); + if (c <= 0xfff) + { + // issue#I49JU8@Gitee + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + } + return tmp.toString(); + } + + /** + * Escape解码 + * + * @param content 被转义的内容 + * @return 解码后的字符串 + */ + public static String decode(String content) + { + if (StringUtils.isEmpty(content)) + { + return content; + } + + StringBuilder tmp = new StringBuilder(content.length()); + int lastPos = 0, pos = 0; + char ch; + while (lastPos < content.length()) + { + pos = content.indexOf("%", lastPos); + if (pos == lastPos) + { + if (content.charAt(pos + 1) == 'u') + { + ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16); + tmp.append(ch); + lastPos = pos + 6; + } + else + { + ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16); + tmp.append(ch); + lastPos = pos + 3; + } + } + else + { + if (pos == -1) + { + tmp.append(content.substring(lastPos)); + lastPos = content.length(); + } + else + { + tmp.append(content.substring(lastPos, pos)); + lastPos = pos; + } + } + } + return tmp.toString(); + } + + public static void main(String[] args) + { + String html = ""; + String escape = EscapeUtil.escape(html); + // String html = "ipt>alert(\"XSS\")ipt>"; + // String html = "<123"; + // String html = "123>"; + System.out.println("clean: " + EscapeUtil.clean(html)); + System.out.println("escape: " + escape); + System.out.println("unescape: " + EscapeUtil.unescape(escape)); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java new file mode 100644 index 0000000..ebff3fd --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java @@ -0,0 +1,570 @@ +package com.ruoyi.common.utils.html; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * HTML过滤器,用于去除XSS漏洞隐患。 + * + * @author ruoyi + */ +public final class HTMLFilter +{ + /** + * regex flag union representing /si modifiers in php + **/ + private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; + private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL); + private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI); + private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL); + private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI); + private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI); + private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI); + private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI); + private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI); + private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?"); + private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?"); + private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?"); + private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))"); + private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL); + private static final Pattern P_END_ARROW = Pattern.compile("^>"); + private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_AMP = Pattern.compile("&"); + private static final Pattern P_QUOTE = Pattern.compile("\""); + private static final Pattern P_LEFT_ARROW = Pattern.compile("<"); + private static final Pattern P_RIGHT_ARROW = Pattern.compile(">"); + private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>"); + + // @xxx could grow large... maybe use sesat's ReferenceMap + private static final ConcurrentMap P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map vTagCounts = new HashMap<>(); + + /** + * html elements which must always be self-closing (e.g. "") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "") + **/ + private final String[] vNeedClosingTags; + /** + * set of disallowed html elements + **/ + private final String[] vDisallowed; + /** + * attributes which should be checked for valid protocols + **/ + private final String[] vProtocolAtts; + /** + * allowed protocols + **/ + private final String[] vAllowedProtocols; + /** + * tags which should be removed if they contain no content (e.g. "" or "") + **/ + private final String[] vRemoveBlanks; + /** + * entities allowed within html markup + **/ + private final String[] vAllowedEntities; + /** + * flag determining whether comments are allowed in input String. + */ + private final boolean stripComment; + private final boolean encodeQuotes; + /** + * flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "" + * becomes " text "). If set to false, unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLFilter() + { + vAllowed = new HashMap<>(); + + final ArrayList a_atts = new ArrayList<>(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList img_atts = new ArrayList<>(); + img_atts.add("src"); + img_atts.add("width"); + img_atts.add("height"); + img_atts.add("alt"); + vAllowed.put("img", img_atts); + + final ArrayList no_atts = new ArrayList<>(); + vAllowed.put("b", no_atts); + vAllowed.put("strong", no_atts); + vAllowed.put("i", no_atts); + vAllowed.put("em", no_atts); + + vSelfClosingTags = new String[] { "img" }; + vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" }; + vDisallowed = new String[] {}; + vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp. + vProtocolAtts = new String[] { "src", "href" }; + vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" }; + vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" }; + stripComment = true; + encodeQuotes = true; + alwaysMakeTags = false; + } + + /** + * Map-parameter configurable constructor. + * + * @param conf map containing configuration. keys match field names. + */ + @SuppressWarnings("unchecked") + public HTMLFilter(final Map conf) + { + + assert conf.containsKey("vAllowed") : "configuration requires vAllowed"; + assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags"; + assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags"; + assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed"; + assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols"; + assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts"; + assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks"; + assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities"; + + vAllowed = Collections.unmodifiableMap((HashMap>) conf.get("vAllowed")); + vSelfClosingTags = (String[]) conf.get("vSelfClosingTags"); + vNeedClosingTags = (String[]) conf.get("vNeedClosingTags"); + vDisallowed = (String[]) conf.get("vDisallowed"); + vAllowedProtocols = (String[]) conf.get("vAllowedProtocols"); + vProtocolAtts = (String[]) conf.get("vProtocolAtts"); + vRemoveBlanks = (String[]) conf.get("vRemoveBlanks"); + vAllowedEntities = (String[]) conf.get("vAllowedEntities"); + stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true; + encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true; + alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true; + } + + private void reset() + { + vTagCounts.clear(); + } + + // --------------------------------------------------------------- + // my versions of some PHP library functions + public static String chr(final int decimal) + { + return String.valueOf((char) decimal); + } + + public static String htmlSpecialChars(final String s) + { + String result = s; + result = regexReplace(P_AMP, "&", result); + result = regexReplace(P_QUOTE, """, result); + result = regexReplace(P_LEFT_ARROW, "<", result); + result = regexReplace(P_RIGHT_ARROW, ">", result); + return result; + } + + // --------------------------------------------------------------- + + /** + * given a user submitted input String, filter out any invalid or restricted html. + * + * @param input text (i.e. submitted by a user) than may contain html + * @return "clean" version of input, with only valid, whitelisted html elements allowed + */ + public String filter(final String input) + { + reset(); + String s = input; + + s = escapeComments(s); + + s = balanceHTML(s); + + s = checkTags(s); + + s = processRemoveBlanks(s); + + // s = validateEntities(s); + + return s; + } + + public boolean isAlwaysMakeTags() + { + return alwaysMakeTags; + } + + public boolean isStripComments() + { + return stripComment; + } + + private String escapeComments(final String s) + { + final Matcher m = P_COMMENTS.matcher(s); + final StringBuffer buf = new StringBuffer(); + if (m.find()) + { + final String match = m.group(1); // (.*?) + m.appendReplacement(buf, Matcher.quoteReplacement("")); + } + m.appendTail(buf); + + return buf.toString(); + } + + private String balanceHTML(String s) + { + if (alwaysMakeTags) + { + // + // try and form html + // + s = regexReplace(P_END_ARROW, "", s); + // 不追加结束标签 + s = regexReplace(P_BODY_TO_END, "<$1>", s); + s = regexReplace(P_XML_CONTENT, "$1<$2", s); + + } + else + { + // + // escape stray brackets + // + s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); + s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); + + // + // the last regexp causes '<>' entities to appear + // (we need to do a lookahead assertion so that the last bracket can + // be used in the next pass of the regexp) + // + s = regexReplace(P_BOTH_ARROWS, "", s); + } + + return s; + } + + private String checkTags(String s) + { + Matcher m = P_TAGS.matcher(s); + + final StringBuffer buf = new StringBuffer(); + while (m.find()) + { + String replaceStr = m.group(1); + replaceStr = processTag(replaceStr); + m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr)); + } + m.appendTail(buf); + + // these get tallied in processTag + // (remember to reset before subsequent calls to filter method) + final StringBuilder sBuilder = new StringBuilder(buf.toString()); + for (String key : vTagCounts.keySet()) + { + for (int ii = 0; ii < vTagCounts.get(key); ii++) + { + sBuilder.append(""); + } + } + s = sBuilder.toString(); + + return s; + } + + private String processRemoveBlanks(final String s) + { + String result = s; + for (String tag : vRemoveBlanks) + { + if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) + { + P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>")); + } + result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result); + if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) + { + P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>")); + } + result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result); + } + + return result; + } + + private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) + { + Matcher m = regex_pattern.matcher(s); + return m.replaceAll(replacement); + } + + private String processTag(final String s) + { + // ending tags + Matcher m = P_END_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + if (allowed(name)) + { + if (!inArray(name, vSelfClosingTags)) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) - 1); + return ""; + } + } + } + } + + // starting tags + m = P_START_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + final String body = m.group(2); + String ending = m.group(3); + + // debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" ); + if (allowed(name)) + { + final StringBuilder params = new StringBuilder(); + + final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body); + final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body); + final List paramNames = new ArrayList<>(); + final List paramValues = new ArrayList<>(); + while (m2.find()) + { + paramNames.add(m2.group(1)); // ([a-z0-9]+) + paramValues.add(m2.group(3)); // (.*?) + } + while (m3.find()) + { + paramNames.add(m3.group(1)); // ([a-z0-9]+) + paramValues.add(m3.group(3)); // ([^\"\\s']+) + } + + String paramName, paramValue; + for (int ii = 0; ii < paramNames.size(); ii++) + { + paramName = paramNames.get(ii).toLowerCase(); + paramValue = paramValues.get(ii); + + // debug( "paramName='" + paramName + "'" ); + // debug( "paramValue='" + paramValue + "'" ); + // debug( "allowed? " + vAllowed.get( name ).contains( paramName ) ); + + if (allowedAttribute(name, paramName)) + { + if (inArray(paramName, vProtocolAtts)) + { + paramValue = processParamProtocol(paramValue); + } + params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\\\""); + } + } + + if (inArray(name, vSelfClosingTags)) + { + ending = " /"; + } + + if (inArray(name, vNeedClosingTags)) + { + ending = ""; + } + + if (ending == null || ending.length() < 1) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) + 1); + } + else + { + vTagCounts.put(name, 1); + } + } + else + { + ending = " /"; + } + return "<" + name + params + ending + ">"; + } + else + { + return ""; + } + } + + // comments + m = P_COMMENT.matcher(s); + if (!stripComment && m.find()) + { + return "<" + m.group() + ">"; + } + + return ""; + } + + private String processParamProtocol(String s) + { + s = decodeEntities(s); + final Matcher m = P_PROTOCOL.matcher(s); + if (m.find()) + { + final String protocol = m.group(1); + if (!inArray(protocol, vAllowedProtocols)) + { + // bad protocol, turn into local anchor link instead + s = "#" + s.substring(protocol.length() + 1); + if (s.startsWith("#//")) + { + s = "#" + s.substring(3); + } + } + } + + return s; + } + + private String decodeEntities(String s) + { + StringBuffer buf = new StringBuffer(); + + Matcher m = P_ENTITY.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.decode(match).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENTITY_UNICODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENCODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + s = validateEntities(s); + return s; + } + + private String validateEntities(final String s) + { + StringBuffer buf = new StringBuffer(); + + // validate entities throughout the string + Matcher m = P_VALID_ENTITIES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // ([^&;]*) + final String two = m.group(2); // (?=(;|&|$)) + m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two))); + } + m.appendTail(buf); + + return encodeQuotes(buf.toString()); + } + + private String encodeQuotes(final String s) + { + if (encodeQuotes) + { + StringBuffer buf = new StringBuffer(); + Matcher m = P_VALID_QUOTES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // (>|^) + final String two = m.group(2); // ([^<]+?) + final String three = m.group(3); // (<|$) + // 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, two) + m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three)); + } + m.appendTail(buf); + return buf.toString(); + } + else + { + return s; + } + } + + private String checkEntity(final String preamble, final String term) + { + + return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble; + } + + private boolean isValidEntity(final String entity) + { + return inArray(entity, vAllowedEntities); + } + + private static boolean inArray(final String s, final String[] array) + { + for (String item : array) + { + if (item != null && item.equals(s)) + { + return true; + } + } + return false; + } + + private boolean allowed(final String name) + { + return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed); + } + + private boolean allowedAttribute(final String name, final String paramName) + { + return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName)); + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpConf.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpConf.java new file mode 100644 index 0000000..8677730 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpConf.java @@ -0,0 +1,33 @@ +package com.ruoyi.common.utils.http; + +/** + * http 配置信息 + * + * @author ruoyi + */ +public class HttpConf +{ + // 获取连接的最大等待时间 + public static int WAIT_TIMEOUT = 10000; + + // 连接超时时间 + public static int CONNECT_TIMEOUT = 10000; + + // 读取超时时间 + public static int SO_TIMEOUT = 60000; + + // 最大连接数 + public static int MAX_TOTAL_CONN = 200; + + // 每个路由最大连接数 + public static int MAX_ROUTE_CONN = 150; + + // 重试次数 + public static int RETRY_COUNT = 3; + + // EPTWebServes地址 + public static String EPTWEBSERVES_URL; + + // tomcat默认keepAliveTimeout为20s + public static int KEEP_ALIVE_TIMEOUT; +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java new file mode 100644 index 0000000..401f25a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java @@ -0,0 +1,55 @@ +package com.ruoyi.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import jakarta.servlet.ServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 通用http工具封装 + * + * @author ruoyi + */ +public class HttpHelper +{ + private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); + + public static String getBodyString(ServletRequest request) + { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try (InputStream inputStream = request.getInputStream()) + { + reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String line = ""; + while ((line = reader.readLine()) != null) + { + sb.append(line); + } + } + catch (IOException e) + { + LOGGER.warn("getBodyString出现问题!"); + } + finally + { + if (reader != null) + { + try + { + reader.close(); + } + catch (IOException e) + { + LOGGER.error(ExceptionUtils.getMessage(e)); + } + } + } + return sb.toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java new file mode 100644 index 0000000..78a103e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java @@ -0,0 +1,471 @@ +package com.ruoyi.common.utils.http; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.hc.client5.http.ConnectTimeoutException; +import org.apache.hc.client5.http.ConnectionKeepAliveStrategy; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.config.RequestConfig.Builder; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.StringUtils; + +/** + * 通用http发送方法 + * + * @author ruoyi + */ +public class HttpUtils { + private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + + public static RequestConfig requestConfig; + + private static CloseableHttpClient httpClient; + + private static PoolingHttpClientConnectionManager connMgr; + + static { + HttpUtils.initClient(); + } + + // ================= 新统一 API ================= + public static String get(String url) { + try { + return getCall(url, null, Constants.UTF8); + } catch (Exception e) { + log.error("GET 请求异常 url={}", url, e); + return null; + } + } + + public static String get(String url, Map headers) { + try { + HttpGet httpGet = new HttpGet(url); + httpGet.setConfig(requestConfig); + if (headers != null) + headers.forEach(httpGet::addHeader); + return execute(httpGet, Constants.UTF8, true); + } catch (Exception e) { + log.error("GET 请求异常", e); + return null; + } + } + + public static String postJson(String url, Object body) { + try { + return postCall(url, ContentType.APPLICATION_JSON.getMimeType(), JSON.toJSONString(body)); + } catch (Exception e) { + log.error("POST JSON 异常", e); + return null; + } + } + + /** + * POST JSON(支持自定义请求头) + */ + public static String postJson(String url, Object body, Map headers) { + try { + return postWithHeaders(url, ContentType.APPLICATION_JSON.getMimeType(), JSON.toJSONString(body), headers); + } catch (Exception e) { + log.error("POST JSON 异常", e); + return null; + } + } + + public static String postForm(String url, Map form) { + try { + return postCall(url, "application/x-www-form-urlencoded", form); + } catch (Exception e) { + log.error("POST Form 异常", e); + return null; + } + } + + public static String postXml(String url, String xml) { + try { + return postCall(url, ContentType.APPLICATION_XML.getMimeType(), xml); + } catch (Exception e) { + log.error("POST XML 异常", e); + return null; + } + } + + public static String postRaw(String url, String raw, ContentType type) { + try { + return postCall(url, type != null ? type.getMimeType() : "text/plain", raw); + } catch (Exception e) { + log.error("POST Raw 异常", e); + return null; + } + } + + public static String postRaw(String url, String raw, ContentType type, Map headers) { + try { + return postWithHeaders(url, type != null ? type.getMimeType() : "text/plain", raw, headers); + } catch (Exception e) { + log.error("POST Raw 异常", e); + return null; + } + } + + /** + * 获取httpClient + * + * @return + */ + public static CloseableHttpClient getHttpClient() { + if (httpClient != null) { + return httpClient; + } else { + return HttpClients.createDefault(); + } + } + + /** + * 创建连接池管理器 + * + * @return + */ + private static PoolingHttpClientConnectionManager createConnectionManager() { + + PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(); + // 将最大连接数增加到 + connMgr.setMaxTotal(HttpConf.MAX_TOTAL_CONN); + // 将每个路由基础的连接增加到 + connMgr.setDefaultMaxPerRoute(HttpConf.MAX_ROUTE_CONN); + + return connMgr; + } + + /** + * 根据当前配置创建HTTP请求配置参数。 + * + * @return 返回HTTP请求配置。 + */ + @SuppressWarnings("deprecation") + private static RequestConfig createRequestConfig() { + Builder builder = RequestConfig.custom(); + int crTimeout = StringUtils.nvl(HttpConf.WAIT_TIMEOUT, 10000); + int connTimeout = StringUtils.nvl(HttpConf.CONNECT_TIMEOUT, 10000); + int respTimeout = StringUtils.nvl(HttpConf.SO_TIMEOUT, 60000); + builder.setConnectionRequestTimeout(Timeout.ofMilliseconds(crTimeout)); + builder.setConnectTimeout(Timeout.ofMilliseconds(connTimeout)); + builder.setResponseTimeout(Timeout.ofMilliseconds(respTimeout)); + return builder.build(); + } + + /** + * 创建默认的HTTPS客户端,信任所有的证书。 + * + * @return 返回HTTPS客户端,如果创建失败,返回HTTP客户端。 + */ + private static CloseableHttpClient createHttpClient(PoolingHttpClientConnectionManager connMgr) { + try { + // 构建信任所有证书的上下文(不鼓励生产使用) + SSLContextBuilder.create().loadTrustMaterial(null, (chain, authType) -> true).build(); + ConnectionKeepAliveStrategy connectionKeepAliveStrategy = (response, context) -> TimeValue + .ofMilliseconds(HttpConf.KEEP_ALIVE_TIMEOUT); + DefaultHttpRequestRetryStrategy retryStrategy = new DefaultHttpRequestRetryStrategy(HttpConf.RETRY_COUNT, + TimeValue.ofMilliseconds(1000)); + httpClient = HttpClients.custom() + // 直接设置 SSLContext 支持(5.x 没有 setSSLContext 方法时,依赖默认) + .setConnectionManager(connMgr) + .setDefaultRequestConfig(requestConfig) + .setRetryStrategy(retryStrategy) + .setKeepAliveStrategy(connectionKeepAliveStrategy) + .evictExpiredConnections() + .evictIdleConnections(TimeValue.ofMilliseconds(HttpConf.KEEP_ALIVE_TIMEOUT)) + .build(); + } catch (Exception e) { + log.error("Create http client failed", e); + httpClient = HttpClients.createDefault(); + } + + return httpClient; + } + + /** + * 初始化 只需调用一次 + */ + public synchronized static CloseableHttpClient initClient() { + if (httpClient == null) { + connMgr = createConnectionManager(); + requestConfig = createRequestConfig(); + // 初始化httpClient连接池 + httpClient = createHttpClient(connMgr); + // 使用内置 evict 机制,无需独立线程 + } + + return httpClient; + } + + /** + * 关闭HTTP客户端。 + * + * @param httpClient HTTP客户端。 + */ + public synchronized static void shutdown() { + try { + // no extra thread to shutdown + } catch (Exception e) { + log.error("httpclient connection manager close", e); + } + + try { + if (httpClient != null) { + httpClient.close(); + httpClient = null; + } + } catch (IOException e) { + log.error("httpclient close", e); + } + } + + /** + * 请求上游 GET提交 + * + * @param uri + * @throws IOException + */ + public static String getCall(final String uri) throws Exception { + + return getCall(uri, null, Constants.UTF8); + } + + /** + * 请求上游 GET提交 + * + * @param uri + * @param contentType + * @throws IOException + */ + public static String getCall(final String uri, String contentType) throws Exception { + + return getCall(uri, contentType, Constants.UTF8); + } + + /** + * 请求上游 GET提交 + * + * @param uri + * @param contentType + * @param charsetName + * @throws IOException + */ + public static String getCall(final String uri, String contentType, String charsetName) throws Exception { + + final String url = uri; + final HttpGet httpGet = new HttpGet(url); + httpGet.setConfig(requestConfig); + if (!StringUtils.isEmpty(contentType)) { + httpGet.addHeader("Content-Type", contentType); + } + return execute(httpGet, charsetName, true); + + } + + /** + * 请求上游 POST提交 + * + * @param uri + * @param paramsMap + * @throws IOException + */ + public static String postCall(final String uri, Map paramsMap) throws Exception { + return postCall(uri, null, paramsMap, Constants.UTF8); + } + + /** + * 请求上游 POST提交 + * + * @param uri + * @param contentType + * @param paramsMap + * @throws IOException + */ + public static String postCall(final String uri, String contentType, Map paramsMap) + throws Exception { + + return postCall(uri, contentType, paramsMap, Constants.UTF8); + } + + /** + * 请求上游 POST提交 + * + * @param uri + * @param contentType + * @param paramsMap + * @param charsetName + * @throws IOException + */ + public static String postCall(final String uri, String contentType, Map paramsMap, + String charsetName) throws Exception { + + final String url = uri; + final HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(requestConfig); + if (!StringUtils.isEmpty(contentType)) { + httpPost.addHeader("Content-Type", contentType); + } + // 添加参数 + List list = new ArrayList(); + if (paramsMap != null) { + for (Map.Entry entry : paramsMap.entrySet()) { + list.add(new BasicNameValuePair(entry.getKey(), (String) entry.getValue())); + } + } + httpPost.setEntity(new UrlEncodedFormEntity(list, Charset.forName(charsetName))); + + return execute(httpPost, charsetName, false); + } + + /** + * 请求上游 POST提交 + * + * @param uri + * @param param + * @throws IOException + */ + public static String postCall(final String uri, String param) throws Exception { + + return postCall(uri, null, param, Constants.UTF8); + } + + /** + * 请求上游 POST提交 + * + * @param uri + * @param contentType + * @param param + * @throws IOException + */ + public static String postCall(final String uri, String contentType, String param) throws Exception { + + return postCall(uri, contentType, param, Constants.UTF8); + } + + /** + * 请求上游 POST提交 + * + * @param uri + * @param contentType + * @param param + * @param charsetName + * @throws IOException + */ + public static String postCall(final String uri, String contentType, String param, String charsetName) + throws Exception { + + final String url = uri; + final HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(requestConfig); + if (!StringUtils.isEmpty(contentType)) { + httpPost.addHeader("Content-Type", contentType); + } else { + httpPost.addHeader("Content-Type", "application/json"); + } + // 添加参数 + // 兼容 5.x API 使用 ContentType 指定字符集 + StringEntity paramEntity = new StringEntity(param, + ContentType.parse(contentType != null ? contentType : "application/json") + .withCharset(Charset.forName(charsetName))); + httpPost.setEntity(paramEntity); + + return execute(httpPost, charsetName, false); + } + + /** + * 判断HTTP异常是否为读取超时。 + * + * @param e 异常对象。 + * @return 如果是读取引起的异常(而非连接),则返回true;否则返回false。 + */ + public static boolean isReadTimeout(final Throwable e) { + return (!isCausedBy(e, ConnectTimeoutException.class) && isCausedBy(e, SocketTimeoutException.class)); + } + + /** + * 检测异常e被触发的原因是不是因为异常cause。检测被封装的异常。 + * + * @param e 捕获的异常。 + * @param cause 异常触发原因。 + * @return 如果异常e是由cause类异常触发,则返回true;否则返回false。 + */ + public static boolean isCausedBy(final Throwable e, final Class cause) { + if (cause.isAssignableFrom(e.getClass())) { + return true; + } else { + Throwable t = e.getCause(); + while (t != null && t != e) { + if (cause.isAssignableFrom(t.getClass())) { + return true; + } + t = t.getCause(); + } + return false; + } + } + + private static String execute(HttpUriRequestBase request, String charset, boolean allowForbidden) + throws IOException { + HttpClientResponseHandler handler = response -> { + int code = response.getCode(); + if (code == HttpStatus.SC_OK || (allowForbidden && code == HttpStatus.SC_FORBIDDEN)) { + HttpEntity entity = response.getEntity(); + String text = entity != null ? EntityUtils.toString(entity, charset) : ""; + if (entity != null) + EntityUtils.consume(entity); + return text; + } + throw new IOException("HTTP StatusCode=" + code); + }; + return getHttpClient().execute(request, handler); + } + + private static String postWithHeaders(String url, String contentType, String body, Map headers) + throws Exception { + HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(requestConfig); + if (contentType != null) { + httpPost.addHeader("Content-Type", contentType); + } + if (headers != null) { + headers.forEach((k, v) -> { + if (v != null) + httpPost.addHeader(k, v); + }); + } + StringEntity entity = new StringEntity(body == null ? "" : body, + ContentType.parse(contentType != null ? contentType : "text/plain") + .withCharset(Charset.forName(Constants.UTF8))); + httpPost.setEntity(entity); + return execute(httpPost, Constants.UTF8, false); + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java new file mode 100644 index 0000000..c79c1ab --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java @@ -0,0 +1,48 @@ +package com.ruoyi.common.utils.ip; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.http.HttpUtils; + +/** + * 获取地址类 + * + * @author ruoyi + */ +public class AddressUtils { + private static final Logger log = LoggerFactory.getLogger(AddressUtils.class); + + // IP地址查询 + public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp"; + + // 未知地址 + public static final String UNKNOWN = "XX XX"; + + public static String getRealAddressByIP(String ip) { + // 内网不查询 + if (IpUtils.internalIp(ip)) { + return "内网IP"; + } + if (RuoYiConfig.isAddressEnabled()) { + try { + String rspStr = HttpUtils.get(IP_URL + "?ip=" + ip + "&json=true"); + if (StringUtils.isEmpty(rspStr)) { + log.error("获取地理位置异常 {}", ip); + return UNKNOWN; + } + JSONObject obj = JSON.parseObject(rspStr); + String region = obj.getString("pro"); + String city = obj.getString("city"); + return String.format("%s %s", region, city); + } catch (Exception e) { + log.error("获取地理位置异常 {}", ip); + } + } + return UNKNOWN; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java new file mode 100644 index 0000000..a376416 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java @@ -0,0 +1,382 @@ +package com.ruoyi.common.utils.ip; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import jakarta.servlet.http.HttpServletRequest; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * 获取IP方法 + * + * @author ruoyi + */ +public class IpUtils +{ + public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)"; + // 匹配 ip + public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")"; + public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))"; + // 匹配网段 + public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")"; + + /** + * 获取客户端IP + * + * @return IP地址 + */ + public static String getIpAddr() + { + return getIpAddr(ServletUtils.getRequest()); + } + + /** + * 获取客户端IP + * + * @param request 请求对象 + * @return IP地址 + */ + public static String getIpAddr(HttpServletRequest request) + { + if (request == null) + { + return "unknown"; + } + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Forwarded-For"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getRemoteAddr(); + } + + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param ip IP地址 + * @return 结果 + */ + public static boolean internalIp(String ip) + { + byte[] addr = textToNumericFormatV4(ip); + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param addr byte地址 + * @return 结果 + */ + private static boolean internalIp(byte[] addr) + { + if (StringUtils.isNull(addr) || addr.length < 2) + { + return true; + } + final byte b0 = addr[0]; + final byte b1 = addr[1]; + // 10.x.x.x/8 + final byte SECTION_1 = 0x0A; + // 172.16.x.x/12 + final byte SECTION_2 = (byte) 0xAC; + final byte SECTION_3 = (byte) 0x10; + final byte SECTION_4 = (byte) 0x1F; + // 192.168.x.x/16 + final byte SECTION_5 = (byte) 0xC0; + final byte SECTION_6 = (byte) 0xA8; + switch (b0) + { + case SECTION_1: + return true; + case SECTION_2: + if (b1 >= SECTION_3 && b1 <= SECTION_4) + { + return true; + } + case SECTION_5: + switch (b1) + { + case SECTION_6: + return true; + } + default: + return false; + } + } + + /** + * 将IPv4地址转换成字节 + * + * @param text IPv4地址 + * @return byte 字节 + */ + public static byte[] textToNumericFormatV4(String text) + { + if (text.length() == 0) + { + return null; + } + + byte[] bytes = new byte[4]; + String[] elements = text.split("\\.", -1); + try + { + long l; + int i; + switch (elements.length) + { + case 1: + l = Long.parseLong(elements[0]); + if ((l < 0L) || (l > 4294967295L)) + { + return null; + } + bytes[0] = (byte) (int) (l >> 24 & 0xFF); + bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 2: + l = Integer.parseInt(elements[0]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[0] = (byte) (int) (l & 0xFF); + l = Integer.parseInt(elements[1]); + if ((l < 0L) || (l > 16777215L)) + { + return null; + } + bytes[1] = (byte) (int) (l >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 3: + for (i = 0; i < 2; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + l = Integer.parseInt(elements[2]); + if ((l < 0L) || (l > 65535L)) + { + return null; + } + bytes[2] = (byte) (int) (l >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 4: + for (i = 0; i < 4; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + break; + default: + return null; + } + } + catch (NumberFormatException e) + { + return null; + } + return bytes; + } + + /** + * 获取IP地址 + * + * @return 本地IP地址 + */ + public static String getHostIp() + { + try + { + return InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException e) + { + } + return "127.0.0.1"; + } + + /** + * 获取主机名 + * + * @return 本地主机名 + */ + public static String getHostName() + { + try + { + return InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException e) + { + } + return "未知"; + } + + /** + * 从多级反向代理中获得第一个非unknown IP地址 + * + * @param ip 获得的IP地址 + * @return 第一个非unknown IP地址 + */ + public static String getMultistageReverseProxyIp(String ip) + { + // 多级反向代理检测 + if (ip != null && ip.indexOf(",") > 0) + { + final String[] ips = ip.trim().split(","); + for (String subIp : ips) + { + if (false == isUnknown(subIp)) + { + ip = subIp; + break; + } + } + } + return StringUtils.substring(ip, 0, 255); + } + + /** + * 检测给定字符串是否为未知,多用于检测HTTP请求相关 + * + * @param checkString 被检测的字符串 + * @return 是否未知 + */ + public static boolean isUnknown(String checkString) + { + return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); + } + + /** + * 是否为IP + */ + public static boolean isIP(String ip) + { + return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP); + } + + /** + * 是否为IP,或 *为间隔的通配符地址 + */ + public static boolean isIpWildCard(String ip) + { + return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD); + } + + /** + * 检测参数是否在ip通配符里 + */ + public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip) + { + String[] s1 = ipWildCard.split("\\."); + String[] s2 = ip.split("\\."); + boolean isMatchedSeg = true; + for (int i = 0; i < s1.length && !s1[i].equals("*"); i++) + { + if (!s1[i].equals(s2[i])) + { + isMatchedSeg = false; + break; + } + } + return isMatchedSeg; + } + + /** + * 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串 + */ + public static boolean isIPSegment(String ipSeg) + { + return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG); + } + + /** + * 判断ip是否在指定网段中 + */ + public static boolean ipIsInNetNoCheck(String iparea, String ip) + { + int idx = iparea.indexOf('-'); + String[] sips = iparea.substring(0, idx).split("\\."); + String[] sipe = iparea.substring(idx + 1).split("\\."); + String[] sipt = ip.split("\\."); + long ips = 0L, ipe = 0L, ipt = 0L; + for (int i = 0; i < 4; ++i) + { + ips = ips << 8 | Integer.parseInt(sips[i]); + ipe = ipe << 8 | Integer.parseInt(sipe[i]); + ipt = ipt << 8 | Integer.parseInt(sipt[i]); + } + if (ips > ipe) + { + long t = ips; + ips = ipe; + ipe = t; + } + return ips <= ipt && ipt <= ipe; + } + + /** + * 校验ip是否符合过滤串规则 + * + * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99` + * @param ip 校验IP地址 + * @return boolean 结果 + */ + public static boolean isMatchedIp(String filter, String ip) + { + if (StringUtils.isEmpty(filter) || StringUtils.isEmpty(ip)) + { + return false; + } + String[] ips = filter.split(";"); + for (String iStr : ips) + { + if (isIP(iStr) && iStr.equals(ip)) + { + return true; + } + else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip)) + { + return true; + } + else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip)) + { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java new file mode 100644 index 0000000..5ea74c1 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.utils.poi; + +/** + * Excel数据格式处理适配器 + * + * @author ruoyi + */ +public interface ExcelHandlerAdapter +{ + /** + * 格式化 + * + * @param value 单元格数据值 + * @param args excel注解args参数组 + * + * @return 处理后的值 + */ + Object format(Object value, String[] args); +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java new file mode 100644 index 0000000..808d8a4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java @@ -0,0 +1,1897 @@ +package com.ruoyi.common.utils.poi; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.hssf.usermodel.HSSFPicture; +import org.apache.poi.hssf.usermodel.HSSFPictureData; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ooxml.POIXMLDocumentPart; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.usermodel.PictureData; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.util.IOUtils; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFDataValidation; +import org.apache.poi.xssf.usermodel.XSSFDrawing; +import org.apache.poi.xssf.usermodel.XSSFPicture; +import org.apache.poi.xssf.usermodel.XSSFShape; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.annotation.Excel.Type; +import com.ruoyi.common.annotation.Excels; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.UtilException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileTypeUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.common.utils.file.ImageUtils; +import com.ruoyi.common.utils.reflect.ReflectUtils; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * Excel相关处理 + * + * @author ruoyi + */ +public class ExcelUtil +{ + private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); + + public static final String SEPARATOR = ","; + + public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; + + public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; + + /** + * 用于dictType属性数据存储,避免重复查缓存 + */ + public Map sysDictMap = new HashMap(); + + /** + * Excel sheet最大行数,默认65536 + */ + public static final int sheetSize = 65536; + + /** + * 工作表名称 + */ + private String sheetName; + + /** + * 导出类型(EXPORT:导出数据;IMPORT:导入模板) + */ + private Type type; + + /** + * 工作薄对象 + */ + private Workbook wb; + + /** + * 工作表对象 + */ + private Sheet sheet; + + /** + * 样式列表 + */ + private Map styles; + + /** + * 导入导出数据列表 + */ + private List list; + + /** + * 注解列表 + */ + private List fields; + + /** + * 当前行号 + */ + private int rownum; + + /** + * 标题 + */ + private String title; + + /** + * 最大高度 + */ + private short maxHeight; + + /** + * 合并后最后行数 + */ + private int subMergedLastRowNum = 0; + + /** + * 合并后开始行数 + */ + private int subMergedFirstRowNum = 1; + + /** + * 对象的子列表方法 + */ + private Method subMethod; + + /** + * 对象的子列表属性 + */ + private List subFields; + + /** + * 统计列表 + */ + private Map statistics = new HashMap(); + + /** + * 实体对象 + */ + public Class clazz; + + /** + * 需要显示列属性 + */ + public String[] includeFields; + + /** + * 需要排除列属性 + */ + public String[] excludeFields; + + public ExcelUtil(Class clazz) + { + this.clazz = clazz; + } + + /** + * 仅在Excel中显示列属性 + * + * @param fields 列属性名 示例[单个"name"/多个"id","name"] + */ + public void showColumn(String... fields) + { + this.includeFields = fields; + } + + /** + * 隐藏Excel中列属性 + * + * @param fields 列属性名 示例[单个"name"/多个"id","name"] + */ + public void hideColumn(String... fields) + { + this.excludeFields = fields; + } + + public void init(List list, String sheetName, String title, Type type) + { + if (list == null) + { + list = new ArrayList(); + } + this.list = list; + this.sheetName = sheetName; + this.type = type; + this.title = title; + createExcelField(); + createWorkbook(); + createTitle(); + createSubHead(); + } + + /** + * 创建excel第一行标题 + */ + public void createTitle() + { + if (StringUtils.isNotEmpty(title)) + { + int titleLastCol = this.fields.size() - 1; + if (isSubList()) + { + titleLastCol = titleLastCol + subFields.size() - 1; + } + Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); + titleRow.setHeightInPoints(30); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellStyle(styles.get("title")); + titleCell.setCellValue(title); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), 0, titleLastCol)); + } + } + + /** + * 创建对象的子列表名称 + */ + public void createSubHead() + { + if (isSubList()) + { + Row subRow = sheet.createRow(rownum); + int column = 0; + int subFieldSize = subFields != null ? subFields.size() : 0; + for (Object[] objects : fields) + { + Field field = (Field) objects[0]; + Excel attr = (Excel) objects[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + Cell cell = subRow.createCell(column); + cell.setCellValue(attr.name()); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + if (subFieldSize > 1) + { + CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1); + sheet.addMergedRegion(cellAddress); + } + column += subFieldSize; + } + else + { + Cell cell = subRow.createCell(column++); + cell.setCellValue(attr.name()); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + } + } + rownum++; + } + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(InputStream is) + { + List list = null; + try + { + list = importExcel(is, 0); + } + catch (Exception e) + { + log.error("导入Excel异常{}", e.getMessage()); + throw new UtilException(e.getMessage()); + } + finally + { + IOUtils.closeQuietly(is); + } + return list; + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @param titleNum 标题占用行数 + * @return 转换后集合 + */ + public List importExcel(InputStream is, int titleNum) throws Exception + { + return importExcel(StringUtils.EMPTY, is, titleNum); + } + + /** + * 对excel表单指定表格索引名转换成list + * + * @param sheetName 表格索引名 + * @param titleNum 标题占用行数 + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(String sheetName, InputStream is, int titleNum) throws Exception + { + this.type = Type.IMPORT; + this.wb = WorkbookFactory.create(is); + List list = new ArrayList(); + // 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet + Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0); + if (sheet == null) + { + throw new IOException("文件sheet不存在"); + } + boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook); + Map> pictures = null; + if (isXSSFWorkbook) + { + pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb); + + } + else + { + pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb); + } + // 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1 + int rows = sheet.getLastRowNum(); + if (rows > 0) + { + // 定义一个map用于存放excel列的序号和field. + Map cellMap = new HashMap(); + // 获取表头 + Row heard = sheet.getRow(titleNum); + for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) + { + Cell cell = heard.getCell(i); + if (StringUtils.isNotNull(cell)) + { + String value = this.getCellValue(heard, i).toString(); + cellMap.put(value, i); + } + else + { + cellMap.put(null, i); + } + } + // 有数据时才处理 得到类的所有field. + List fields = this.getFields(); + Map fieldsMap = new HashMap(); + for (Object[] objects : fields) + { + Excel attr = (Excel) objects[1]; + Integer column = cellMap.get(attr.name()); + if (column != null) + { + fieldsMap.put(column, objects); + } + } + for (int i = titleNum + 1; i <= rows; i++) + { + // 从第2行开始取数据,默认第一行是表头. + Row row = sheet.getRow(i); + // 判断当前行是否是空行 + if (isRowEmpty(row)) + { + continue; + } + T entity = null; + for (Map.Entry entry : fieldsMap.entrySet()) + { + Object val = this.getCellValue(row, entry.getKey()); + + // 如果不存在实例则新建. + entity = (entity == null ? clazz.getDeclaredConstructor().newInstance() : entity); + // 从map中得到对应列的field. + Field field = (Field) entry.getValue()[0]; + Excel attr = (Excel) entry.getValue()[1]; + // 取得类型,并根据对象类型设置值. + Class fieldType = field.getType(); + if (String.class == fieldType) + { + String s = Convert.toStr(val); + if (s.matches("^\\d+\\.0$")) + { + val = StringUtils.substringBefore(s, ".0"); + } + else + { + String dateFormat = field.getAnnotation(Excel.class).dateFormat(); + if (StringUtils.isNotEmpty(dateFormat)) + { + val = parseDateToStr(dateFormat, val); + } + else + { + val = Convert.toStr(val); + } + } + } + else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toInt(val); + } + else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toLong(val); + } + else if (Double.TYPE == fieldType || Double.class == fieldType) + { + val = Convert.toDouble(val); + } + else if (Float.TYPE == fieldType || Float.class == fieldType) + { + val = Convert.toFloat(val); + } + else if (BigDecimal.class == fieldType) + { + val = Convert.toBigDecimal(val); + } + else if (Date.class == fieldType) + { + if (val instanceof String) + { + val = DateUtils.parseDate(val); + } + else if (val instanceof Double) + { + val = DateUtil.getJavaDate((Double) val); + } + } + else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) + { + val = Convert.toBool(val, false); + } + if (StringUtils.isNotNull(fieldType)) + { + String propertyName = field.getName(); + if (StringUtils.isNotEmpty(attr.targetAttr())) + { + propertyName = field.getName() + "." + attr.targetAttr(); + } + if (StringUtils.isNotEmpty(attr.readConverterExp())) + { + val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); + } + else if (StringUtils.isNotEmpty(attr.dictType())) + { + if (!sysDictMap.containsKey(attr.dictType() + val)) + { + String dictValue = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); + sysDictMap.put(attr.dictType() + val, dictValue); + } + val = sysDictMap.get(attr.dictType() + val); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + val = dataFormatHandlerAdapter(val, attr, null); + } + else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) + { + StringBuilder propertyString = new StringBuilder(); + List images = pictures.get(row.getRowNum() + "_" + entry.getKey()); + for (PictureData picture : images) + { + byte[] data = picture.getData(); + String fileName = FileUtils.writeImportBytes(data); + propertyString.append(fileName).append(SEPARATOR); + } + val = StringUtils.stripEnd(propertyString.toString(), SEPARATOR); + } + ReflectUtils.invokeSetter(entity, propertyName, val); + } + } + list.add(entity); + } + } + return list; + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName) + { + return exportExcel(list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName, String title) + { + this.init(list, sheetName, title, Type.EXPORT); + return exportExcel(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName) + { + exportExcel(response, list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(list, sheetName, title, Type.EXPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName) + { + return importTemplateExcel(sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName, String title) + { + this.init(null, sheetName, title, Type.IMPORT); + return exportExcel(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName) + { + importTemplateExcel(response, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(null, sheetName, title, Type.IMPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public void exportExcel(HttpServletResponse response) + { + try + { + writeSheet(); + wb.write(response.getOutputStream()); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + } + finally + { + IOUtils.closeQuietly(wb); + } + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public AjaxResult exportExcel() + { + OutputStream out = null; + try + { + writeSheet(); + String filename = encodingFilename(sheetName); + out = new FileOutputStream(getAbsoluteFile(filename)); + wb.write(out); + return AjaxResult.success(filename); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + throw new UtilException("导出Excel失败,请联系网站管理员!"); + } + finally + { + IOUtils.closeQuietly(wb); + IOUtils.closeQuietly(out); + } + } + + /** + * 创建写入数据到Sheet + */ + public void writeSheet() + { + // 取出一共有多少个sheet. + int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize)); + for (int index = 0; index < sheetNo; index++) + { + createSheet(sheetNo, index); + + // 产生一行 + Row row = sheet.createRow(rownum); + int column = 0; + // 写入各个字段的列头名称 + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + this.createHeadCell(subExcel, row, column++); + } + } + else + { + this.createHeadCell(excel, row, column++); + } + } + if (Type.EXPORT.equals(type)) + { + fillExcelData(index, row); + addStatisticsRow(); + } + } + } + + /** + * 填充excel数据 + * + * @param index 序号 + * @param row 单元格行 + */ + @SuppressWarnings("unchecked") + public void fillExcelData(int index, Row row) + { + int startNo = index * sheetSize; + int endNo = Math.min(startNo + sheetSize, list.size()); + int currentRowNum = rownum + 1; // 从标题行后开始 + + for (int i = startNo; i < endNo; i++) + { + row = sheet.createRow(currentRowNum); + T vo = (T) list.get(i); + int column = 0; + int maxSubListSize = getCurrentMaxSubListSize(vo); + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + try + { + Collection subList = (Collection) getTargetValue(vo, field, excel); + if (subList != null && !subList.isEmpty()) + { + int subIndex = 0; + for (Object subVo : subList) + { + Row subRow = sheet.getRow(currentRowNum + subIndex); + if (subRow == null) + { + subRow = sheet.createRow(currentRowNum + subIndex); + } + + int subColumn = column; + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + addCell(subExcel, subRow, (T) subVo, subField, subColumn++); + } + subIndex++; + } + column += subFields.size(); + } + } + catch (Exception e) + { + log.error("填充集合数据失败", e); + } + } + else + { + // 创建单元格并设置值 + addCell(excel, row, vo, field, column); + if (maxSubListSize > 1 && excel.needMerge()) + { + sheet.addMergedRegion(new CellRangeAddress(currentRowNum, currentRowNum + maxSubListSize - 1, column, column)); + } + column++; + } + } + currentRowNum += maxSubListSize; + } + } + + /** + * 获取子列表最大数 + */ + private int getCurrentMaxSubListSize(T vo) + { + int maxSubListSize = 1; + for (Object[] os : fields) + { + Field field = (Field) os[0]; + if (Collection.class.isAssignableFrom(field.getType())) + { + try + { + Collection subList = (Collection) getTargetValue(vo, field, (Excel) os[1]); + if (subList != null && !subList.isEmpty()) + { + maxSubListSize = Math.max(maxSubListSize, subList.size()); + } + } + catch (Exception e) + { + log.error("获取集合大小失败", e); + } + } + } + return maxSubListSize; + } + + /** + * 创建表格样式 + * + * @param wb 工作薄对象 + * @return 样式列表 + */ + private Map createStyles(Workbook wb) + { + // 写入各条记录,每条记录对应excel表中的一行 + Map styles = new HashMap(); + CellStyle style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBold(true); + style.setFont(titleFont); + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + styles.put("title", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + style.setFont(dataFont); + styles.put("data", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setDataFormat(dataFormat.getFormat("######0.00")); + Font totalFont = wb.createFont(); + totalFont.setFontName("Arial"); + totalFont.setFontHeightInPoints((short) 10); + style.setFont(totalFont); + styles.put("total", style); + + styles.putAll(annotationHeaderStyles(wb, styles)); + + styles.putAll(annotationDataStyles(wb)); + + return styles; + } + + /** + * 根据Excel注解创建表格头样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationHeaderStyles(Workbook wb, Map styles) + { + Map headerStyles = new HashMap(); + for (Object[] os : fields) + { + Excel excel = (Excel) os[1]; + String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor()); + if (!headerStyles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(excel.headerBackgroundColor().index); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBold(true); + headerFont.setColor(excel.headerColor().index); + style.setFont(headerFont); + // 设置表格头单元格文本形式 + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + headerStyles.put(key, style); + } + } + return headerStyles; + } + + /** + * 根据Excel注解创建表格列样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationDataStyles(Workbook wb) + { + Map styles = new HashMap(); + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + List subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + annotationDataStyles(styles, subField, subExcel); + } + } + else + { + annotationDataStyles(styles, field, excel); + } + } + return styles; + } + + /** + * 根据Excel注解创建表格列样式 + * + * @param styles 自定义样式列表 + * @param field 属性列信息 + * @param excel 注解信息 + */ + public void annotationDataStyles(Map styles, Field field, Excel excel) + { + String key = StringUtils.format("data_{}_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor(), excel.cellType()); + if (!styles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.setAlignment(excel.align()); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setFillForegroundColor(excel.backgroundColor().getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + dataFont.setColor(excel.color().index); + style.setFont(dataFont); + if (ColumnType.TEXT == excel.cellType()) + { + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + } + styles.put(key, style); + } + } + + /** + * 创建单元格 + */ + public Cell createHeadCell(Excel attr, Row row, int column) + { + // 创建列 + Cell cell = row.createCell(column); + // 写入列信息 + cell.setCellValue(attr.name()); + setDataValidation(attr, row, column); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + if (isSubList()) + { + // 填充默认样式,防止合并单元格样式失效 + sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType()))); + if (attr.needMerge()) + { + sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column)); + } + } + return cell; + } + + /** + * 设置单元格信息 + * + * @param value 单元格值 + * @param attr 注解相关 + * @param cell 单元格信息 + */ + public void setCellVo(Object value, Excel attr, Cell cell) + { + if (ColumnType.STRING == attr.cellType() || ColumnType.TEXT == attr.cellType()) + { + String cellValue = Convert.toStr(value); + // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。 + if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) + { + cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0"); + } + if (value instanceof Collection && StringUtils.equals("[]", cellValue)) + { + cellValue = StringUtils.EMPTY; + } + cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix()); + } + else if (ColumnType.NUMERIC == attr.cellType()) + { + if (StringUtils.isNotNull(value)) + { + cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value)); + } + } + else if (ColumnType.IMAGE == attr.cellType()) + { + ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1); + String propertyValue = Convert.toStr(value); + if (StringUtils.isNotEmpty(propertyValue)) + { + List imagePaths = StringUtils.str2List(propertyValue, SEPARATOR); + for (String imagePath : imagePaths) + { + byte[] data = ImageUtils.getImage(imagePath); + getDrawingPatriarch(cell.getSheet()).createPicture(anchor, cell.getSheet().getWorkbook().addPicture(data, getImageType(data))); + } + } + } + } + + /** + * 获取画布 + */ + public static Drawing getDrawingPatriarch(Sheet sheet) + { + if (sheet.getDrawingPatriarch() == null) + { + sheet.createDrawingPatriarch(); + } + return sheet.getDrawingPatriarch(); + } + + /** + * 获取图片类型,设置图片插入类型 + */ + public int getImageType(byte[] value) + { + String type = FileTypeUtils.getFileExtendName(value); + if ("JPG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_JPEG; + } + else if ("PNG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_PNG; + } + return Workbook.PICTURE_TYPE_JPEG; + } + + /** + * 创建表格样式 + */ + public void setDataValidation(Excel attr, Row row, int column) + { + if (attr.name().indexOf("注:") >= 0) + { + sheet.setColumnWidth(column, 6000); + } + else + { + // 设置列宽 + sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256)); + } + if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0 || attr.comboReadDict()) + { + String[] comboArray = attr.combo(); + if (attr.comboReadDict()) + { + if (!sysDictMap.containsKey("combo_" + attr.dictType())) + { + String labels = DictUtils.getDictLabels(attr.dictType()); + sysDictMap.put("combo_" + attr.dictType(), labels); + } + String val = sysDictMap.get("combo_" + attr.dictType()); + comboArray = StringUtils.split(val, DictUtils.SEPARATOR); + } + if (comboArray.length > 15 || StringUtils.join(comboArray).length() > 255) + { + // 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到 + setXSSFValidationWithHidden(sheet, comboArray, attr.prompt(), 1, 100, column, column); + } + else + { + // 提示信息或只能选择不能输入的列内容. + setPromptOrValidation(sheet, comboArray, attr.prompt(), 1, 100, column, column); + } + } + } + + /** + * 添加单元格 + */ + public Cell addCell(Excel attr, Row row, T vo, Field field, int column) + { + Cell cell = null; + try + { + // 设置行高 + row.setHeight(maxHeight); + // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列. + if (attr.isExport()) + { + // 创建cell + cell = row.createCell(column); + if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) + { + if (subMergedLastRowNum >= subMergedFirstRowNum) + { + sheet.addMergedRegion(new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column)); + } + } + cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType()))); + + // 用于读取对象中的属性 + Object value = getTargetValue(vo, field, attr); + String dateFormat = attr.dateFormat(); + String readConverterExp = attr.readConverterExp(); + String separator = attr.separator(); + String dictType = attr.dictType(); + if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) + { + cell.getCellStyle().setDataFormat(this.wb.getCreationHelper().createDataFormat().getFormat(dateFormat)); + cell.setCellValue(parseDateToStr(dateFormat, value)); + } + else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) + { + cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator)); + } + else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) + { + if (!sysDictMap.containsKey(dictType + value)) + { + String lable = convertDictByExp(Convert.toStr(value), dictType, separator); + sysDictMap.put(dictType + value, lable); + } + cell.setCellValue(sysDictMap.get(dictType + value)); + } + else if (value instanceof BigDecimal && -1 != attr.scale()) + { + cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell)); + } + else + { + // 设置列类型 + setCellVo(value, attr, cell); + } + addStatisticsData(column, Convert.toStr(value), attr); + } + } + catch (Exception e) + { + log.error("导出Excel失败{}", e); + } + return cell; + } + + /** + * 设置 POI XSSFSheet 单元格提示或选择框 + * + * @param sheet 表单 + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, + int firstCol, int endCol) + { + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1"); + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + sheet.addValidationData(dataValidation); + } + + /** + * 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框). + * + * @param sheet 要设置的sheet. + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) + { + String hideSheetName = "combo_" + firstCol + "_" + endCol; + Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 + for (int i = 0; i < textlist.length; i++) + { + hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); + } + // 创建名称,可被其他单元格引用 + Name name = wb.createName(); + name.setNameName(hideSheetName + "_data"); + name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); + DataValidationHelper helper = sheet.getDataValidationHelper(); + // 加载下拉列表内容 + DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data"); + // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + // 数据有效性对象 + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + + sheet.addValidationData(dataValidation); + // 设置hiddenSheet隐藏 + wb.setSheetHidden(wb.getSheetIndex(hideSheet), true); + } + + /** + * 解析导出值 0=男,1=女,2=未知 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String convertByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(SEPARATOR); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[0].equals(value)) + { + propertyString.append(itemArray[1] + separator); + break; + } + } + } + else + { + if (itemArray[0].equals(propertyValue)) + { + return itemArray[1]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 反向解析值 男=0,女=1,未知=2 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String reverseByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(SEPARATOR); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[1].equals(value)) + { + propertyString.append(itemArray[0] + separator); + break; + } + } + } + else + { + if (itemArray[1].equals(propertyValue)) + { + return itemArray[0]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 解析字典值 + * + * @param dictValue 字典值 + * @param dictType 字典类型 + * @param separator 分隔符 + * @return 字典标签 + */ + public static String convertDictByExp(String dictValue, String dictType, String separator) + { + return DictUtils.getDictLabel(dictType, dictValue, separator); + } + + /** + * 反向解析值字典值 + * + * @param dictLabel 字典标签 + * @param dictType 字典类型 + * @param separator 分隔符 + * @return 字典值 + */ + public static String reverseDictByExp(String dictLabel, String dictType, String separator) + { + return DictUtils.getDictValue(dictType, dictLabel, separator); + } + + /** + * 数据处理器 + * + * @param value 数据值 + * @param excel 数据注解 + * @return + */ + public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell) + { + try + { + Object instance = excel.handler().getDeclaredConstructor().newInstance(); + Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class }); + value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb); + } + catch (Exception e) + { + log.error("不能格式化数据 " + excel.handler(), e.getMessage()); + } + return Convert.toStr(value); + } + + /** + * 合计统计信息 + */ + private void addStatisticsData(Integer index, String text, Excel entity) + { + if (entity != null && entity.isStatistics()) + { + Double temp = 0D; + if (!statistics.containsKey(index)) + { + statistics.put(index, temp); + } + try + { + temp = Double.valueOf(text); + } + catch (NumberFormatException e) + { + } + statistics.put(index, statistics.get(index) + temp); + } + } + + /** + * 创建统计行 + */ + public void addStatisticsRow() + { + if (statistics.size() > 0) + { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + Set keys = statistics.keySet(); + Cell cell = row.createCell(0); + cell.setCellStyle(styles.get("total")); + cell.setCellValue("合计"); + + for (Integer key : keys) + { + cell = row.createCell(key); + cell.setCellStyle(styles.get("total")); + cell.setCellValue(statistics.get(key)); + } + statistics.clear(); + } + } + + /** + * 编码文件名 + */ + public String encodingFilename(String filename) + { + filename = UUID.randomUUID() + "_" + filename + ".xlsx"; + return filename; + } + + /** + * 获取下载路径 + * + * @param filename 文件名称 + */ + public String getAbsoluteFile(String filename) + { + String downloadPath = RuoYiConfig.getDownloadPath() + filename; + File desc = new File(downloadPath); + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + return downloadPath; + } + + /** + * 获取bean中的属性值 + * + * @param vo 实体对象 + * @param field 字段 + * @param excel 注解 + * @return 最终的属性值 + * @throws Exception + */ + private Object getTargetValue(T vo, Field field, Excel excel) throws Exception + { + field.setAccessible(true); + Object o = field.get(vo); + if (StringUtils.isNotEmpty(excel.targetAttr())) + { + String target = excel.targetAttr(); + if (target.contains(".")) + { + String[] targets = target.split("[.]"); + for (String name : targets) + { + o = getValue(o, name); + } + } + else + { + o = getValue(o, target); + } + } + return o; + } + + /** + * 以类的属性的get方法方法形式获取值 + * + * @param o + * @param name + * @return value + * @throws Exception + */ + private Object getValue(Object o, String name) throws Exception + { + if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) + { + Class clazz = o.getClass(); + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + o = field.get(o); + } + return o; + } + + /** + * 得到所有定义字段 + */ + private void createExcelField() + { + this.fields = getFields(); + this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList()); + this.maxHeight = getRowHeight(); + } + + /** + * 获取字段注解信息 + */ + public List getFields() + { + List fields = new ArrayList(); + List tempFields = new ArrayList<>(); + tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); + tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); + if (StringUtils.isNotEmpty(includeFields)) + { + for (Field field : tempFields) + { + if (ArrayUtils.contains(this.includeFields, field.getName()) || field.isAnnotationPresent(Excels.class)) + { + addField(fields, field); + } + } + } + else if (StringUtils.isNotEmpty(excludeFields)) + { + for (Field field : tempFields) + { + if (!ArrayUtils.contains(this.excludeFields, field.getName())) + { + addField(fields, field); + } + } + } + else + { + for (Field field : tempFields) + { + addField(fields, field); + } + } + return fields; + } + + /** + * 添加字段信息 + */ + public void addField(List fields, Field field) + { + // 单注解 + if (field.isAnnotationPresent(Excel.class)) + { + Excel attr = field.getAnnotation(Excel.class); + if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) + { + fields.add(new Object[] { field, attr }); + } + if (Collection.class.isAssignableFrom(field.getType())) + { + subMethod = getSubMethod(field.getName(), clazz); + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); + } + } + + // 多注解 + if (field.isAnnotationPresent(Excels.class)) + { + Excels attrs = field.getAnnotation(Excels.class); + Excel[] excels = attrs.value(); + for (Excel attr : excels) + { + if (StringUtils.isNotEmpty(includeFields)) + { + if (ArrayUtils.contains(this.includeFields, field.getName() + "." + attr.targetAttr()) + && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) + { + fields.add(new Object[] { field, attr }); + } + } + else + { + if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr()) + && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) + { + fields.add(new Object[] { field, attr }); + } + } + } + } + } + + /** + * 根据注解获取最大行高 + */ + public short getRowHeight() + { + double maxHeight = 0; + for (Object[] os : this.fields) + { + Excel excel = (Excel) os[1]; + maxHeight = Math.max(maxHeight, excel.height()); + } + return (short) (maxHeight * 20); + } + + /** + * 创建一个工作簿 + */ + public void createWorkbook() + { + this.wb = new SXSSFWorkbook(500); + this.sheet = wb.createSheet(); + wb.setSheetName(0, sheetName); + this.styles = createStyles(wb); + } + + /** + * 创建工作表 + * + * @param sheetNo sheet数量 + * @param index 序号 + */ + public void createSheet(int sheetNo, int index) + { + // 设置工作表的名称. + if (sheetNo > 1 && index > 0) + { + this.sheet = wb.createSheet(); + this.createTitle(); + wb.setSheetName(index, sheetName + index); + } + } + + /** + * 获取单元格值 + * + * @param row 获取的行 + * @param column 获取单元格列号 + * @return 单元格值 + */ + public Object getCellValue(Row row, int column) + { + if (row == null) + { + return row; + } + Object val = ""; + try + { + Cell cell = row.getCell(column); + if (StringUtils.isNotNull(cell)) + { + if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) + { + val = cell.getNumericCellValue(); + if (DateUtil.isCellDateFormatted(cell)) + { + val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换 + } + else + { + if ((Double) val % 1 != 0) + { + val = new BigDecimal(val.toString()); + } + else + { + val = new DecimalFormat("0").format(val); + } + } + } + else if (cell.getCellType() == CellType.STRING) + { + val = cell.getStringCellValue(); + } + else if (cell.getCellType() == CellType.BOOLEAN) + { + val = cell.getBooleanCellValue(); + } + else if (cell.getCellType() == CellType.ERROR) + { + val = cell.getErrorCellValue(); + } + + } + } + catch (Exception e) + { + return val; + } + return val; + } + + /** + * 判断是否是空行 + * + * @param row 判断的行 + * @return + */ + private boolean isRowEmpty(Row row) + { + if (row == null) + { + return true; + } + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) + { + Cell cell = row.getCell(i); + if (cell != null && cell.getCellType() != CellType.BLANK) + { + return false; + } + } + return true; + } + + /** + * 获取Excel2003图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map> getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) + { + Map> sheetIndexPicMap = new HashMap<>(); + List pictures = workbook.getAllPictures(); + if (!pictures.isEmpty() && sheet.getDrawingPatriarch() != null) + { + for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) + { + if (shape instanceof HSSFPicture) + { + HSSFPicture pic = (HSSFPicture) shape; + HSSFClientAnchor anchor = (HSSFClientAnchor) pic.getAnchor(); + String picIndex = anchor.getRow1() + "_" + anchor.getCol1(); + sheetIndexPicMap.computeIfAbsent(picIndex, k -> new ArrayList<>()).add(pic.getPictureData()); + } + } + } + return sheetIndexPicMap; + } + + /** + * 获取Excel2007图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map> getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) + { + Map> sheetIndexPicMap = new HashMap<>(); + for (POIXMLDocumentPart dr : sheet.getRelations()) + { + if (dr instanceof XSSFDrawing) + { + XSSFDrawing drawing = (XSSFDrawing) dr; + for (XSSFShape shape : drawing.getShapes()) + { + if (shape instanceof XSSFPicture) + { + XSSFPicture pic = (XSSFPicture) shape; + XSSFClientAnchor anchor = pic.getPreferredSize(); + CTMarker ctMarker = anchor.getFrom(); + String picIndex = ctMarker.getRow() + "_" + ctMarker.getCol(); + sheetIndexPicMap.computeIfAbsent(picIndex, k -> new ArrayList<>()).add(pic.getPictureData()); + } + } + } + } + return sheetIndexPicMap; + } + + /** + * 格式化不同类型的日期对象 + * + * @param dateFormat 日期格式 + * @param val 被格式化的日期对象 + * @return 格式化后的日期字符 + */ + public String parseDateToStr(String dateFormat, Object val) + { + if (val == null) + { + return ""; + } + String str; + if (val instanceof Date) + { + str = DateUtils.parseDateToStr(dateFormat, (Date) val); + } + else if (val instanceof LocalDateTime) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val)); + } + else if (val instanceof LocalDate) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val)); + } + else + { + str = val.toString(); + } + return str; + } + + /** + * 是否有对象的子列表 + */ + public boolean isSubList() + { + return StringUtils.isNotNull(subFields) && subFields.size() > 0; + } + + /** + * 是否有对象的子列表,集合不为空 + */ + public boolean isSubListValue(T vo) + { + return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0; + } + + /** + * 获取集合的值 + */ + public Collection getListCellValue(Object obj) + { + Object value; + try + { + value = subMethod.invoke(obj, new Object[] {}); + } + catch (Exception e) + { + return new ArrayList(); + } + return (Collection) value; + } + + /** + * 获取对象的子列表方法 + * + * @param name 名称 + * @param pojoClass 类对象 + * @return 子列表方法 + */ + public Method getSubMethod(String name, Class pojoClass) + { + StringBuffer getMethodName = new StringBuffer("get"); + getMethodName.append(name.substring(0, 1).toUpperCase()); + getMethodName.append(name.substring(1)); + Method method = null; + try + { + method = pojoClass.getMethod(getMethodName.toString(), new Class[] {}); + } + catch (Exception e) + { + log.error("获取对象异常{}", e.getMessage()); + } + return method; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java new file mode 100644 index 0000000..ce6d380 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java @@ -0,0 +1,410 @@ +package com.ruoyi.common.utils.reflect; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Date; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.apache.poi.ss.usermodel.DateUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.DateUtils; + +/** + * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * + * @author ruoyi + */ +@SuppressWarnings("rawtypes") +public class ReflectUtils +{ + private static final String SETTER_PREFIX = "set"; + + private static final String GETTER_PREFIX = "get"; + + private static final String CGLIB_CLASS_SEPARATOR = "$$"; + + private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); + + /** + * 调用Getter方法. + * 支持多级,如:对象名.对象名.方法 + */ + @SuppressWarnings("unchecked") + public static E invokeGetter(Object obj, String propertyName) + { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + return (E) object; + } + + /** + * 调用Setter方法, 仅匹配方法名。 + * 支持多级,如:对象名.对象名.方法 + */ + public static void invokeSetter(Object obj, String propertyName, E value) + { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i = 0; i < names.length; i++) + { + if (i < names.length - 1) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + else + { + String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + invokeMethodByName(object, setterMethodName, new Object[] { value }); + } + } + } + + /** + * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. + */ + @SuppressWarnings("unchecked") + public static E getFieldValue(final Object obj, final String fieldName) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return null; + } + E result = null; + try + { + result = (E) field.get(obj); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常{}", e.getMessage()); + } + return result; + } + + /** + * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. + */ + public static void setFieldValue(final Object obj, final String fieldName, final E value) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return; + } + try + { + field.set(obj, value); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常: {}", e.getMessage()); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符. + * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. + * 同时匹配方法名+参数类型, + */ + @SuppressWarnings("unchecked") + public static E invokeMethod(final Object obj, final String methodName, final Class[] parameterTypes, + final Object[] args) + { + if (obj == null || methodName == null) + { + return null; + } + Method method = getAccessibleMethod(obj, methodName, parameterTypes); + if (method == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符, + * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. + * 只匹配函数名,如果有多个同名函数调用第一个。 + */ + @SuppressWarnings("unchecked") + public static E invokeMethodByName(final Object obj, final String methodName, final Object[] args) + { + Method method = getAccessibleMethodByName(obj, methodName, args.length); + if (method == null) + { + // 如果为空不报错,直接返回空。 + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + // 类型转换(将参数数据类型转换为目标方法参数类型) + Class[] cs = method.getParameterTypes(); + for (int i = 0; i < cs.length; i++) + { + if (args[i] != null && !args[i].getClass().equals(cs[i])) + { + if (cs[i] == String.class) + { + args[i] = Convert.toStr(args[i]); + if (StringUtils.endsWith((String) args[i], ".0")) + { + args[i] = StringUtils.substringBefore((String) args[i], ".0"); + } + } + else if (cs[i] == Integer.class) + { + args[i] = Convert.toInt(args[i]); + } + else if (cs[i] == Long.class) + { + args[i] = Convert.toLong(args[i]); + } + else if (cs[i] == Double.class) + { + args[i] = Convert.toDouble(args[i]); + } + else if (cs[i] == Float.class) + { + args[i] = Convert.toFloat(args[i]); + } + else if (cs[i] == Date.class) + { + if (args[i] instanceof String) + { + args[i] = DateUtils.parseDate(args[i]); + } + else + { + args[i] = DateUtil.getJavaDate((Double) args[i]); + } + } + else if (cs[i] == boolean.class || cs[i] == Boolean.class) + { + args[i] = Convert.toBool(args[i]); + } + } + } + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + */ + public static Field getAccessibleField(final Object obj, final String fieldName) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(fieldName, "fieldName can't be blank"); + for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) + { + try + { + Field field = superClass.getDeclaredField(fieldName); + makeAccessible(field); + return field; + } + catch (NoSuchFieldException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 匹配函数名+参数类型。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethod(final Object obj, final String methodName, + final Class... parameterTypes) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + try + { + Method method = searchType.getDeclaredMethod(methodName, parameterTypes); + makeAccessible(method); + return method; + } + catch (NoSuchMethodException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 只匹配函数名。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + Method[] methods = searchType.getDeclaredMethods(); + for (Method method : methods) + { + if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) + { + makeAccessible(method); + return method; + } + } + } + return null; + } + + /** + * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Method method) + { + if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) + && !method.canAccess(null)) + { + method.setAccessible(true); + } + } + + /** + * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Field field) + { + if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) + || Modifier.isFinal(field.getModifiers())) && !field.canAccess(null)) + { + field.setAccessible(true); + } + } + + /** + * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 + * 如无法找到, 返回Object.class. + */ + @SuppressWarnings("unchecked") + public static Class getClassGenricType(final Class clazz) + { + return getClassGenricType(clazz, 0); + } + + /** + * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. + * 如无法找到, 返回Object.class. + */ + public static Class getClassGenricType(final Class clazz, final int index) + { + Type genType = clazz.getGenericSuperclass(); + + if (!(genType instanceof ParameterizedType)) + { + logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType"); + return Object.class; + } + + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + + if (index >= params.length || index < 0) + { + logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + + params.length); + return Object.class; + } + if (!(params[index] instanceof Class)) + { + logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); + return Object.class; + } + + return (Class) params[index]; + } + + public static Class getUserClass(Object instance) + { + if (instance == null) + { + throw new RuntimeException("Instance must not be null"); + } + Class clazz = instance.getClass(); + if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) + { + Class superClass = clazz.getSuperclass(); + if (superClass != null && !Object.class.equals(superClass)) + { + return superClass; + } + } + return clazz; + + } + + /** + * 将反射时的checked exception转换为unchecked exception. + */ + public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e) + { + if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException + || e instanceof NoSuchMethodException) + { + return new IllegalArgumentException(msg, e); + } + else if (e instanceof InvocationTargetException) + { + return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException()); + } + return new RuntimeException(msg, e); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java new file mode 100644 index 0000000..ca1cd92 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java @@ -0,0 +1,291 @@ +package com.ruoyi.common.utils.sign; + +/** + * Base64工具类 + * + * @author ruoyi + */ +public final class Base64 +{ + static private final int BASELENGTH = 128; + static private final int LOOKUPLENGTH = 64; + static private final int TWENTYFOURBITGROUP = 24; + static private final int EIGHTBIT = 8; + static private final int SIXTEENBIT = 16; + static private final int FOURBYTE = 4; + static private final int SIGN = -128; + static private final char PAD = '='; + static final private byte[] base64Alphabet = new byte[BASELENGTH]; + static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; + + static + { + for (int i = 0; i < BASELENGTH; ++i) + { + base64Alphabet[i] = -1; + } + for (int i = 'Z'; i >= 'A'; i--) + { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) + { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + + for (int i = '9'; i >= '0'; i--) + { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) + { + lookUpBase64Alphabet[i] = (char) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('0' + j); + } + lookUpBase64Alphabet[62] = (char) '+'; + lookUpBase64Alphabet[63] = (char) '/'; + } + + private static boolean isWhiteSpace(char octect) + { + return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); + } + + private static boolean isPad(char octect) + { + return (octect == PAD); + } + + private static boolean isData(char octect) + { + return (octect < BASELENGTH && base64Alphabet[octect] != -1); + } + + /** + * Encodes hex octects into Base64 + * + * @param binaryData Array containing binaryData + * @return Encoded Base64 array + */ + public static String encode(byte[] binaryData) + { + if (binaryData == null) + { + return null; + } + + int lengthDataBits = binaryData.length * EIGHTBIT; + if (lengthDataBits == 0) + { + return ""; + } + + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; + char encodedData[] = null; + + encodedData = new char[numberQuartet * 4]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + + for (int i = 0; i < numberTriplets; i++) + { + b1 = binaryData[dataIndex++]; + b2 = binaryData[dataIndex++]; + b3 = binaryData[dataIndex++]; + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; + } + + // form integral number of 6-bit groups + if (fewerThan24bits == EIGHTBIT) + { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex++] = PAD; + encodedData[encodedIndex++] = PAD; + } + else if (fewerThan24bits == SIXTEENBIT) + { + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex++] = PAD; + } + return new String(encodedData); + } + + /** + * Decodes Base64 data into octects + * + * @param encoded string containing Base64 data + * @return Array containind decoded data. + */ + public static byte[] decode(String encoded) + { + if (encoded == null) + { + return null; + } + + char[] base64Data = encoded.toCharArray(); + // remove white spaces + int len = removeWhiteSpace(base64Data); + + if (len % FOURBYTE != 0) + { + return null;// should be divisible by four + } + + int numberQuadruple = (len / FOURBYTE); + + if (numberQuadruple == 0) + { + return new byte[0]; + } + + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; + char d1 = 0, d2 = 0, d3 = 0, d4 = 0; + + int i = 0; + int encodedIndex = 0; + int dataIndex = 0; + decodedData = new byte[(numberQuadruple) * 3]; + + for (; i < numberQuadruple - 1; i++) + { + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) + || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) + { + return null; + } // if found "no data" just return null + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + } + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) + { + return null;// if found "no data" just return null + } + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + + d3 = base64Data[dataIndex++]; + d4 = base64Data[dataIndex++]; + if (!isData((d3)) || !isData((d4))) + {// Check if they are PAD characters + if (isPad(d3) && isPad(d4)) + { + if ((b2 & 0xf) != 0)// last 4 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 1]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + return tmp; + } + else if (!isPad(d3) && isPad(d4)) + { + b3 = base64Alphabet[d3]; + if ((b3 & 0x3) != 0)// last 2 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 2]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + return tmp; + } + else + { + return null; + } + } + else + { // No PAD e.g 3cQl + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + + } + return decodedData; + } + + /** + * remove WhiteSpace from MIME containing encoded Base64 data. + * + * @param data the byte array of base64 data (with WS) + * @return the new length + */ + private static int removeWhiteSpace(char[] data) + { + if (data == null) + { + return 0; + } + + // count characters that's not whitespace + int newSize = 0; + int len = data.length; + for (int i = 0; i < len; i++) + { + if (!isWhiteSpace(data[i])) + { + data[newSize++] = data[i]; + } + } + return newSize; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java new file mode 100644 index 0000000..a31b4a4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java @@ -0,0 +1,166 @@ +package com.ruoyi.common.utils.sign; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.apache.commons.codec.digest.DigestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +/** + * Md5加密方法 + * + * @author ruoyi + */ +public class Md5Utils { + private static final Logger log = LoggerFactory.getLogger(Md5Utils.class); + + private static final ThreadLocal md5DigestThreadLocal = ThreadLocal.withInitial(() -> { + try { + return MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("MD5 algorithm not found", e); + } + }); + + private static byte[] md5(String s) { + MessageDigest algorithm; + try { + algorithm = MessageDigest.getInstance("MD5"); + algorithm.reset(); + algorithm.update(s.getBytes("UTF-8")); + byte[] messageDigest = algorithm.digest(); + return messageDigest; + } catch (Exception e) { + log.error("MD5 Error...", e); + } + return null; + } + + private static final String toHex(byte hash[]) { + if (hash == null) { + return null; + } + StringBuffer buf = new StringBuffer(hash.length * 2); + int i; + + for (i = 0; i < hash.length; i++) { + if ((hash[i] & 0xff) < 0x10) { + buf.append("0"); + } + buf.append(Long.toString(hash[i] & 0xff, 16)); + } + return buf.toString(); + } + + public static String hash(String s) { + try { + return new String(toHex(md5(s)).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + } catch (Exception e) { + log.error("not supported charset...{}", e); + return s; + } + } + + public static String encryptMd5(String string) throws UnsupportedEncodingException { + return encryptMd5(string, "UTF-8"); + } + + public static String encryptMd5(String string, String charSet) throws UnsupportedEncodingException { + return DigestUtils.md5Hex(string.getBytes(charSet)); + } + + /** + * 计算文件的md5 + * @param file 文件,可以是 MultipartFile 或 File + * @return + */ + public static String getMd5(Object file) { + try (InputStream inputStream = getInputStream(file)) { + long fileSize = getFileSize(file); + // 10MB作为分界点 + if (fileSize < 10 * 1024 * 1024) { + return getMd5ForSmallFile(inputStream); + } else { + return getMd5ForLargeFile(inputStream); + } + } catch (Exception e) { + log.error(e.getMessage()); + } + return null; + } + + private static InputStream getInputStream(Object file) throws IOException { + if (file instanceof MultipartFile) { + return ((MultipartFile) file).getInputStream(); + } else if (file instanceof File) { + return new FileInputStream((File) file); + } + throw new IllegalArgumentException("Unsupported file type"); + } + + private static long getFileSize(Object file) throws IOException { + if (file instanceof MultipartFile) { + return ((MultipartFile) file).getSize(); + } else if (file instanceof File) { + return ((File) file).length(); + } + throw new IllegalArgumentException("Unsupported file type"); + } + + /** + * 计算小文件的md5 + * + * @param inputStream 文件输入流 + * @return + */ + private static String getMd5ForSmallFile(InputStream inputStream) { + try { + byte[] uploadBytes = inputStream.readAllBytes(); + MessageDigest md5 = md5DigestThreadLocal.get(); + byte[] digest = md5.digest(uploadBytes); + String md5Hex = new BigInteger(1, digest).toString(16); + while (md5Hex.length() < 32) { + md5Hex = "0" + md5Hex; + } + return md5Hex; + } catch (Exception e) { + log.error(e.getMessage()); + } + return null; + } + + /** + * 计算大文件的md5 + * + * @param inputStream 文件输入流 + * @return + */ + private static String getMd5ForLargeFile(InputStream inputStream) { + try (InputStream is = inputStream) { + MessageDigest md = md5DigestThreadLocal.get(); + byte[] buffer = new byte[81920]; + int read; + while ((read = is.read(buffer)) != -1) { + md.update(buffer, 0, read); + } + byte[] digest = md.digest(); + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } catch (Exception e) { + log.error(e.getMessage()); + } + return null; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java new file mode 100644 index 0000000..f290ec3 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java @@ -0,0 +1,158 @@ +package com.ruoyi.common.utils.spring; + +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; +import com.ruoyi.common.utils.StringUtils; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author ruoyi + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware +{ + /** Spring应用上下文环境 */ + private static ConfigurableListableBeanFactory beanFactory; + + private static ApplicationContext applicationContext; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException + { + SpringUtils.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + SpringUtils.applicationContext = applicationContext; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws org.springframework.beans.BeansException + * + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws org.springframework.beans.BeansException + * + */ + public static T getBean(Class clz) throws BeansException + { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) + { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) + { + return (T) AopContext.currentProxy(); + } + + /** + * 获取当前的环境配置,无配置返回null + * + * @return 当前的环境配置 + */ + public static String[] getActiveProfiles() + { + return applicationContext.getEnvironment().getActiveProfiles(); + } + + /** + * 获取当前的环境配置,当有多个环境配置时,只获取第一个 + * + * @return 当前的环境配置 + */ + public static String getActiveProfile() + { + final String[] activeProfiles = getActiveProfiles(); + return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; + } + + /** + * 获取配置文件中的值 + * + * @param key 配置文件的key + * @return 当前的配置文件的值 + * + */ + public static String getRequiredProperty(String key) + { + return applicationContext.getEnvironment().getRequiredProperty(key); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java new file mode 100644 index 0000000..31eb813 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java @@ -0,0 +1,66 @@ +package com.ruoyi.common.utils.sql; + +import java.io.StringReader; + +import com.ruoyi.common.exception.UtilException; +import com.ruoyi.common.utils.StringUtils; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.statement.Statement; + +/** + * sql操作工具类 + * + * @author ruoyi + */ +public class SqlUtil { + + private static final CCJSqlParserManager parserManager = new CCJSqlParserManager(); + + /** + * 定义常用的 sql关键字 + */ + public static String SQL_REGEX = "and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()"; + + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; + + /** + * 检查字符,防止注入绕过 + */ + public static String escapeOrderBySql(String value) { + if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) { + throw new UtilException("参数不符合规范,不能进行查询"); + } + return value; + } + + /** + * 验证 order by 语法是否符合规范 + */ + public static boolean isValidOrderBySql(String value) { + return value.matches(SQL_PATTERN); + } + + /** + * SQL关键字检查 + */ + public static void filterKeyword(String value) { + if (StringUtils.isEmpty(value)) { + return; + } + String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); + for (String sqlKeyword : sqlKeywords) { + if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) { + throw new UtilException("参数存在SQL注入风险"); + } + } + } + + public static Statement parseSql(String sql) throws JSQLParserException { + return parserManager.parse(new StringReader(sql)); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java new file mode 100644 index 0000000..2c84427 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java @@ -0,0 +1,49 @@ +package com.ruoyi.common.utils.uuid; + +/** + * ID生成器工具类 + * + * @author ruoyi + */ +public class IdUtils +{ + /** + * 获取随机UUID + * + * @return 随机UUID + */ + public static String randomUUID() + { + return UUID.randomUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线 + * + * @return 简化的UUID,去掉了横线 + */ + public static String simpleUUID() + { + return UUID.randomUUID().toString(true); + } + + /** + * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 随机UUID + */ + public static String fastUUID() + { + return UUID.fastUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 简化的UUID,去掉了横线 + */ + public static String fastSimpleUUID() + { + return UUID.fastUUID().toString(true); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java new file mode 100644 index 0000000..bf99611 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java @@ -0,0 +1,86 @@ +package com.ruoyi.common.utils.uuid; + +import java.util.concurrent.atomic.AtomicInteger; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * @author ruoyi 序列生成类 + */ +public class Seq +{ + // 通用序列类型 + public static final String commSeqType = "COMMON"; + + // 上传序列类型 + public static final String uploadSeqType = "UPLOAD"; + + // 通用接口序列数 + private static AtomicInteger commSeq = new AtomicInteger(1); + + // 上传接口序列数 + private static AtomicInteger uploadSeq = new AtomicInteger(1); + + // 机器标识 + private static final String machineCode = "A"; + + /** + * 获取通用序列号 + * + * @return 序列值 + */ + public static String getId() + { + return getId(commSeqType); + } + + /** + * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串 + * + * @return 序列值 + */ + public static String getId(String type) + { + AtomicInteger atomicInt = commSeq; + if (uploadSeqType.equals(type)) + { + atomicInt = uploadSeq; + } + return getId(atomicInt, 3); + } + + /** + * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串 + * + * @param atomicInt 序列数 + * @param length 数值长度 + * @return 序列值 + */ + public static String getId(AtomicInteger atomicInt, int length) + { + String result = DateUtils.dateTimeNow(); + result += machineCode; + result += getSeq(atomicInt, length); + return result; + } + + /** + * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数 + * + * @return 序列值 + */ + private synchronized static String getSeq(AtomicInteger atomicInt, int length) + { + // 先取值再+1 + int value = atomicInt.getAndIncrement(); + + // 如果更新后值>=10 的 (length)幂次方则重置为1 + int maxSeq = (int) Math.pow(10, length); + if (atomicInt.get() >= maxSeq) + { + atomicInt.set(1); + } + // 转字符串,用0左补齐 + return StringUtils.padl(value, length); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java new file mode 100644 index 0000000..dfda46c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java @@ -0,0 +1,484 @@ +package com.ruoyi.common.utils.uuid; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import com.ruoyi.common.exception.UtilException; + +/** + * 提供通用唯一识别码(universally unique identifier)(UUID)实现 + * + * @author ruoyi + */ +public final class UUID implements java.io.Serializable, Comparable +{ + private static final long serialVersionUID = -1185015143654744140L; + + /** + * SecureRandom 的单例 + * + */ + private static class Holder + { + static final SecureRandom numberGenerator = getSecureRandom(); + } + + /** 此UUID的最高64有效位 */ + private final long mostSigBits; + + /** 此UUID的最低64有效位 */ + private final long leastSigBits; + + /** + * 私有构造 + * + * @param data 数据 + */ + private UUID(byte[] data) + { + long msb = 0; + long lsb = 0; + assert data.length == 16 : "data must be 16 bytes in length"; + for (int i = 0; i < 8; i++) + { + msb = (msb << 8) | (data[i] & 0xff); + } + for (int i = 8; i < 16; i++) + { + lsb = (lsb << 8) | (data[i] & 0xff); + } + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + /** + * 使用指定的数据构造新的 UUID。 + * + * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 + * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 + */ + public UUID(long mostSigBits, long leastSigBits) + { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID fastUUID() + { + return randomUUID(false); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID() + { + return randomUUID(true); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID(boolean isSecure) + { + final Random ng = isSecure ? Holder.numberGenerator : getRandom(); + + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(randomBytes); + } + + /** + * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 + * + * @param name 用于构造 UUID 的字节数组。 + * + * @return 根据指定数组生成的 {@code UUID} + */ + public static UUID nameUUIDFromBytes(byte[] name) + { + MessageDigest md; + try + { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException nsae) + { + throw new InternalError("MD5 not supported"); + } + byte[] md5Bytes = md.digest(name); + md5Bytes[6] &= 0x0f; /* clear version */ + md5Bytes[6] |= 0x30; /* set to version 3 */ + md5Bytes[8] &= 0x3f; /* clear variant */ + md5Bytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(md5Bytes); + } + + /** + * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 + * + * @param name 指定 {@code UUID} 字符串 + * @return 具有指定值的 {@code UUID} + * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 + * + */ + public static UUID fromString(String name) + { + String[] components = name.split("-"); + if (components.length != 5) + { + throw new IllegalArgumentException("Invalid UUID string: " + name); + } + for (int i = 0; i < 5; i++) + { + components[i] = "0x" + components[i]; + } + + long mostSigBits = Long.decode(components[0]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[2]).longValue(); + + long leastSigBits = Long.decode(components[3]).longValue(); + leastSigBits <<= 48; + leastSigBits |= Long.decode(components[4]).longValue(); + + return new UUID(mostSigBits, leastSigBits); + } + + /** + * 返回此 UUID 的 128 位值中的最低有效 64 位。 + * + * @return 此 UUID 的 128 位值中的最低有效 64 位。 + */ + public long getLeastSignificantBits() + { + return leastSigBits; + } + + /** + * 返回此 UUID 的 128 位值中的最高有效 64 位。 + * + * @return 此 UUID 的 128 位值中最高有效 64 位。 + */ + public long getMostSignificantBits() + { + return mostSigBits; + } + + /** + * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 + *

+ * 版本号具有以下含意: + *

    + *
  • 1 基于时间的 UUID + *
  • 2 DCE 安全 UUID + *
  • 3 基于名称的 UUID + *
  • 4 随机生成的 UUID + *
+ * + * @return 此 {@code UUID} 的版本号 + */ + public int version() + { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

+ * 变体号具有以下含意: + *

    + *
  • 0 为 NCS 向后兼容保留 + *
  • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
  • 6 保留,微软向后兼容 + *
  • 7 保留供以后定义使用 + *
+ * + * @return 此 {@code UUID} 相关联的变体号 + */ + public int variant() + { + // This field is composed of a varying number of bits. + // 0 - - Reserved for NCS backward compatibility + // 1 0 - The IETF aka Leach-Salz variant (used by this class) + // 1 1 0 Reserved, Microsoft backward compatibility + // 1 1 1 Reserved for future definition. + return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); + } + + /** + * 与此 UUID 相关联的时间戳值。 + * + *

+ * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
+ * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

+ * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 + */ + public long timestamp() throws UnsupportedOperationException + { + checkTimeBase(); + return (mostSigBits & 0x0FFFL) << 48// + | ((mostSigBits >> 16) & 0x0FFFFL) << 32// + | mostSigBits >>> 32; + } + + /** + * 与此 UUID 相关联的时钟序列值。 + * + *

+ * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

+ * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 + * UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的时钟序列 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public int clockSequence() throws UnsupportedOperationException + { + checkTimeBase(); + return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); + } + + /** + * 与此 UUID 相关的节点值。 + * + *

+ * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

+ * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException + { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() + { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 + * @return 此{@code UUID} 的字符串表现形式 + */ + public String toString(boolean isSimple) + { + final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); + // time_low + builder.append(digits(mostSigBits >> 32, 8)); + if (!isSimple) + { + builder.append('-'); + } + // time_mid + builder.append(digits(mostSigBits >> 16, 4)); + if (!isSimple) + { + builder.append('-'); + } + // time_high_and_version + builder.append(digits(mostSigBits, 4)); + if (!isSimple) + { + builder.append('-'); + } + // variant_and_sequence + builder.append(digits(leastSigBits >> 48, 4)); + if (!isSimple) + { + builder.append('-'); + } + // node + builder.append(digits(leastSigBits, 12)); + + return builder.toString(); + } + + /** + * 返回此 UUID 的哈希码。 + * + * @return UUID 的哈希码值。 + */ + @Override + public int hashCode() + { + long hilo = mostSigBits ^ leastSigBits; + return ((int) (hilo >> 32)) ^ (int) hilo; + } + + /** + * 将此对象与指定对象比较。 + *

+ * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 + * + * @param obj 要与之比较的对象 + * + * @return 如果对象相同,则返回 {@code true};否则返回 {@code false} + */ + @Override + public boolean equals(Object obj) + { + if ((null == obj) || (obj.getClass() != UUID.class)) + { + return false; + } + UUID id = (UUID) obj; + return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + // Comparison Operations + + /** + * 将此 UUID 与指定的 UUID 比较。 + * + *

+ * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 + * + * @param val 与此 UUID 比较的 UUID + * + * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 + * + */ + @Override + public int compareTo(UUID val) + { + // The ordering is intentionally set up so that the UUIDs + // can simply be numerically compared as two numbers + return (this.mostSigBits < val.mostSigBits ? -1 : // + (this.mostSigBits > val.mostSigBits ? 1 : // + (this.leastSigBits < val.leastSigBits ? -1 : // + (this.leastSigBits > val.leastSigBits ? 1 : // + 0)))); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Private method start + /** + * 返回指定数字对应的hex值 + * + * @param val 值 + * @param digits 位 + * @return 值 + */ + private static String digits(long val, int digits) + { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * 检查是否为time-based版本UUID + */ + private void checkTimeBase() + { + if (version() != 1) + { + throw new UnsupportedOperationException("Not a time-based UUID"); + } + } + + /** + * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) + * + * @return {@link SecureRandom} + */ + public static SecureRandom getSecureRandom() + { + try + { + return SecureRandom.getInstance("SHA1PRNG"); + } + catch (NoSuchAlgorithmException e) + { + throw new UtilException(e); + } + } + + /** + * 获取随机数生成器对象
+ * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() + { + return ThreadLocalRandom.current(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java new file mode 100644 index 0000000..cb4b408 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java @@ -0,0 +1,27 @@ +package com.ruoyi.common.xss; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义xss校验注解 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) +@Constraint(validatedBy = { XssValidator.class }) +public @interface Xss +{ + String message() + + default "不允许任何脚本运行"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java new file mode 100644 index 0000000..cc282f3 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java @@ -0,0 +1,34 @@ +package com.ruoyi.common.xss; + +import com.ruoyi.common.utils.StringUtils; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 自定义xss校验注解实现 + * + * @author ruoyi + */ +public class XssValidator implements ConstraintValidator +{ + private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />"; + + @Override + public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) + { + if (StringUtils.isBlank(value)) + { + return true; + } + return !containsHtml(value); + } + + public static boolean containsHtml(String value) + { + Pattern pattern = Pattern.compile(HTML_PATTERN); + Matcher matcher = pattern.matcher(value); + return matcher.matches(); + } +} \ No newline at end of file diff --git a/ruoyi-framework/pom.xml b/ruoyi-framework/pom.xml new file mode 100644 index 0000000..408cfb8 --- /dev/null +++ b/ruoyi-framework/pom.xml @@ -0,0 +1,68 @@ + + + + ruoyi + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-framework + + + framework框架核心 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.alibaba + druid-spring-boot-3-starter + + + + javax.transaction + jta + + + + + pro.fessional + kaptcha + + + jakarta.servlet-api + jakarta.servlet + + + + + + + com.github.oshi + oshi-core + + + + + com.ruoyi.geekxd + ruoyi-system + + + + + diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java new file mode 100644 index 0000000..1c2c4a2 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java @@ -0,0 +1,186 @@ +package com.ruoyi.framework.aspectj; + +import java.util.ArrayList; +import java.util.List; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.annotation.DataScope; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.DataSourceManager; +import com.ruoyi.framework.security.context.PermissionContextHolder; + +/** + * 数据过滤处理 + * + * @author ruoyi + */ +@Aspect +@Component +@ConditionalOnProperty(prefix = "datascope", name = "type", havingValue = "default", matchIfMissing = true) +public class DataScopeAspect { + + @Autowired + DataSourceManager dataSourceManager; + /** + * 全部数据权限 + */ + public static final String DATA_SCOPE_ALL = "1"; + + /** + * 自定数据权限 + */ + public static final String DATA_SCOPE_CUSTOM = "2"; + + /** + * 部门数据权限 + */ + public static final String DATA_SCOPE_DEPT = "3"; + + /** + * 部门及以下数据权限 + */ + public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; + + /** + * 仅本人数据权限 + */ + public static final String DATA_SCOPE_SELF = "5"; + + /** + * 数据权限过滤关键字 + */ + public static final String DATA_SCOPE = "dataScope"; + + @Before("@annotation(controllerDataScope)") + public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable { + clearDataScope(point); + handleDataScope(point, controllerDataScope); + } + + protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNotNull(loginUser)) { + SysUser currentUser = loginUser.getUser(); + // 如果是超级管理员,则不过滤数据 + if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) { + String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), + PermissionContextHolder.getContext()); + dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), + controllerDataScope.userAlias(), permission); + } + } + } + + /** + * 数据范围过滤 + * + * @param joinPoint 切点 + * @param user 用户 + * @param deptAlias 部门别名 + * @param userAlias 用户别名 + * @param permission 权限字符 + */ + public void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, + String permission) { + StringBuilder sqlString = new StringBuilder(); + List conditions = new ArrayList(); + List scopeCustomIds = new ArrayList(); + user.getRoles().forEach(role -> { + if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) + && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) + && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) { + scopeCustomIds.add(Convert.toStr(role.getRoleId())); + } + }); + + for (SysRole role : user.getRoles()) { + String dataScope = role.getDataScope(); + if (conditions.contains(dataScope) || StringUtils.equals(role.getStatus(), UserConstants.ROLE_DISABLE)) { + continue; + } + if (StringUtils.isNotEmpty(permission) + && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) { + continue; + } + if (DATA_SCOPE_ALL.equals(dataScope)) { + sqlString = new StringBuilder(); + conditions.add(dataScope); + break; + } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) { + if (scopeCustomIds.size() > 1) { + // 多个自定数据权限使用in查询,避免多次拼接。 + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, + String.join(",", scopeCustomIds))); + } else { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, + role.getRoleId())); + } + } else if (DATA_SCOPE_DEPT.equals(dataScope)) { + sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); + } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) { + String databaseId = dataSourceManager.getCurrentDatabaseId(); + if (databaseId.equals("postgresql")) { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or array_position(string_to_array(ancestors , ','), CAST( {} AS TEXT)) IS NOT NULL )", + deptAlias, user.getDeptId(), user.getDeptId())); + } else if (databaseId.equals("openGauss")) { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or CAST( {} AS TEXT) = ANY(string_to_array(ancestors , ',')) )", + deptAlias, user.getDeptId(), user.getDeptId())); + } else { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", + deptAlias, user.getDeptId(), user.getDeptId())); + } + } else if (DATA_SCOPE_SELF.equals(dataScope)) { + if (StringUtils.isNotBlank(userAlias)) { + sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); + } else { + // 数据权限为仅本人且没有userAlias别名不查询任何数据 + sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); + } + } + conditions.add(dataScope); + } + + // 多角色情况下,所有角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据 + if (StringUtils.isEmpty(conditions)) { + sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); + } + + if (StringUtils.isNotBlank(sqlString.toString())) { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); + } + } + } + + /** + * 拼接权限sql前先清空params.dataScope参数防止注入 + */ + private void clearDataScope(final JoinPoint joinPoint) { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, ""); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java new file mode 100644 index 0000000..4b479e2 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java @@ -0,0 +1,65 @@ +package com.ruoyi.framework.aspectj; + +import java.util.Objects; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.annotation.DataSource; +import com.ruoyi.common.enums.DataSourceType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder; + +/** + * 多数据源处理 + * + * @author ruoyi + */ +@Aspect +@Order(1) +@Component +public class DataSourceAspect { + protected Logger logger = LoggerFactory.getLogger(getClass()); + + @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)" + + "|| @within(com.ruoyi.common.annotation.DataSource)") + public void dsPointCut() { + + } + + @Around("dsPointCut()") + public Object around(ProceedingJoinPoint point) throws Throwable { + DataSource dataSource = getDataSource(point); + String name = DataSourceType.MASTER.name(); + if (StringUtils.isNotNull(dataSource)) { + name = "".equals(dataSource.name()) ? dataSource.value().name() : dataSource.name(); + } + try { + DynamicDataSourceContextHolder.setDataSourceType(name); + return point.proceed(); + } finally { + // 销毁数据源 在执行方法之后 + DynamicDataSourceContextHolder.clearDataSourceType(); + } + } + + /** + * 获取需要切换的数据源 + */ + public DataSource getDataSource(ProceedingJoinPoint point) { + MethodSignature signature = (MethodSignature) point.getSignature(); + DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); + if (Objects.nonNull(dataSource)) { + return dataSource; + } + return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java new file mode 100644 index 0000000..7170ae2 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java @@ -0,0 +1,227 @@ +package com.ruoyi.framework.aspectj; + +import java.util.Collection; +import java.util.Map; + +import org.apache.commons.lang3.ArrayUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.NamedThreadLocal; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.enums.BusinessStatus; +import com.ruoyi.common.enums.HttpMethod; +import com.ruoyi.common.filter.PropertyPreExcludeFilter; +import com.ruoyi.common.utils.ExceptionUtil; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.system.domain.SysOperLog; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 操作日志记录处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class LogAspect { + private static final Logger log = LoggerFactory.getLogger(LogAspect.class); + + /** 排除敏感属性字段 */ + public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; + + /** 计算操作消耗时间 */ + private static final ThreadLocal TIME_THREADLOCAL = new NamedThreadLocal("Cost Time"); + + /** + * 处理请求前执行 + */ + @Before(value = "@annotation(controllerLog)") + public void boBefore(JoinPoint joinPoint, Log controllerLog) { + TIME_THREADLOCAL.set(System.currentTimeMillis()); + } + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) { + handleLog(joinPoint, controllerLog, null, jsonResult); + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) { + handleLog(joinPoint, controllerLog, e, null); + } + + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) { + try { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + + // *========数据库日志=========*// + SysOperLog operLog = new SysOperLog(); + operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); + // 请求的地址 + String ip = IpUtils.getIpAddr(); + operLog.setOperIp(ip); + operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + if (loginUser != null) { + operLog.setOperName(loginUser.getUsername()); + SysUser currentUser = loginUser.getUser(); + if (StringUtils.isNotNull(currentUser) && StringUtils.isNotNull(currentUser.getDept())) { + operLog.setDeptName(currentUser.getDept().getDeptName()); + } + } + + if (e != null) { + operLog.setStatus(BusinessStatus.FAIL.ordinal()); + operLog.setErrorMsg(StringUtils + .substring(Convert.toStr(e.getMessage(), ExceptionUtil.getExceptionMessage(e)), 0, 2000)); + } + // 设置方法名称 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operLog.setMethod(className + "." + methodName + "()"); + // 设置请求方式 + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + // 处理设置注解上的参数 + getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); + // 设置消耗时间 + operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get()); + // 保存数据库 + AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); + } catch (Exception exp) { + // 记录本地异常日志 + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } finally { + TIME_THREADLOCAL.remove(); + } + } + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param log 日志 + * @param operLog 操作日志 + * @throws Exception + */ + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) + throws Exception { + // 设置action动作 + operLog.setBusinessType(log.businessType().ordinal()); + // 设置标题 + operLog.setTitle(log.title()); + // 设置操作人类别 + operLog.setOperatorType(log.operatorType().ordinal()); + // 是否需要保存request,参数和值 + if (log.isSaveRequestData()) { + // 获取参数的信息,传入到数据库中。 + setRequestValue(joinPoint, operLog, log.excludeParamNames()); + } + // 是否需要保存response,参数和值 + if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) { + operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operLog 操作日志 + * @throws Exception 异常 + */ + private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception { + Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); + String requestMethod = operLog.getRequestMethod(); + if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), + HttpMethod.POST.name(), HttpMethod.DELETE.name())) { + String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } else { + operLog.setOperParam(StringUtils + .substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) { + String params = ""; + if (paramsArray != null && paramsArray.length > 0) { + for (Object o : paramsArray) { + if (StringUtils.isNotNull(o) && !isFilterObject(o)) { + try { + String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); + params += jsonObj.toString() + " "; + } catch (Exception e) { + } + } + } + } + return params.trim(); + } + + /** + * 忽略敏感属性 + */ + public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) { + return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) { + Class clazz = o.getClass(); + if (clazz.isArray()) { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection) o; + for (Object value : collection) { + return value instanceof MultipartFile; + } + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map) o; + for (Object value : map.entrySet()) { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java new file mode 100644 index 0000000..c664c33 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java @@ -0,0 +1,31 @@ +package com.ruoyi.framework.config; + +import java.util.TimeZone; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * 程序注解配置 + * + * @author ruoyi + */ +@Configuration +// 表示通过aop框架暴露该代理对象,AopContext能够访问 +@EnableAspectJAutoProxy(exposeProxy = true) +// 指定要扫描的Mapper类的包的路径 +@MapperScan(basePackages = "com.ruoyi.**.mapper", sqlSessionTemplateRef = "sqlSessionTemplate") +public class ApplicationConfig +{ + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() + { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java new file mode 100644 index 0000000..43e78ae --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java @@ -0,0 +1,83 @@ +package com.ruoyi.framework.config; + +import java.util.Properties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import static com.google.code.kaptcha.Constants.*; + +/** + * 验证码配置 + * + * @author ruoyi + */ +@Configuration +public class CaptchaConfig +{ + @Bean(name = "captchaProducer") + public DefaultKaptcha getKaptchaBean() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + + @Bean(name = "captchaProducerMath") + public DefaultKaptcha getKaptchaBeanMath() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.framework.config.KaptchaTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java new file mode 100644 index 0000000..9666460 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java @@ -0,0 +1,95 @@ +package com.ruoyi.framework.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import com.ruoyi.framework.config.properties.DynamicDataSourceProperties; + +import jakarta.annotation.PreDestroy; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +/** + * druid 配置多数据源 + * + * @author ruoyi + */ +@Configuration +public class DruidConfig { + + @Autowired + DynamicDataSourceProperties dataSourceProperties; + + private List druidDataSources = new ArrayList<>(); + + Logger logger = LoggerFactory.getLogger(DruidConfig.class); + + /** + * 去除监控页面底部的广告 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true") + FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) { + // 获取web监控页面的参数 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + // 提取common.js的配置路径 + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + final String filePath = "support/http/resources/js/common.js"; + // 创建filter进行过滤 + Filter filter = new Filter() { + @Override + public void init(jakarta.servlet.FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + chain.doFilter(request, response); + // 重置缓冲区,响应头不会被重置 + response.resetBuffer(); + // 获取common.js + String text = Utils.readFromResource(filePath); + // 正则替换banner, 除去底部的广告信息 + text = text.replaceAll("
", ""); + text = text.replaceAll("powered.*?shrek.wang", ""); + response.getWriter().write(text); + } + + @Override + public void destroy() { + } + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } + + public List getDruidDataSources() { + return druidDataSources; + } + + @PreDestroy + public void destroy() { + for (DruidDataSource druidDataSource : druidDataSources) { + druidDataSource.close(); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java new file mode 100644 index 0000000..f49314b --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java @@ -0,0 +1,79 @@ +package com.ruoyi.framework.config; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.filter.RefererFilter; +import com.ruoyi.common.filter.RepeatableFilter; +import com.ruoyi.common.filter.XssFilter; +import com.ruoyi.common.utils.StringUtils; + +import jakarta.servlet.DispatcherType; + +/** + * Filter配置 + * + * @author ruoyi + */ +@Configuration +public class FilterConfig { + @Value("${xss.excludes}") + private String excludes; + + @Value("${xss.urlPatterns}") + private String urlPatterns; + + @Value("${referer.allowed-domains}") + private String allowedDomains; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") + public FilterRegistrationBean xssFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new XssFilter()); + registration.addUrlPatterns(StringUtils.split(urlPatterns, ",")); + registration.setName("xssFilter"); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + Map initParameters = new HashMap(); + initParameters.put("excludes", excludes); + registration.setInitParameters(initParameters); + return registration; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(value = "referer.enabled", havingValue = "true") + public FilterRegistrationBean refererFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new RefererFilter()); + registration.addUrlPatterns(Constants.RESOURCE_PREFIX + "/*"); + registration.setName("refererFilter"); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + Map initParameters = new HashMap(); + initParameters.put("allowedDomains", allowedDomains); + registration.setInitParameters(initParameters); + return registration; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public FilterRegistrationBean someFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new RepeatableFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + return registration; + } + +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java new file mode 100644 index 0000000..8296e55 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java @@ -0,0 +1,44 @@ +package com.ruoyi.framework.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; + +import com.ruoyi.common.constant.Constants; + +/** + * 资源文件配置加载 + * + * @author ruoyi + */ +@Configuration +public class I18nConfig implements WebMvcConfigurer +{ + @Bean + public LocaleResolver localeResolver() + { + SessionLocaleResolver slr = new SessionLocaleResolver(); + // 默认语言 + slr.setDefaultLocale(Constants.DEFAULT_LOCALE); + return slr; + } + + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() + { + LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); + // 参数名 + lci.setParamName("lang"); + return lci; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) + { + registry.addInterceptor(localeChangeInterceptor()); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java new file mode 100644 index 0000000..211e9ab --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java @@ -0,0 +1,69 @@ +package com.ruoyi.framework.config; + +import java.security.SecureRandom; + +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +/** + * 验证码文本生成器 + * + * @author ruoyi + */ +public class KaptchaTextCreator extends DefaultTextCreator +{ + private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); + + @Override + public String getText() + { + Integer result = 0; + SecureRandom random = new SecureRandom(); + int x = random.nextInt(10); + int y = random.nextInt(10); + StringBuilder suChinese = new StringBuilder(); + int randomoperands = random.nextInt(3); + if (randomoperands == 0) + { + result = x * y; + suChinese.append(CNUMBERS[x]); + suChinese.append("*"); + suChinese.append(CNUMBERS[y]); + } + else if (randomoperands == 1) + { + if ((x != 0) && y % x == 0) + { + result = y / x; + suChinese.append(CNUMBERS[y]); + suChinese.append("/"); + suChinese.append(CNUMBERS[x]); + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + } + else + { + if (x >= y) + { + result = x - y; + suChinese.append(CNUMBERS[x]); + suChinese.append("-"); + suChinese.append(CNUMBERS[y]); + } + else + { + result = y - x; + suChinese.append(CNUMBERS[y]); + suChinese.append("-"); + suChinese.append(CNUMBERS[x]); + } + } + suChinese.append("=?@" + result); + return suChinese.toString(); + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java new file mode 100644 index 0000000..fbb14e7 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java @@ -0,0 +1,60 @@ +package com.ruoyi.framework.config; + +import javax.sql.DataSource; + +import org.apache.ibatis.io.VFS; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.io.DefaultResourceLoader; + +import com.github.pagehelper.PageInterceptor; +import com.github.pagehelper.autoconfigure.PageHelperStandardProperties; +import com.ruoyi.common.service.mybatis.CreateSqlSessionFactory; +import com.ruoyi.common.utils.MybatisUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.mybatis.CustomDatabaseIdProvider; + +/** + * Mybatis支持*匹配扫描包 + * + * @author ruoyi + */ +@Configuration +public class MyBatisConfig { + + Logger logger = LoggerFactory.getLogger(MyBatisConfig.class); + + @Bean + @ConditionalOnProperty(prefix = "createSqlSessionFactory", name = "use", havingValue = "mybatis") + public CreateSqlSessionFactory createSqlSessionFactory(PageHelperStandardProperties packageHelperStandardProperties) { + return new CreateSqlSessionFactory() { + public SqlSessionFactory createSqlSessionFactory(Environment env, DataSource dataSource) throws Exception { + String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage"); + String mapperLocations = env.getProperty("mybatis.mapperLocations"); + String configLocation = env.getProperty("mybatis.configLocation"); + typeAliasesPackage = MybatisUtils.setTypeAliasesPackage(typeAliasesPackage); + VFS.addImplClass(SpringBootVFS.class); + + final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + sessionFactory.setDatabaseIdProvider(new CustomDatabaseIdProvider()); + sessionFactory.setTypeAliasesPackage(typeAliasesPackage); + sessionFactory.setMapperLocations( + MybatisUtils.resolveMapperLocations(StringUtils.split(mapperLocations, ","))); + sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); + PageInterceptor interceptor = new PageInterceptor(); + interceptor.setProperties(packageHelperStandardProperties.getProperties()); + sessionFactory.addPlugins(interceptor); + return sessionFactory.getObject(); + } + }; + } + +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java new file mode 100644 index 0000000..0eae1ba --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java @@ -0,0 +1,77 @@ +package com.ruoyi.framework.config; + +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 通用配置 + * + * @author ruoyi + */ +@Configuration +public class ResourcesConfig implements WebMvcConfigurer { + @Autowired + private RepeatSubmitInterceptor repeatSubmitInterceptor; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + /** 本地文件上传路径 */ + registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**") + .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/"); + + /** swagger配置 */ + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") + .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic()); + registry.addResourceHandler("/**") + .addResourceLocations("classpath:/static/"); + registry.addResourceHandler("swagger-ui.html", "doc.html") + .addResourceLocations("classpath:/META-INF/resources/"); + registry.addResourceHandler("/webjars/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/"); + } + + /** + * 自定义拦截规则 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); + } + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // 设置访问源地址 + config.addAllowedOriginPattern("*"); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 有效期 1800秒 + config.setMaxAge(1800L); + // 添加映射路径,拦截一切请求 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + // 返回新的CorsFilter + return new CorsFilter(source); + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java new file mode 100644 index 0000000..8a2ed5f --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -0,0 +1,152 @@ +package com.ruoyi.framework.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.filter.CorsFilter; + +import com.ruoyi.framework.config.properties.PermitAllUrlProperties; +import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter; +import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl; +import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; + +/** + * spring security配置 + * + * @author Dftre + */ +@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) +@Configuration +public class SecurityConfig { + /** + * 自定义用户认证逻辑 + */ + @Autowired + private UserDetailsService userDetailsService; + + /** + * 认证失败处理类 + */ + @Autowired + private AuthenticationEntryPointImpl unauthorizedHandler; + + /** + * 退出处理类 + */ + @Autowired + private LogoutSuccessHandlerImpl logoutSuccessHandler; + + /** + * token认证过滤器 + */ + @Autowired + private JwtAuthenticationTokenFilter authenticationTokenFilter; + + /** + * 跨域过滤器 + */ + @Autowired + private CorsFilter corsFilter; + + /** + * 允许匿名访问的地址 + */ + @Autowired + private PermitAllUrlProperties permitAllUrl; + + /** + * @return + * @throws Exception + */ + @Bean + AuthenticationManager authenticationManager() { + DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(userDetailsService); + daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder()); + return new ProviderManager(daoAuthenticationProvider); + } + + /** + * anyRequest | 匹配所有请求路径 + * access | SpringEl表达式结果为true时可以访问 + * anonymous | 匿名可以访问 + * denyAll | 用户不能访问 + * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) + * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 + * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 + * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 + * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 + * hasRole | 如果有参数,参数表示角色,则其角色可以访问 + * permitAll | 用户可以任意访问 + * rememberMe | 允许通过remember-me登录的用户访问 + * authenticated | 用户登录后可访问 + */ + @Bean + SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { + return httpSecurity + // CSRF禁用,因为不使用session + .csrf(csrf -> csrf.disable()) + // 禁用HTTP响应标头 + .headers((headersCustomizer) -> { + headersCustomizer.cacheControl(cache -> cache.disable()) + .frameOptions(options -> options.sameOrigin()); + }) + // 认证失败处理类 + .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) + // 基于token,所以不需要session + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + // 注解标记允许匿名访问的url + .authorizeHttpRequests((requests) -> { + permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll()); + // 对于登录login 注册register 验证码captchaImage 允许匿名访问 + requests.requestMatchers("/login", "/register", "/captchaImage").permitAll() + // 静态资源,可匿名访问 + .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", + "/profile/**") + .permitAll() + .requestMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", + "/druid/**", "/*/api-docs/**") + .permitAll() + // 除上面外的所有请求全部需要鉴权认证 + .anyRequest().authenticated(); + }) + // 添加Logout filter + .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler)) + // 添加JWT filter + .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class) + // 添加CORS filter + .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class) + .addFilterBefore(corsFilter, LogoutFilter.class) + .build(); + } + /** + * 忽略web安全配置 + * 主要是忽略websocket的安全配置 + * + * @return WebSecurityCustomizer + */ + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> web.ignoring().requestMatchers("/websocket/**"); + } + + /** + * 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java new file mode 100644 index 0000000..36f08a5 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java @@ -0,0 +1,32 @@ +package com.ruoyi.framework.config; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import com.ruoyi.common.utils.ServletUtils; + +/** + * 服务相关配置 + * + * @author ruoyi + */ +@Component +public class ServerConfig +{ + /** + * 获取完整的请求路径,包括:域名,端口,上下文访问路径 + * + * @return 服务地址 + */ + public String getUrl() + { + HttpServletRequest request = ServletUtils.getRequest(); + return getDomain(request); + } + + public static String getDomain(HttpServletRequest request) + { + StringBuffer url = request.getRequestURL(); + String contextPath = request.getSession().getServletContext().getContextPath(); + return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java new file mode 100644 index 0000000..913561b --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java @@ -0,0 +1,65 @@ +package com.ruoyi.framework.config; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import com.ruoyi.common.utils.Threads; + +/** + * 线程池配置 + * + * @author ruoyi + **/ +@Configuration +public class ThreadPoolConfig +{ + // 核心线程池大小 + private int corePoolSize = 50; + + // 最大可创建的线程数 + private int maxPoolSize = 200; + + // 队列最大长度 + private int queueCapacity = 1000; + + // 线程池维护线程所允许的空闲时间 + private int keepAliveSeconds = 300; + + @Bean(name = "threadPoolTaskExecutor") + public ThreadPoolTaskExecutor threadPoolTaskExecutor() + { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setMaxPoolSize(maxPoolSize); + executor.setCorePoolSize(corePoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setKeepAliveSeconds(keepAliveSeconds); + // 线程池对拒绝任务(无线程可用)的处理策略 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + return executor; + } + + /** + * 执行周期性或定时任务 + */ + @Bean(name = "scheduledExecutorService") + protected ScheduledExecutorService scheduledExecutorService() + { + return new ScheduledThreadPoolExecutor(corePoolSize, + new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), + new ThreadPoolExecutor.CallerRunsPolicy()) + { + @Override + protected void afterExecute(Runnable r, Throwable t) + { + super.afterExecute(r, t); + Threads.printException(r, t); + } + }; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java new file mode 100644 index 0000000..6b9deac --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java @@ -0,0 +1,177 @@ +package com.ruoyi.framework.config.properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +/** + * druid 配置属性 + * + * @author ruoyi + */ +@Configuration +public class DruidProperties { + @Value("${spring.datasource.druid.initialSize}") + private int initialSize; + + @Value("${spring.datasource.druid.minIdle}") + private int minIdle; + + @Value("${spring.datasource.druid.maxActive}") + private int maxActive; + + @Value("${spring.datasource.druid.maxWait}") + private int maxWait; + + @Value("${spring.datasource.druid.connectTimeout}") + private int connectTimeout; + + @Value("${spring.datasource.druid.socketTimeout}") + private int socketTimeout; + + @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") + private int timeBetweenEvictionRunsMillis; + + @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") + private int minEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") + private int maxEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.validationQuery}") + private String validationQuery; + + @Value("${spring.datasource.druid.testWhileIdle}") + private boolean testWhileIdle; + + @Value("${spring.datasource.druid.testOnBorrow}") + private boolean testOnBorrow; + + @Value("${spring.datasource.druid.testOnReturn}") + private boolean testOnReturn; + + @Value("${spring.datasource.druid.connectionProperties}") + private String connectionProperties; + + @Value("${spring.datasource.druid.filters}") + private String filters; + + public String getConnectionProperties() { + return connectionProperties; + } + + public void setConnectionProperties(String connectionProperties) { + this.connectionProperties = connectionProperties; + } + + public String getFilters() { + return filters; + } + + public void setFilters(String filters) { + this.filters = filters; + } + + public int getInitialSize() { + return initialSize; + } + + public void setInitialSize(int initialSize) { + this.initialSize = initialSize; + } + + public int getMinIdle() { + return minIdle; + } + + public void setMinIdle(int minIdle) { + this.minIdle = minIdle; + } + + public int getMaxActive() { + return maxActive; + } + + public void setMaxActive(int maxActive) { + this.maxActive = maxActive; + } + + public int getMaxWait() { + return maxWait; + } + + public void setMaxWait(int maxWait) { + this.maxWait = maxWait; + } + + public int getConnectTimeout() { + return connectTimeout; + } + + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public int getSocketTimeout() { + return socketTimeout; + } + + public void setSocketTimeout(int socketTimeout) { + this.socketTimeout = socketTimeout; + } + + public int getTimeBetweenEvictionRunsMillis() { + return timeBetweenEvictionRunsMillis; + } + + public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) { + this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; + } + + public int getMinEvictableIdleTimeMillis() { + return minEvictableIdleTimeMillis; + } + + public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) { + this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; + } + + public int getMaxEvictableIdleTimeMillis() { + return maxEvictableIdleTimeMillis; + } + + public void setMaxEvictableIdleTimeMillis(int maxEvictableIdleTimeMillis) { + this.maxEvictableIdleTimeMillis = maxEvictableIdleTimeMillis; + } + + public String getValidationQuery() { + return validationQuery; + } + + public void setValidationQuery(String validationQuery) { + this.validationQuery = validationQuery; + } + + public boolean isTestWhileIdle() { + return testWhileIdle; + } + + public void setTestWhileIdle(boolean testWhileIdle) { + this.testWhileIdle = testWhileIdle; + } + + public boolean isTestOnBorrow() { + return testOnBorrow; + } + + public void setTestOnBorrow(boolean testOnBorrow) { + this.testOnBorrow = testOnBorrow; + } + + public boolean isTestOnReturn() { + return testOnReturn; + } + + public void setTestOnReturn(boolean testOnReturn) { + this.testOnReturn = testOnReturn; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DynamicDataSourceProperties.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DynamicDataSourceProperties.java new file mode 100644 index 0000000..3b37123 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DynamicDataSourceProperties.java @@ -0,0 +1,77 @@ +package com.ruoyi.framework.config.properties; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Properties; + +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.util.DruidDataSourceUtils; +import com.ruoyi.common.utils.spring.SpringUtils; + +@Configuration +@ConfigurationProperties(prefix = "spring.datasource.dynamic") +public class DynamicDataSourceProperties { + + private Map datasource; + private String primary; + + public Properties build(DataSourceProperties dataSourceProperties) { + Properties prop = new Properties(); + DruidProperties druidProperties = SpringUtils.getBean(DruidProperties.class); + prop.setProperty("druid.url", dataSourceProperties.getUrl()); + prop.setProperty("druid.username", dataSourceProperties.getUsername()); + prop.setProperty("druid.password", dataSourceProperties.getPassword()); + prop.setProperty("druid.initialSize", String.valueOf(druidProperties.getInitialSize())); + prop.setProperty("druid.minIdle", String.valueOf(druidProperties.getMinIdle())); + prop.setProperty("druid.maxActive", String.valueOf(druidProperties.getMaxActive())); + prop.setProperty("druid.maxWait", String.valueOf(druidProperties.getMaxWait())); + prop.setProperty("druid.validationQuery", druidProperties.getValidationQuery()); + prop.setProperty("druid.testWhileIdle", String.valueOf(druidProperties.isTestWhileIdle())); + prop.setProperty("druid.testOnBorrow", String.valueOf(druidProperties.isTestOnBorrow())); + prop.setProperty("druid.testOnReturn", String.valueOf(druidProperties.isTestOnReturn())); + prop.setProperty("druid.filters", String.valueOf(druidProperties.getFilters())); + prop.setProperty("druid.connectionProperties", String.valueOf(druidProperties.getConnectionProperties())); + prop.setProperty("druid.timeBetweenEvictionRunsMillis", + String.valueOf(druidProperties.getTimeBetweenEvictionRunsMillis())); + prop.setProperty("druid.minEvictableIdleTimeMillis", + String.valueOf(druidProperties.getMinEvictableIdleTimeMillis())); + prop.setProperty("druid.maxEvictableIdleTimeMillis", + String.valueOf(druidProperties.getMaxEvictableIdleTimeMillis())); + return prop; + } + + public void setProperties(DruidDataSource dataSource, Properties prop) { + DruidDataSourceUtils.configFromProperties(dataSource, prop); + // 确保过滤器配置生效 + try { + if (prop.getProperty("druid.connectionProperties") != null) { + dataSource.setConnectionProperties(prop.getProperty("druid.connectionProperties")); + } + // 启用防火墙功能 + dataSource.setProxyFilters(new ArrayList<>()); + } catch (Exception e) { + throw new RuntimeException("配置Druid过滤器失败", e); + } + } + + public Map getDatasource() { + return datasource; + } + + public void setDatasource(Map datasource) { + this.datasource = datasource; + } + + public String getPrimaryStorageBucket() { + return primary; + } + + public void setPrimary(String primary) { + this.primary = primary; + } + +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java new file mode 100644 index 0000000..7942b52 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java @@ -0,0 +1,75 @@ +package com.ruoyi.framework.config.properties; + +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.RegExUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.utils.spring.SpringUtils; + +/** + * 设置Anonymous注解允许匿名访问的url + * + * @author ruoyi + */ +@Configuration +public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware { + + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); + + public static final String ASTERISK = "*"; + + private ApplicationContext applicationContext; + + private Set urls = new HashSet<>(); + + @Override + public void afterPropertiesSet() { + RequestMappingHandlerMapping mapping = applicationContext.getBean("requestMappingHandlerMapping", + RequestMappingHandlerMapping.class); + Map map = mapping.getHandlerMethods(); + String matching = SpringUtils.getRequiredProperty("spring.mvc.pathmatch.matching-strategy").toLowerCase(); + map.entrySet().stream() + .flatMap(entry -> { + HandlerMethod handlerMethod = entry.getValue(); + Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class); + Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); + return List.of( + new SimpleImmutableEntry<>(entry.getKey(), method), + new SimpleImmutableEntry<>(entry.getKey(), controller)).stream(); + }) + .filter(pair -> pair.getValue() != null) + .map(pair -> switch (matching) { + case "ant_path_matcher" -> pair.getKey().getPatternsCondition().getPatterns(); + case "path_pattern_parser" -> pair.getKey().getPathPatternsCondition().getPatternValues(); + default -> pair.getKey().getPatternsCondition().getPatterns(); + }) + .flatMap(patterns -> Objects.requireNonNull(patterns).stream()) + .map(url -> RegExUtils.replaceAll(url, PATTERN, ASTERISK)) + .forEach(urls::add); + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.applicationContext = context; + } + + public Set getUrls() { + return urls; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java new file mode 100644 index 0000000..f3793aa --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java @@ -0,0 +1,28 @@ +package com.ruoyi.framework.datasource; + +import java.util.Map; + +import javax.sql.CommonDataSource; + +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * 动态数据源 + * + * @author ruoyi + */ +public class DynamicDataSource extends AbstractRoutingDataSource +{ + public DynamicDataSource(CommonDataSource defaultTargetDataSource, Map targetDataSources) + { + super.setDefaultTargetDataSource(defaultTargetDataSource); + super.setTargetDataSources(targetDataSources); + super.afterPropertiesSet(); + } + + @Override + protected Object determineCurrentLookupKey() + { + return DynamicDataSourceContextHolder.getDataSourceType(); + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java new file mode 100644 index 0000000..e287645 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java @@ -0,0 +1,60 @@ +package com.ruoyi.framework.datasource; + +import java.util.Objects; +import java.util.Stack; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 数据源切换处理 + * + * @author ruoyi + */ +public class DynamicDataSourceContextHolder { + public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); + + /** + * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, + * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 + */ + private static final ThreadLocal> CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 设置数据源的变量 + */ + public static void setDataSourceType(String dsType) { + log.info("切换到{}数据源", dsType); + Stack stack = CONTEXT_HOLDER.get(); + if (Objects.isNull(stack)) { + stack = new Stack<>(); + CONTEXT_HOLDER.set(stack); + } + stack.push(dsType); + } + + /** + * 获得数据源的变量 + */ + public static String getDataSourceType() { + Stack stack = CONTEXT_HOLDER.get(); + if (Objects.isNull(stack)) { + return null; + } else { + return stack.peek(); + } + } + + /** + * 清空数据源变量 + */ + public static void clearDataSourceType() { + Stack stack = CONTEXT_HOLDER.get(); + if (Objects.nonNull(stack) && !stack.isEmpty()) { + stack.pop(); + if (stack.isEmpty()) { + CONTEXT_HOLDER.remove(); + } + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicSqlSessionTemplate.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicSqlSessionTemplate.java new file mode 100644 index 0000000..5f3b893 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicSqlSessionTemplate.java @@ -0,0 +1,311 @@ +package com.ruoyi.framework.datasource; + +import static java.lang.reflect.Proxy.*; +import static org.apache.ibatis.reflection.ExceptionUtil.*; +import static org.mybatis.spring.SqlSessionUtils.*; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.exceptions.PersistenceException; +import org.apache.ibatis.executor.BatchResult; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.MyBatisExceptionTranslator; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.dao.support.PersistenceExceptionTranslator; + +/** + * 自定义SqlSessionTemplate,动态切换数据源 + * + * @author ruoyi + */ +public class DynamicSqlSessionTemplate extends SqlSessionTemplate { + private final SqlSessionFactory sqlSessionFactory; + private final ExecutorType executorType; + private final SqlSession sqlSessionProxy; + private final PersistenceExceptionTranslator exceptionTranslator; + private Map targetSqlSessionFactorys; + private SqlSessionFactory defaultTargetSqlSessionFactory; + + public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { + this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType()); + } + + public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) { + this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator( + sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true)); + } + + public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, + PersistenceExceptionTranslator exceptionTranslator) { + super(sqlSessionFactory, executorType, exceptionTranslator); + this.sqlSessionFactory = sqlSessionFactory; + this.executorType = executorType; + this.exceptionTranslator = exceptionTranslator; + this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), + new Class[] { SqlSession.class }, new SqlSessionInterceptor()); + this.defaultTargetSqlSessionFactory = sqlSessionFactory; + } + + public void setTargetSqlSessionFactorys(Map targetSqlSessionFactorys) { + this.targetSqlSessionFactorys = targetSqlSessionFactorys; + } + + public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) { + this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory; + } + + @Override + public SqlSessionFactory getSqlSessionFactory() { + SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys + .get(DynamicDataSourceContextHolder.getDataSourceType()); + if (targetSqlSessionFactory != null) { + return targetSqlSessionFactory; + } else if (defaultTargetSqlSessionFactory != null) { + return defaultTargetSqlSessionFactory; + } + return this.sqlSessionFactory; + } + + @Override + public Configuration getConfiguration() { + return this.getSqlSessionFactory().getConfiguration(); + } + + public ExecutorType getExecutorType() { + return this.executorType; + } + + public PersistenceExceptionTranslator getPersistenceExceptionTranslator() { + return this.exceptionTranslator; + } + + /** + * {@inheritDoc} + */ + public T selectOne(String statement) { + return this.sqlSessionProxy.selectOne(statement); + } + + /** + * {@inheritDoc} + */ + public T selectOne(String statement, Object parameter) { + return this.sqlSessionProxy.selectOne(statement, parameter); + } + + /** + * {@inheritDoc} + */ + public Map selectMap(String statement, String mapKey) { + return this.sqlSessionProxy.selectMap(statement, mapKey); + } + + /** + * {@inheritDoc} + */ + public Map selectMap(String statement, Object parameter, String mapKey) { + return this.sqlSessionProxy.selectMap(statement, parameter, mapKey); + } + + /** + * {@inheritDoc} + */ + public Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { + return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds); + } + + /** + * {@inheritDoc} + */ + public List selectList(String statement) { + return this.sqlSessionProxy.selectList(statement); + } + + /** + * {@inheritDoc} + */ + public List selectList(String statement, Object parameter) { + return this.sqlSessionProxy.selectList(statement, parameter); + } + + /** + * {@inheritDoc} + */ + public List selectList(String statement, Object parameter, RowBounds rowBounds) { + return this.sqlSessionProxy.selectList(statement, parameter, rowBounds); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("rawtypes") + public void select(String statement, ResultHandler handler) { + this.sqlSessionProxy.select(statement, handler); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("rawtypes") + public void select(String statement, Object parameter, ResultHandler handler) { + this.sqlSessionProxy.select(statement, parameter, handler); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("rawtypes") + public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { + this.sqlSessionProxy.select(statement, parameter, rowBounds, handler); + } + + /** + * {@inheritDoc} + */ + public int insert(String statement) { + return this.sqlSessionProxy.insert(statement); + } + + /** + * {@inheritDoc} + */ + public int insert(String statement, Object parameter) { + return this.sqlSessionProxy.insert(statement, parameter); + } + + /** + * {@inheritDoc} + */ + public int update(String statement) { + return this.sqlSessionProxy.update(statement); + } + + /** + * {@inheritDoc} + */ + public int update(String statement, Object parameter) { + return this.sqlSessionProxy.update(statement, parameter); + } + + /** + * {@inheritDoc} + */ + public int delete(String statement) { + return this.sqlSessionProxy.delete(statement); + } + + /** + * {@inheritDoc} + */ + public int delete(String statement, Object parameter) { + return this.sqlSessionProxy.delete(statement, parameter); + } + + /** + * {@inheritDoc} + */ + public T getMapper(Class type) { + return getConfiguration().getMapper(type, this); + } + + /** + * {@inheritDoc} + */ + public void commit() { + throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); + } + + /** + * {@inheritDoc} + */ + public void commit(boolean force) { + throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); + } + + /** + * {@inheritDoc} + */ + public void rollback() { + throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); + } + + /** + * {@inheritDoc} + */ + public void rollback(boolean force) { + throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); + } + + /** + * {@inheritDoc} + */ + public void close() { + throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession"); + } + + /** + * {@inheritDoc} + */ + public void clearCache() { + this.sqlSessionProxy.clearCache(); + } + + /** + * {@inheritDoc} + */ + public Connection getConnection() { + return this.sqlSessionProxy.getConnection(); + } + + /** + * {@inheritDoc} + * + * @since 1.0.2 + */ + public List flushStatements() { + return this.sqlSessionProxy.flushStatements(); + } + + /** + * Proxy needed to route MyBatis method calls to the proper SqlSession got from + * Spring's Transaction Manager It also + * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass + * a {@code PersistenceException} to + * the {@code PersistenceExceptionTranslator}. + */ + private class SqlSessionInterceptor implements InvocationHandler { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + final SqlSession sqlSession = getSqlSession(DynamicSqlSessionTemplate.this.getSqlSessionFactory(), + DynamicSqlSessionTemplate.this.executorType, DynamicSqlSessionTemplate.this.exceptionTranslator); + try { + Object result = method.invoke(sqlSession, args); + if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory())) { + sqlSession.commit(true); + } + return result; + } catch (Throwable t) { + Throwable unwrapped = unwrapThrowable(t); + if (DynamicSqlSessionTemplate.this.exceptionTranslator != null + && unwrapped instanceof PersistenceException) { + Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator + .translateExceptionIfPossible((PersistenceException) unwrapped); + if (translated != null) { + unwrapped = translated; + } + } + throw unwrapped; + } finally { + closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory()); + } + } + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java new file mode 100644 index 0000000..cbb5600 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java @@ -0,0 +1,55 @@ +package com.ruoyi.framework.interceptor; + +import java.lang.reflect.Method; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.annotation.RepeatSubmit; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.ServletUtils; + +/** + * 防止重复提交拦截器 + * + * @author ruoyi + */ +@Component +public abstract class RepeatSubmitInterceptor implements HandlerInterceptor +{ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception + { + if (handler instanceof HandlerMethod) + { + HandlerMethod handlerMethod = (HandlerMethod) handler; + Method method = handlerMethod.getMethod(); + RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); + if (annotation != null) + { + if (this.isRepeatSubmit(request, annotation)) + { + AjaxResult ajaxResult = AjaxResult.error(annotation.message()); + ServletUtils.renderString(response, JSON.toJSONString(ajaxResult)); + return false; + } + } + return true; + } + else + { + return true; + } + } + + /** + * 验证是否重复提交由子类实现具体的防重复提交的规则 + * + * @param request + * @return + * @throws Exception + */ + public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java new file mode 100644 index 0000000..004ca42 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java @@ -0,0 +1,104 @@ +package com.ruoyi.framework.interceptor.impl; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.annotation.RepeatSubmit; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.filter.RepeatedlyRequestWrapper; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.http.HttpHelper; +import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * 判断请求url和数据是否和上一次相同, + * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 + * + * @author ruoyi + */ +@Component +public class SameUrlDataInterceptor extends RepeatSubmitInterceptor +{ + public final String REPEAT_PARAMS = "repeatParams"; + + public final String REPEAT_TIME = "repeatTime"; + + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + @SuppressWarnings("unchecked") + @Override + public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) + { + String nowParams = ""; + if (request instanceof RepeatedlyRequestWrapper) + { + RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; + nowParams = HttpHelper.getBodyString(repeatedlyRequest); + } + // body参数为空,获取Parameter的数据 + if (StringUtils.isEmpty(nowParams)) + { + nowParams = JSON.toJSONString(request.getParameterMap()); + } + Map nowDataMap = new HashMap(); + nowDataMap.put(REPEAT_PARAMS, nowParams); + nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); + // 请求地址(作为存放cache的key值) + String url = request.getRequestURI(); + // 唯一值(没有消息头则使用请求地址) + String submitKey = StringUtils.trimToEmpty(request.getHeader(header)); + // 唯一标识(指定key + url + 消息头) + String cacheRepeatKey = url + submitKey; + Object sessionObj = CacheUtils.get(CacheConstants.REPEAT_SUBMIT_KEY, cacheRepeatKey); + if (sessionObj != null) + { + Map sessionMap = (Map) sessionObj; + if (sessionMap.containsKey(url)) + { + Map preDataMap = (Map) sessionMap.get(url); + if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) + { + return true; + } + } + } + Map cacheMap = new HashMap(); + cacheMap.put(url, nowDataMap); + CacheUtils.put(CacheConstants.REPEAT_SUBMIT_KEY, cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS); + return false; + } + + /** + * 判断参数是否相同 + */ + private boolean compareParams(Map nowMap, Map preMap) + { + String nowParams = (String) nowMap.get(REPEAT_PARAMS); + String preParams = (String) preMap.get(REPEAT_PARAMS); + return nowParams.equals(preParams); + } + + /** + * 判断两次间隔时间 + */ + private boolean compareTime(Map nowMap, Map preMap, int interval) + { + long time1 = (Long) nowMap.get(REPEAT_TIME); + long time2 = (Long) preMap.get(REPEAT_TIME); + if ((time1 - time2) < interval) + { + return true; + } + return false; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java new file mode 100644 index 0000000..7387a02 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java @@ -0,0 +1,55 @@ +package com.ruoyi.framework.manager; + +import java.util.TimerTask; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import com.ruoyi.common.utils.Threads; +import com.ruoyi.common.utils.spring.SpringUtils; + +/** + * 异步任务管理器 + * + * @author ruoyi + */ +public class AsyncManager +{ + /** + * 操作延迟10毫秒 + */ + private final int OPERATE_DELAY_TIME = 10; + + /** + * 异步操作任务调度线程池 + */ + private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); + + /** + * 单例模式 + */ + private AsyncManager(){} + + private static AsyncManager me = new AsyncManager(); + + public static AsyncManager me() + { + return me; + } + + /** + * 执行任务 + * + * @param task 任务 + */ + public void execute(TimerTask task) + { + executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); + } + + /** + * 停止任务线程池 + */ + public void shutdown() + { + Threads.shutdownAndAwaitTermination(executor); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/DataSourceManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/DataSourceManager.java new file mode 100644 index 0000000..6034338 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/DataSourceManager.java @@ -0,0 +1,230 @@ +package com.ruoyi.framework.manager; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +import javax.sql.CommonDataSource; +import javax.sql.DataSource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.pool.xa.DruidXADataSource; +import com.ruoyi.common.service.datasource.AfterCreateDataSource; +import com.ruoyi.framework.config.DruidConfig; +import com.ruoyi.framework.config.properties.DynamicDataSourceProperties; +import com.ruoyi.framework.datasource.DynamicDataSource; +import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder; + +@Configuration +public class DataSourceManager implements InitializingBean { + protected final Logger logger = LoggerFactory.getLogger(DataSourceManager.class); + private Map targetDataSources = new HashMap<>(); + private final Map dsDatabaseId = new ConcurrentHashMap<>(); + private final Map dataSourceKeyIndex = new ConcurrentHashMap<>(); + + @Value("${spring.datasource.dynamic.xa}") + private boolean xa; + + @Autowired + private DynamicDataSourceProperties dataSourceProperties; + + @Autowired + private DruidConfig druidConfig; + + @Autowired(required = false) + private AfterCreateDataSource afterCreateDataSource; + + @Bean(name = "dynamicDataSource") + @Primary + DynamicDataSource dataSource() { + Map objectMap = new HashMap<>(); + Map targetDataSources = this.getDataSourcesMap(); + for (Map.Entry entry : targetDataSources.entrySet()) { + objectMap.put(entry.getKey(), entry.getValue()); + } + return new DynamicDataSource(targetDataSources.get(dataSourceProperties.getPrimaryStorageBucket()), objectMap); + } + + // 仅启动期调用:基于 JDBC 元数据判定 mybatis databaseId(含 openGauss 区分),与 MyBatis 机制兼容 + private String detectDatabaseId(DataSource dataSource) { + try (Connection conn = dataSource.getConnection()) { + DatabaseMetaData md = conn.getMetaData(); + String productName = safeLower(md.getDatabaseProductName()); + String driverName = safeLower(md.getDriverName()); + String url = safeLower(null); + try { + url = safeLower(md.getURL()); + } catch (Exception ignore) { + } + + // 优先尝试:通过 select version() 识别并顺带验证数据源 + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("select version()")) { + if (rs.next()) { + String v = safeLower(rs.getString(1)); + if (v != null) { + if (v.contains("openGauss") || v.contains("gauss")) { + return "openGauss"; + } + if (v.contains("postgresql") || v.contains("postgres")) { + return "postgresql"; + } + if (v.contains("mysql") || v.contains("mariadb")) { + return "mysql"; + } + if (v.contains("oracle")) { + return "oracle"; + } + if (v.contains("sql server") || v.contains("microsoft")) { + return "sqlserver"; + } + if (v.contains("h2")) { + return "h2"; + } + } + } + } catch (Exception ignore) { + } + // 先看 URL/驱动是否明确标识 openGauss/Gauss + if ((url != null && url.startsWith("jdbc:openGauss:")) + || (driverName != null && (driverName.contains("openGauss") || driverName.contains("gauss"))) + || (productName != null && (productName.contains("openGauss") || productName.contains("gauss")))) { + return "openGauss"; + } + + if (productName != null && productName.contains("postgres")) { + // 区分 openGauss 与 PostgreSQL + String ver = safeLower(md.getDatabaseProductVersion()); + if ((ver != null && (ver.contains("openGauss") || ver.contains("gauss")))) { + return "openGauss"; + } + return "postgresql"; + } + if (productName != null) { + if (productName.contains("mysql")) + return "mysql"; + if (productName.contains("oracle")) + return "oracle"; + if (productName.contains("sql server") || productName.contains("microsoft sql")) + return "sqlserver"; + if (productName.contains("h2")) + return "h2"; + } + return productName != null ? productName : null; + } catch (SQLException e) { + logger.warn("databaseId 判定失败,返回 null", e); + return null; + } + } + + @Override + public void afterPropertiesSet() throws Exception { + dataSourceProperties.getDatasource().forEach((name, props) -> { + Properties properties = dataSourceProperties.build(props); + CommonDataSource commonDataSource = createDataSource(name, properties); + if (afterCreateDataSource != null) { + afterCreateDataSource.afterCreateDataSource(name, properties, commonDataSource); + } + DataSource dataSource = (DataSource) commonDataSource; + logger.info("数据源:{} 校验中.......", name); + long start = System.currentTimeMillis(); + String databaseId = detectDatabaseId(dataSource); + if (databaseId != null) { + dsDatabaseId.put(name, databaseId); + } + logger.info("数据源:{} 链接成功,耗时:{}ms,databaseId:{}", name, System.currentTimeMillis() - start, databaseId); + + putDataSource(name, dataSource); + }); + } + + public DataSource createDataSource(String name, Properties prop) { + DruidDataSource dataSource = null; + if (xa) { + dataSource = new DruidXADataSource(); + } else { + dataSource = new DruidDataSource(); + } + druidConfig.getDruidDataSources().add(dataSource); + dataSource.setConnectProperties(prop); + dataSourceProperties.setProperties(dataSource, prop); + return dataSource; + } + + public DataSource getPrimaryDataSource() { + return targetDataSources.get(dataSourceProperties.getPrimaryStorageBucket()); + } + + public DataSource getDataSource(String name) { + return targetDataSources.get(name); + } + + public Collection getDataSources() { + return targetDataSources.values(); + } + + public Map getDataSourcesMap() { + return targetDataSources; + } + + public void putDataSource(String name, DataSource dataSource) { + targetDataSources.put(name, dataSource); + dataSourceKeyIndex.put(dataSource, name); + dsDatabaseId.computeIfAbsent(name, k -> detectDatabaseId(dataSource)); + } + + // 提供给 Provider/业务:根据 key 或当前线程获取已缓存的 databaseId + public String getDatabaseId(String dsKey) { + String key = (dsKey == null || dsKey.isEmpty()) ? dataSourceProperties.getPrimaryStorageBucket() : dsKey; + return dsDatabaseId.get(key); + } + + public String getCurrentDatabaseId() { + return getDatabaseId(DynamicDataSourceContextHolder.getDataSourceType()); + } + + // 提供 DataSource -> key 的反查,供 Provider 复用缓存 + public String findKeyByDataSource(DataSource dataSource) { + return dataSourceKeyIndex.get(dataSource); + } + + // 提供复用:按 DataSource 计算并(在可确定 key 时)写入缓存 + public String computeDatabaseIdForDataSource(DataSource dataSource) { + String key = findKeyByDataSource(dataSource); + String id = detectDatabaseId(dataSource); + if (key != null && id != null) { + dsDatabaseId.putIfAbsent(key, id); + } + return id; + } + + // 提供复用:按指定 key 计算并写入缓存 + public String computeAndCacheDatabaseId(String dsKey, DataSource dataSource) { + String id = detectDatabaseId(dataSource); + if (dsKey != null && id != null) { + dsDatabaseId.putIfAbsent(dsKey, id); + } + return id; + } + + private static String safeLower(String s) { + return s == null ? null : s.toLowerCase(Locale.ROOT); + } + +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/DynamicTransactionManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/DynamicTransactionManager.java new file mode 100644 index 0000000..f280c18 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/DynamicTransactionManager.java @@ -0,0 +1,30 @@ +package com.ruoyi.framework.manager; + +import java.util.Objects; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.support.JdbcTransactionManager; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder; + +@Component +@EnableTransactionManagement(proxyTargetClass = true) +public class DynamicTransactionManager extends JdbcTransactionManager { + + @Autowired + DataSourceManager dataSourceManager; + + @Override + public DataSource getDataSource() { + DataSource dataSource = dataSourceManager.getDataSource(DynamicDataSourceContextHolder.getDataSourceType()); + if (!Objects.isNull(dataSource)) { + return dataSource; + } else { + return dataSourceManager.getPrimaryDataSource(); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java new file mode 100644 index 0000000..f70ba5b --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java @@ -0,0 +1,43 @@ +package com.ruoyi.framework.manager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.utils.http.HttpUtils; + +import jakarta.annotation.PreDestroy; + +/** + * 确保应用退出时能关闭后台线程 + * + * @author ruoyi + */ +@Component +public class ShutdownManager +{ + private static final Logger logger = LoggerFactory.getLogger("sys-user"); + + @PreDestroy + public void destroy() + { + shutdownAsyncManager(); + HttpUtils.shutdown(); + } + + /** + * 停止异步执行任务 + */ + private void shutdownAsyncManager() + { + try + { + logger.info("====关闭后台任务任务线程池===="); + AsyncManager.me().shutdown(); + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java new file mode 100644 index 0000000..267e305 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java @@ -0,0 +1,102 @@ +package com.ruoyi.framework.manager.factory; + +import java.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.LogUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.AddressUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.system.domain.SysLogininfor; +import com.ruoyi.system.domain.SysOperLog; +import com.ruoyi.system.service.ISysLogininforService; +import com.ruoyi.system.service.ISysOperLogService; +import eu.bitwalker.useragentutils.UserAgent; + +/** + * 异步工厂(产生任务用) + * + * @author ruoyi + */ +public class AsyncFactory +{ + private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息 + * @param args 列表 + * @return 任务task + */ + public static TimerTask recordLogininfor(final String username, final String status, final String message, + final Object... args) + { + final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + final String ip = IpUtils.getIpAddr(); + return new TimerTask() + { + @Override + public void run() + { + String address = AddressUtils.getRealAddressByIP(ip); + StringBuilder s = new StringBuilder(); + s.append(LogUtils.getBlock(ip)); + s.append(address); + s.append(LogUtils.getBlock(username)); + s.append(LogUtils.getBlock(status)); + s.append(LogUtils.getBlock(message)); + // 打印信息到日志 + sys_user_logger.info(s.toString(), args); + // 获取客户端操作系统 + String os = userAgent.getOperatingSystem().getName(); + // 获取客户端浏览器 + String browser = userAgent.getBrowser().getName(); + // 封装对象 + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setUserName(username); + logininfor.setIpaddr(ip); + logininfor.setLoginLocation(address); + logininfor.setBrowser(browser); + logininfor.setOs(os); + logininfor.setMsg(message); + // 日志状态 + if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) + { + logininfor.setStatus(Constants.SUCCESS); + } + else if (Constants.LOGIN_FAIL.equals(status)) + { + logininfor.setStatus(Constants.FAIL); + } + // 插入数据 + SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor); + } + }; + } + + /** + * 操作日志记录 + * + * @param operLog 操作日志信息 + * @return 任务task + */ + public static TimerTask recordOper(final SysOperLog operLog) + { + return new TimerTask() + { + @Override + public void run() + { + // 远程查询操作地点 + operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp())); + SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog); + } + }; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/DynamicSqlSessionFactory.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/DynamicSqlSessionFactory.java new file mode 100644 index 0000000..b9d493e --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/DynamicSqlSessionFactory.java @@ -0,0 +1,47 @@ +package com.ruoyi.framework.manager.factory; + +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.service.mybatis.CreateSqlSessionFactory; +import com.ruoyi.framework.config.properties.DynamicDataSourceProperties; +import com.ruoyi.framework.datasource.DynamicSqlSessionTemplate; +import com.ruoyi.framework.manager.DataSourceManager; + +@Component +public class DynamicSqlSessionFactory { + + @Autowired + CreateSqlSessionFactory createSqlSessionFactory; + + @Autowired + DynamicDataSourceProperties dataSourceProperties; + + @Autowired + DataSourceManager dataSourceManager; + + @Bean(name = "sqlSessionTemplate") + public DynamicSqlSessionTemplate sqlSessionTemplate(Environment env) throws Exception { + Map sqlSessionFactoryMap = new HashMap<>(); + Map targetDataSources = dataSourceManager.getDataSourcesMap(); + for (Map.Entry entry : targetDataSources.entrySet()) { + SqlSessionFactory sessionFactory = createSqlSessionFactory.createSqlSessionFactory(env, entry.getValue()); + sqlSessionFactoryMap.put(entry.getKey(), sessionFactory); + } + SqlSessionFactory factoryMaster = sqlSessionFactoryMap.get(dataSourceProperties.getPrimaryStorageBucket()); + if (factoryMaster == null) { + throw new RuntimeException("找不到主库配置" + dataSourceProperties.getPrimaryStorageBucket()); + } + DynamicSqlSessionTemplate customSqlSessionTemplate = new DynamicSqlSessionTemplate(factoryMaster); + customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap); + return customSqlSessionTemplate; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/mybatis/CustomDatabaseIdProvider.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/mybatis/CustomDatabaseIdProvider.java new file mode 100644 index 0000000..09f0886 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/mybatis/CustomDatabaseIdProvider.java @@ -0,0 +1,55 @@ +package com.ruoyi.framework.mybatis; + +import java.sql.SQLException; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.apache.ibatis.mapping.DatabaseIdProvider; + +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.framework.manager.DataSourceManager; + +/** + * 自定义 DatabaseIdProvider: + * - 与 MyBatis 兼容的扩展点,用于为不同数据库返回 databaseId。 + * - 解决 openGauss 的 DatabaseProductName 返回 "PostgreSQL" 无法区分的问题。 + * + * 规则: + * 1) 先取 DatabaseProductName(与 MyBatis 一致)。 + * 2) 若为 PostgreSQL,再用 ProductVersion / DriverName / 可选的 select version() 轻量判断一次 openGauss。 + * 3) 仅在构建 SqlSessionFactory 时调用一次,不引入运行期开销。 + */ +public class CustomDatabaseIdProvider implements DatabaseIdProvider { + + @Override + public void setProperties(Properties p) { + // 无需额外属性 + } + + @Override + public String getDatabaseId(DataSource dataSource) throws SQLException { + // 先尝试从缓存复用:避免重复探测,兼容动态数据源 + try { + DataSourceManager dsm = SpringUtils.getBean(DataSourceManager.class); + String key = dsm.findKeyByDataSource(dataSource); + if (key != null) { + String cached = dsm.getDatabaseId(key); + if (cached != null) return cached; + // 缓存缺失:计算并写回缓存,再返回 + String computed = dsm.computeAndCacheDatabaseId(key, dataSource); + if (computed != null) return computed; + } else { + // 缓存缺失且 key 未知:计算但不写回(无法索引),直接返回 + return dsm.computeDatabaseIdForDataSource(dataSource); + } + } catch (Exception ignore) { /* Spring 尚未就绪或非容器内调用时,忽略 */ } + // 容器未就绪时的兜底:直接打开连接由 DataSourceManager 再次计算(会在 SqlSessionFactory 创建期间运行) + try { + DataSourceManager dsm = SpringUtils.getBean(DataSourceManager.class); + return dsm.computeDatabaseIdForDataSource(dataSource); + } catch (Exception e) { + throw new SQLException("Unable to resolve databaseId", e); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java new file mode 100644 index 0000000..6c776ce --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java @@ -0,0 +1,28 @@ +package com.ruoyi.framework.security.context; + +import org.springframework.security.core.Authentication; + +/** + * 身份验证信息 + * + * @author ruoyi + */ +public class AuthenticationContextHolder +{ + private static final ThreadLocal contextHolder = new ThreadLocal<>(); + + public static Authentication getContext() + { + return contextHolder.get(); + } + + public static void setContext(Authentication context) + { + contextHolder.set(context); + } + + public static void clearContext() + { + contextHolder.remove(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java new file mode 100644 index 0000000..5472f3d --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java @@ -0,0 +1,27 @@ +package com.ruoyi.framework.security.context; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import com.ruoyi.common.core.text.Convert; + +/** + * 权限信息 + * + * @author ruoyi + */ +public class PermissionContextHolder +{ + private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT"; + + public static void setContext(String permission) + { + RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission, + RequestAttributes.SCOPE_REQUEST); + } + + public static String getContext() + { + return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES, + RequestAttributes.SCOPE_REQUEST)); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 0000000..9d2f494 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,44 @@ +package com.ruoyi.framework.security.filter; + +import java.io.IOException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.TokenService; + +/** + * token过滤器 验证token有效性 + * + * @author ruoyi + */ +@Component +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter +{ + @Autowired + private TokenService tokenService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) + { + tokenService.verifyToken(loginUser); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + chain.doFilter(request, response); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java new file mode 100644 index 0000000..e1789f8 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java @@ -0,0 +1,34 @@ +package com.ruoyi.framework.security.handle; + +import java.io.IOException; +import java.io.Serializable; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * 认证失败处理类 返回未授权 + * + * @author ruoyi + */ +@Component +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable +{ + private static final long serialVersionUID = -8970718410437077606L; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) + throws IOException + { + int code = HttpStatus.UNAUTHORIZED; + String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg))); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java new file mode 100644 index 0000000..82b68a8 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -0,0 +1,55 @@ +package com.ruoyi.framework.security.handle; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.web.service.TokenService; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 自定义退出处理类 返回成功 + * + * @author ruoyi + */ +@Configuration +public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { + @Autowired + private TokenService tokenService; + + /** + * 退出处理 + * + * @return + */ + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser)) { + String userName = loginUser.getUsername(); + // 删除用户缓存记录 + tokenService.delLoginUser(loginUser.getToken()); + // 记录用户退出日志 + AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, + MessageUtils.message("user.logout.success"))); + } + ServletUtils.renderString(response, + JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success")))); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java new file mode 100644 index 0000000..63b03da --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java @@ -0,0 +1,240 @@ +package com.ruoyi.framework.web.domain; + +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import com.ruoyi.common.utils.Arith; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.framework.web.domain.server.Cpu; +import com.ruoyi.framework.web.domain.server.Jvm; +import com.ruoyi.framework.web.domain.server.Mem; +import com.ruoyi.framework.web.domain.server.Sys; +import com.ruoyi.framework.web.domain.server.SysFile; +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.CentralProcessor.TickType; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.software.os.FileSystem; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.Util; + +/** + * 服务器相关信息 + * + * @author ruoyi + */ +public class Server +{ + private static final int OSHI_WAIT_SECOND = 1000; + + /** + * CPU相关信息 + */ + private Cpu cpu = new Cpu(); + + /** + * 內存相关信息 + */ + private Mem mem = new Mem(); + + /** + * JVM相关信息 + */ + private Jvm jvm = new Jvm(); + + /** + * 服务器相关信息 + */ + private Sys sys = new Sys(); + + /** + * 磁盘相关信息 + */ + private List sysFiles = new LinkedList(); + + public Cpu getCpu() + { + return cpu; + } + + public void setCpu(Cpu cpu) + { + this.cpu = cpu; + } + + public Mem getMem() + { + return mem; + } + + public void setMem(Mem mem) + { + this.mem = mem; + } + + public Jvm getJvm() + { + return jvm; + } + + public void setJvm(Jvm jvm) + { + this.jvm = jvm; + } + + public Sys getSys() + { + return sys; + } + + public void setSys(Sys sys) + { + this.sys = sys; + } + + public List getSysFiles() + { + return sysFiles; + } + + public void setSysFiles(List sysFiles) + { + this.sysFiles = sysFiles; + } + + public void copyTo() throws Exception + { + SystemInfo si = new SystemInfo(); + HardwareAbstractionLayer hal = si.getHardware(); + + setCpuInfo(hal.getProcessor()); + + setMemInfo(hal.getMemory()); + + setSysInfo(); + + setJvmInfo(); + + setSysFiles(si.getOperatingSystem()); + } + + /** + * 设置CPU信息 + */ + private void setCpuInfo(CentralProcessor processor) + { + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + Util.sleep(OSHI_WAIT_SECOND); + long[] ticks = processor.getSystemCpuLoadTicks(); + long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; + long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; + long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; + long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; + long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; + long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; + long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; + long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; + long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; + cpu.setCpuNum(processor.getLogicalProcessorCount()); + cpu.setTotal(totalCpu); + cpu.setSys(cSys); + cpu.setUsed(user); + cpu.setWait(iowait); + cpu.setFree(idle); + } + + /** + * 设置内存信息 + */ + private void setMemInfo(GlobalMemory memory) + { + mem.setTotal(memory.getTotal()); + mem.setUsed(memory.getTotal() - memory.getAvailable()); + mem.setFree(memory.getAvailable()); + } + + /** + * 设置服务器信息 + */ + private void setSysInfo() + { + Properties props = System.getProperties(); + sys.setComputerName(IpUtils.getHostName()); + sys.setComputerIp(IpUtils.getHostIp()); + sys.setOsName(props.getProperty("os.name")); + sys.setOsArch(props.getProperty("os.arch")); + sys.setUserDir(props.getProperty("user.dir")); + } + + /** + * 设置Java虚拟机 + */ + private void setJvmInfo() throws UnknownHostException + { + Properties props = System.getProperties(); + jvm.setTotal(Runtime.getRuntime().totalMemory()); + jvm.setMax(Runtime.getRuntime().maxMemory()); + jvm.setFree(Runtime.getRuntime().freeMemory()); + jvm.setVersion(props.getProperty("java.version")); + jvm.setHome(props.getProperty("java.home")); + } + + /** + * 设置磁盘信息 + */ + private void setSysFiles(OperatingSystem os) + { + FileSystem fileSystem = os.getFileSystem(); + List fsArray = fileSystem.getFileStores(); + for (OSFileStore fs : fsArray) + { + long free = fs.getUsableSpace(); + long total = fs.getTotalSpace(); + long used = total - free; + SysFile sysFile = new SysFile(); + sysFile.setDirName(fs.getMount()); + sysFile.setSysTypeName(fs.getType()); + sysFile.setTypeName(fs.getName()); + sysFile.setTotal(convertFileSize(total)); + sysFile.setFree(convertFileSize(free)); + sysFile.setUsed(convertFileSize(used)); + sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100)); + sysFiles.add(sysFile); + } + } + + /** + * 字节转换 + * + * @param size 字节大小 + * @return 转换后值 + */ + public String convertFileSize(long size) + { + long kb = 1024; + long mb = kb * 1024; + long gb = mb * 1024; + if (size >= gb) + { + return String.format("%.1f GB", (float) size / gb); + } + else if (size >= mb) + { + float f = (float) size / mb; + return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); + } + else if (size >= kb) + { + float f = (float) size / kb; + return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); + } + else + { + return String.format("%d B", size); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java new file mode 100644 index 0000000..a13a66c --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java @@ -0,0 +1,101 @@ +package com.ruoyi.framework.web.domain.server; + +import com.ruoyi.common.utils.Arith; + +/** + * CPU相关信息 + * + * @author ruoyi + */ +public class Cpu +{ + /** + * 核心数 + */ + private int cpuNum; + + /** + * CPU总的使用率 + */ + private double total; + + /** + * CPU系统使用率 + */ + private double sys; + + /** + * CPU用户使用率 + */ + private double used; + + /** + * CPU当前等待率 + */ + private double wait; + + /** + * CPU当前空闲率 + */ + private double free; + + public int getCpuNum() + { + return cpuNum; + } + + public void setCpuNum(int cpuNum) + { + this.cpuNum = cpuNum; + } + + public double getTotal() + { + return Arith.round(Arith.mul(total, 100), 2); + } + + public void setTotal(double total) + { + this.total = total; + } + + public double getSys() + { + return Arith.round(Arith.mul(sys / total, 100), 2); + } + + public void setSys(double sys) + { + this.sys = sys; + } + + public double getUsed() + { + return Arith.round(Arith.mul(used / total, 100), 2); + } + + public void setUsed(double used) + { + this.used = used; + } + + public double getWait() + { + return Arith.round(Arith.mul(wait / total, 100), 2); + } + + public void setWait(double wait) + { + this.wait = wait; + } + + public double getFree() + { + return Arith.round(Arith.mul(free / total, 100), 2); + } + + public void setFree(double free) + { + this.free = free; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java new file mode 100644 index 0000000..1fdc6ac --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java @@ -0,0 +1,130 @@ +package com.ruoyi.framework.web.domain.server; + +import java.lang.management.ManagementFactory; +import com.ruoyi.common.utils.Arith; +import com.ruoyi.common.utils.DateUtils; + +/** + * JVM相关信息 + * + * @author ruoyi + */ +public class Jvm +{ + /** + * 当前JVM占用的内存总数(M) + */ + private double total; + + /** + * JVM最大可用内存总数(M) + */ + private double max; + + /** + * JVM空闲内存(M) + */ + private double free; + + /** + * JDK版本 + */ + private String version; + + /** + * JDK路径 + */ + private String home; + + public double getTotal() + { + return Arith.div(total, (1024 * 1024), 2); + } + + public void setTotal(double total) + { + this.total = total; + } + + public double getMax() + { + return Arith.div(max, (1024 * 1024), 2); + } + + public void setMax(double max) + { + this.max = max; + } + + public double getFree() + { + return Arith.div(free, (1024 * 1024), 2); + } + + public void setFree(double free) + { + this.free = free; + } + + public double getUsed() + { + return Arith.div(total - free, (1024 * 1024), 2); + } + + public double getUsage() + { + return Arith.mul(Arith.div(total - free, total, 4), 100); + } + + /** + * 获取JDK名称 + */ + public String getName() + { + return ManagementFactory.getRuntimeMXBean().getVmName(); + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getHome() + { + return home; + } + + public void setHome(String home) + { + this.home = home; + } + + /** + * JDK启动时间 + */ + public String getStartTime() + { + return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate()); + } + + /** + * JDK运行时间 + */ + public String getRunTime() + { + return DateUtils.timeDistance(DateUtils.getNowDate(), DateUtils.getServerStartDate()); + } + + /** + * 运行参数 + */ + public String getInputArgs() + { + return ManagementFactory.getRuntimeMXBean().getInputArguments().toString(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java new file mode 100644 index 0000000..13eec52 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java @@ -0,0 +1,61 @@ +package com.ruoyi.framework.web.domain.server; + +import com.ruoyi.common.utils.Arith; + +/** + * 內存相关信息 + * + * @author ruoyi + */ +public class Mem +{ + /** + * 内存总量 + */ + private double total; + + /** + * 已用内存 + */ + private double used; + + /** + * 剩余内存 + */ + private double free; + + public double getTotal() + { + return Arith.div(total, (1024 * 1024 * 1024), 2); + } + + public void setTotal(long total) + { + this.total = total; + } + + public double getUsed() + { + return Arith.div(used, (1024 * 1024 * 1024), 2); + } + + public void setUsed(long used) + { + this.used = used; + } + + public double getFree() + { + return Arith.div(free, (1024 * 1024 * 1024), 2); + } + + public void setFree(long free) + { + this.free = free; + } + + public double getUsage() + { + return Arith.mul(Arith.div(used, total, 4), 100); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java new file mode 100644 index 0000000..45d64d9 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java @@ -0,0 +1,84 @@ +package com.ruoyi.framework.web.domain.server; + +/** + * 系统相关信息 + * + * @author ruoyi + */ +public class Sys +{ + /** + * 服务器名称 + */ + private String computerName; + + /** + * 服务器Ip + */ + private String computerIp; + + /** + * 项目路径 + */ + private String userDir; + + /** + * 操作系统 + */ + private String osName; + + /** + * 系统架构 + */ + private String osArch; + + public String getComputerName() + { + return computerName; + } + + public void setComputerName(String computerName) + { + this.computerName = computerName; + } + + public String getComputerIp() + { + return computerIp; + } + + public void setComputerIp(String computerIp) + { + this.computerIp = computerIp; + } + + public String getUserDir() + { + return userDir; + } + + public void setUserDir(String userDir) + { + this.userDir = userDir; + } + + public String getOsName() + { + return osName; + } + + public void setOsName(String osName) + { + this.osName = osName; + } + + public String getOsArch() + { + return osArch; + } + + public void setOsArch(String osArch) + { + this.osArch = osArch; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java new file mode 100644 index 0000000..1320cde --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java @@ -0,0 +1,114 @@ +package com.ruoyi.framework.web.domain.server; + +/** + * 系统文件相关信息 + * + * @author ruoyi + */ +public class SysFile +{ + /** + * 盘符路径 + */ + private String dirName; + + /** + * 盘符类型 + */ + private String sysTypeName; + + /** + * 文件类型 + */ + private String typeName; + + /** + * 总大小 + */ + private String total; + + /** + * 剩余大小 + */ + private String free; + + /** + * 已经使用量 + */ + private String used; + + /** + * 资源的使用率 + */ + private double usage; + + public String getDirName() + { + return dirName; + } + + public void setDirName(String dirName) + { + this.dirName = dirName; + } + + public String getSysTypeName() + { + return sysTypeName; + } + + public void setSysTypeName(String sysTypeName) + { + this.sysTypeName = sysTypeName; + } + + public String getTypeName() + { + return typeName; + } + + public void setTypeName(String typeName) + { + this.typeName = typeName; + } + + public String getTotal() + { + return total; + } + + public void setTotal(String total) + { + this.total = total; + } + + public String getFree() + { + return free; + } + + public void setFree(String free) + { + this.free = free; + } + + public String getUsed() + { + return used; + } + + public void setUsed(String used) + { + this.used = used; + } + + public double getUsage() + { + return usage; + } + + public void setUsage(double usage) + { + this.usage = usage; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..2df2ed3 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java @@ -0,0 +1,137 @@ +package com.ruoyi.framework.web.exception; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.DemoModeException; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.html.EscapeUtil; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * 全局异常处理器 + * + * @author ruoyi + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 权限校验异常 + */ + @ExceptionHandler(AccessDeniedException.class) + public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权"); + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return AjaxResult.error(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); + } + + /** + * 请求路径中缺少必需的路径变量 + */ + @ExceptionHandler(MissingPathVariableException.class) + public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); + } + + /** + * 请求参数类型不匹配 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, + HttpServletRequest request) { + String requestURI = request.getRequestURI(); + String value = Convert.toStr(e.getValue()); + if (StringUtils.isNotEmpty(value)) { + value = EscapeUtil.clean(value); + } + log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), + e.getRequiredType().getName(), value)); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public AjaxResult handleException(Exception e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public AjaxResult handleBindException(BindException e) { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 演示模式异常 + */ + @ExceptionHandler(DemoModeException.class) + public AjaxResult handleDemoModeException(DemoModeException e) { + return AjaxResult.error("演示模式,不允许操作"); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java new file mode 100644 index 0000000..65f1aea --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java @@ -0,0 +1,130 @@ +package com.ruoyi.framework.web.service; + +import java.util.Set; + +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.security.service.IPermissionService; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.security.context.PermissionContextHolder; + +/** + * RuoYi首创 自定义权限实现,ss取自SpringSecurity首字母 + * Geek改进 将自定义权限实现形成规范,提高扩展性 + * + * @author ruoyi&&Dftre + */ +@Service("ss") +public class PermissionService implements IPermissionService { + /** 所有权限标识 */ + private static final String ALL_PERMISSION = "*:*:*"; + + /** 管理员角色权限标识 */ + private static final String SUPER_ADMIN = "admin"; + + private static final String ROLE_DELIMETER = ","; + + private static final String PERMISSION_DELIMETER = ","; + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermi(String permission) { + if (StringUtils.isEmpty(permission)) { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) { + return false; + } + PermissionContextHolder.setContext(permission); + return hasPermissions(loginUser.getPermissions(), permission); + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + public boolean hasAnyPermi(String permissions) { + if (StringUtils.isEmpty(permissions)) { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) { + return false; + } + PermissionContextHolder.setContext(permissions); + Set authorities = loginUser.getPermissions(); + for (String permission : permissions.split(PERMISSION_DELIMETER)) { + if (permission != null && hasPermissions(authorities, permission)) { + return true; + } + } + return false; + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色字符串 + * @return 用户是否具备某角色 + */ + public boolean hasRole(String role) { + if (StringUtils.isEmpty(role)) { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) { + return false; + } + for (SysRole sysRole : loginUser.getUser().getRoles()) { + String roleKey = sysRole.getRoleKey(); + if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) { + return true; + } + } + return false; + } + + /** + * 验证用户是否具有以下任意一个角色 + * + * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + public boolean hasAnyRoles(String roles) { + if (StringUtils.isEmpty(roles)) { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) { + return false; + } + for (String role : roles.split(ROLE_DELIMETER)) { + if (hasRole(role)) { + return true; + } + } + return false; + } + + /** + * 判断是否包含权限 + * + * @param permissions 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + private boolean hasPermissions(Set permissions, String permission) { + return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java new file mode 100644 index 0000000..d53b69a --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java @@ -0,0 +1,187 @@ +package com.ruoyi.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.exception.user.BlackListException; +import com.ruoyi.common.exception.user.CaptchaException; +import com.ruoyi.common.exception.user.CaptchaExpireException; +import com.ruoyi.common.exception.user.UserNotExistsException; +import com.ruoyi.common.exception.user.UserPasswordNotMatchException; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.security.context.AuthenticationContextHolder; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysUserService; + +import jakarta.annotation.Resource; + +/** + * 登录校验方法 + * + * @author ruoyi + */ +@Component +public class SysLoginService +{ + @Autowired + private TokenService tokenService; + + @Resource + private AuthenticationManager authenticationManager; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + @Autowired + private SysPasswordService passwordService; + + /** + * 登录验证 + * + * @param username 用户名 + * @param password 密码 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public String login(String username, String password, String code, String uuid) + { + // 验证码校验 + validateCaptcha(username, code, uuid); + // 登录前置校验 + loginPreCheck(username, password); + String ip = IpUtils.getIpAddr(); + // 验证 IP 是否被封锁 + passwordService.validateIp(ip); + // 用户验证 + Authentication authentication = null; + try + { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); + AuthenticationContextHolder.setContext(authenticationToken); + // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername + authentication = authenticationManager.authenticate(authenticationToken); + } + catch (Exception e) + { + if (e instanceof BadCredentialsException) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + else + { + passwordService.incrementIpFailCount(ip); + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); + throw new ServiceException(e.getMessage()); + } + } + finally + { + AuthenticationContextHolder.clearContext(); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + recordLoginInfo(loginUser.getUserId()); + // 生成token + return tokenService.createToken(loginUser); + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + String captcha = CacheUtils.get(CacheConstants.CAPTCHA_CODE_KEY, StringUtils.nvl(uuid, ""), String.class); + CacheUtils.removeIfPresent(CacheConstants.CAPTCHA_CODE_KEY, StringUtils.nvl(uuid, "")); + if (captcha == null) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); + throw new CaptchaException(); + } + } + } + + /** + * 登录前置校验 + * @param username 用户名 + * @param password 用户密码 + */ + public void loginPreCheck(String username, String password) + { + // 用户名或密码为空 错误 + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null"))); + throw new UserNotExistsException(); + } + // 密码如果不在指定范围内 错误 + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + // 用户名不在指定范围内 错误 + if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + // IP黑名单校验 + String blackStr = configService.selectConfigByKey("sys.login.blackIPList"); + if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked"))); + throw new BlackListException(); + } + } + + /** + * 记录登录信息 + * + * @param userId 用户ID + */ + public void recordLoginInfo(Long userId) + { + SysUser sysUser = new SysUser(); + sysUser.setUserId(userId); + sysUser.setDeptId(0L); + sysUser.setLoginIp(IpUtils.getIpAddr()); + sysUser.setLoginDate(DateUtils.getNowDate()); + userService.updateUserProfile(sysUser); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java new file mode 100644 index 0000000..cdb8316 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java @@ -0,0 +1,125 @@ +package com.ruoyi.framework.web.service; + +import com.ruoyi.common.exception.user.IpRetryLimitExceedException; +import com.ruoyi.common.utils.ip.IpUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.Cache; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.exception.user.UserPasswordNotMatchException; +import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.security.context.AuthenticationContextHolder; + +import java.util.concurrent.TimeUnit; + +/** + * 登录密码方法 + * + * @author ruoyi + */ +@Component +public class SysPasswordService +{ + + @Value(value = "${user.password.maxRetryCount}") + private int maxRetryCount; + + @Value(value = "${user.password.lockTime}") + private int lockTime; + + @Value(value = "${user.ip.maxRetryCount:15}") + public int maxIpRetryCount; + + @Value(value = "${user.ip.lockTime:15}") + public int ipLockTime; + /** + * 登录账户密码错误次数缓存键名 + * + * @return 缓存Cache + */ + private Cache getCache() + { + return CacheUtils.getCache(CacheConstants.PWD_ERR_CNT_KEY); + } + + private Cache getIpCache() { + return CacheUtils.getCache(CacheConstants.IP_ERR_CNT_KEY); + } + + public void validate(SysUser user) + { + Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext(); + String username = usernamePasswordAuthenticationToken.getName(); + String password = usernamePasswordAuthenticationToken.getCredentials().toString(); + + String ip = IpUtils.getIpAddr(); + validateIp(ip); + Integer retryCount = getCache().get(username, Integer.class); + if (retryCount == null) + { + retryCount = 0; + } + if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, + MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount, lockTime))); + throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime); + } + if (!matches(user, password)) + { + retryCount = retryCount + 1; + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, + MessageUtils.message("user.password.retry.limit.count", retryCount))); + CacheUtils.put(CacheConstants.PWD_ERR_CNT_KEY,username,retryCount,lockTime,TimeUnit.MINUTES); + throw new UserPasswordNotMatchException(); + } + else + { + clearLoginRecordCache(username); + } + } + + public void validateIp(String ip) + { + Integer ipRetryCount = getIpCache().get(ip, Integer.class); + if (ipRetryCount == null) + { + ipRetryCount = 0; + } + + if (ipRetryCount >= maxIpRetryCount) + { + throw new IpRetryLimitExceedException(maxIpRetryCount, ipLockTime); + } + } + + public void incrementIpFailCount(String ip) + { + Integer ipRetryCount = getIpCache().get(ip, Integer.class); + if (ipRetryCount == null) + { + ipRetryCount = 0; + } + ipRetryCount += 1; + CacheUtils.put(CacheConstants.IP_ERR_CNT_KEY,ip,ipRetryCount,ipLockTime,TimeUnit.MINUTES); + } + + public boolean matches(SysUser user, String rawPassword) + { + return SecurityUtils.matchesPassword(rawPassword, user.getPassword()); + } + + public void clearLoginRecordCache(String loginName) + { + getCache().evictIfPresent(loginName); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java new file mode 100644 index 0000000..08e2381 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java @@ -0,0 +1,76 @@ +package com.ruoyi.framework.web.service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysMenuService; +import com.ruoyi.system.service.ISysRoleService; + +/** + * 用户权限处理 + * + * @author ruoyi + */ +@Component +public class SysPermissionService { + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysMenuService menuService; + + /** + * 获取角色数据权限 + * + * @param user 用户信息 + * @return 角色权限信息 + */ + public Set getRolePermission(SysUser user) { + Set roles = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) { + roles.add("admin"); + } else { + roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId())); + } + return roles; + } + + /** + * 获取菜单数据权限 + * + * @param user 用户信息 + * @return 菜单权限信息 + */ + public Set getMenuPermission(SysUser user) { + Set perms = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) { + perms.add("*:*:*"); + } else { + List roles = user.getRoles(); + if (!CollectionUtils.isEmpty(roles)) { + // 多角色设置permissions属性,以便数据权限匹配权限 + for (SysRole role : roles) { + if (StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL)) { + Set rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId()); + role.setPermissions(rolePerms); + perms.addAll(rolePerms); + } + } + } else { + perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); + } + } + return perms; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java new file mode 100644 index 0000000..6b80994 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java @@ -0,0 +1,110 @@ +package com.ruoyi.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.RegisterBody; +import com.ruoyi.common.exception.user.CaptchaException; +import com.ruoyi.common.exception.user.CaptchaExpireException; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 注册校验方法 + * + * @author ruoyi + */ +@Component +public class SysRegisterService +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + /** + * 注册 + */ + public String register(RegisterBody registerBody) + { + String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(); + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + // 验证码开关 + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + validateCaptcha(username, registerBody.getCode(), registerBody.getUuid()); + } + if (StringUtils.isEmpty(username)) + { + msg = "用户名不能为空"; + } + else if (StringUtils.isEmpty(password)) + { + msg = "用户密码不能为空"; + } + else if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + msg = "账户长度必须在2到20个字符之间"; + } + else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + msg = "密码长度必须在5到20个字符之间"; + } + else if (!userService.checkUserNameUnique(sysUser)) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + } + else + { + sysUser.setNickName(username); + sysUser.setPassword(SecurityUtils.encryptPassword(password)); + boolean regFlag = userService.registerUser(sysUser); + if (!regFlag) + { + msg = "注册失败,请联系系统管理人员"; + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"))); + } + } + return msg; + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + String captcha = CacheUtils.get(CacheConstants.CAPTCHA_CODE_KEY, StringUtils.nvl(uuid, ""), String.class); + CacheUtils.removeIfPresent(CacheConstants.CAPTCHA_CODE_KEY, StringUtils.nvl(uuid, "")); + if (captcha == null) + { + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + throw new CaptchaException(); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java new file mode 100644 index 0000000..0becf79 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java @@ -0,0 +1,202 @@ +package com.ruoyi.framework.web.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.crypto.SecretKey; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.AddressUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.common.utils.uuid.IdUtils; + +import eu.bitwalker.useragentutils.UserAgent; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletRequest; + +/** + * token验证处理 + * + * @author ruoyi + */ +@Component +public class TokenService { + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + // 令牌秘钥 + @Value("${token.secret}") + private String secret; + + // 令牌有效期(默认30分钟) + @Value("${token.expireTime}") + private int expireTime; + + protected static final long MILLIS_SECOND = 1000; + + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L; + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(HttpServletRequest request) { + // 获取请求携带的令牌 + String token = request.getHeader(header); + + return getLoginUser(token); + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(String token) { + if (StringUtils.isNotEmpty(token)) { + try { + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) { + token = token.replace(Constants.TOKEN_PREFIX, ""); + } + Claims claims = parseToken(token); + // 解析对应的权限以及用户信息 + String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); + LoginUser user = CacheUtils.get(CacheConstants.LOGIN_TOKEN_KEY, uuid, LoginUser.class); + return user; + } catch (Exception e) { + } + } + return null; + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser(LoginUser loginUser) { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) { + refreshToken(loginUser); + } + } + + /** + * 删除用户身份信息 + */ + public void delLoginUser(String token) { + if (StringUtils.isNotEmpty(token)) { + CacheUtils.removeIfPresent(CacheConstants.LOGIN_TOKEN_KEY, token); + } + } + + /** + * 创建令牌 + * + * @param loginUser 用户信息 + * @return 令牌 + */ + public String createToken(LoginUser loginUser) { + String token = IdUtils.fastUUID(); + loginUser.setToken(token); + setUserAgent(loginUser); + refreshToken(loginUser); + Map claims = new HashMap<>(); + claims.put(Constants.LOGIN_USER_KEY, token); + return createToken(claims); + } + + /** + * 验证令牌有效期,相差不足20分钟,自动刷新缓存 + * + * @param loginUser + * @return 令牌 + */ + public void verifyToken(LoginUser loginUser) { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) { + refreshToken(loginUser); + } + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken(LoginUser loginUser) { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + CacheUtils.put(CacheConstants.LOGIN_TOKEN_KEY, loginUser.getToken(), loginUser, expireTime, TimeUnit.MINUTES); + } + + /** + * 设置用户代理信息 + * + * @param loginUser 登录信息 + */ + public void setUserAgent(LoginUser loginUser) { + UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + String ip = IpUtils.getIpAddr(); + loginUser.setIpaddr(ip); + loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); + loginUser.setBrowser(userAgent.getBrowser().getName()); + loginUser.setOs(userAgent.getOperatingSystem().getName()); + } + + /** + * 从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + private String createToken(Map claims) { + SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); + String token = Jwts.builder() + .claims(claims) + .signWith(key) + .compact(); + return token; + } + + /** + * 从令牌中获取数据声明 + * + * @param token 令牌 + * @return 数据声明 + */ + private Claims parseToken(String token) { + SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); + return Jwts.parser() + .verifyWith(key) + .build() + .parseSignedClaims(token) + .getPayload(); + } + + /** + * 从令牌中获取用户名 + * + * @param token 令牌 + * @return 用户名 + */ + public String getUsernameFromToken(String token) { + Claims claims = parseToken(token); + return claims.getSubject(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..84c6cdd --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java @@ -0,0 +1,72 @@ +package com.ruoyi.framework.web.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.UserStatus; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysUserService; + +/** + * 用户验证处理 + * + * @author ruoyi + */ +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); + + @Autowired + private ISysUserService userService; + + @Autowired + private SysPasswordService passwordService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private TokenService tokenService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + SysUser user = userService.selectUserByUserName(username); + if (StringUtils.isNull(user)) { + log.info("登录用户:{} 不存在.", username); + throw new ServiceException("登录用户:" + username + " 不存在"); + } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { + log.info("登录用户:{} 已被删除.", username); + throw new ServiceException("对不起,您的账号:" + username + " 已被删除"); + } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { + log.info("登录用户:{} 已被停用.", username); + throw new ServiceException("对不起,您的账号:" + username + " 已停用"); + } + + passwordService.validate(user); + + return createLoginUser(user); + } + + public UserDetails createLoginUser(SysUser user) { + return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + } + + public void refreshLoginUser(Long userId) { + SysUser user = userService.selectUserById(userId); + LoginUser loginUser = SecurityUtils.getLoginUser(); + loginUser.setUserId(user.getUserId()); + loginUser.setDeptId(user.getDeptId()); + loginUser.setUser(user); + loginUser.setPermissions(permissionService.getMenuPermission(user)); + tokenService.refreshToken(loginUser); + } +} diff --git a/ruoyi-models/pom.xml b/ruoyi-models/pom.xml new file mode 100644 index 0000000..2470712 --- /dev/null +++ b/ruoyi-models/pom.xml @@ -0,0 +1,97 @@ + + + + ruoyi + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-models + + + 中间件 + + + + + + + com.ruoyi.geekxd + ruoyi-quartz + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-generator + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-online + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-message + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-form + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-flowable + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-tfa-phone + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-tfa-email + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-ngtools + ${ruoyi.version} + + + + + + + + ruoyi-models-starter + ruoyi-generator + ruoyi-quartz + ruoyi-online + ruoyi-message + ruoyi-form + ruoyi-flowable + ruoyi-ngtools + + pom + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-flowable/pom.xml b/ruoyi-models/ruoyi-flowable/pom.xml new file mode 100644 index 0000000..146981e --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/pom.xml @@ -0,0 +1,64 @@ + + + + ruoyi-models + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-flowable + + + form表单 + + + + + + + com.ruoyi.geekxd + ruoyi-system + + + + + org.flowable + flowable-spring-boot-starter + 7.1.0 + + + org.flowable + flowable-spring-security + + + + + + com.googlecode.aviator + aviator + 5.3.3 + + + + org.graalvm.js + js + 24.2.1 + pom + + + org.graalvm.js + js-scriptengine + 24.2.1 + runtime + + + + com.ruoyi.geekxd + ruoyi-form + + + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/constant/ProcessConstants.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/constant/ProcessConstants.java new file mode 100644 index 0000000..9967434 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/constant/ProcessConstants.java @@ -0,0 +1,80 @@ +package com.ruoyi.flowable.common.constant; + +/** + * 流程常量信息 + * + * @author Tony + * @date 2021/4/17 22:46 + */ +public class ProcessConstants { + + /** + * 动态数据 + */ + public static final String DYNAMIC = "dynamic"; + + /** + * 固定任务接收 + */ + public static final String FIXED = "fixed"; + + /** + * 单个审批人 + */ + public static final String ASSIGNEE = "assignee"; + + + /** + * 候选人 + */ + public static final String CANDIDATE_USERS = "candidateUsers"; + + + /** + * 审批组 + */ + public static final String CANDIDATE_GROUPS = "candidateGroups"; + + /** + * 单个审批人 + */ + public static final String PROCESS_APPROVAL = "approval"; + + /** + * 会签人员 + */ + public static final String PROCESS_MULTI_INSTANCE_USER = "userList"; + + /** + * nameapace + */ + public static final String NAMASPASE = "http://flowable.org/bpmn"; + + /** + * 会签节点 + */ + public static final String PROCESS_MULTI_INSTANCE = "multiInstance"; + + /** + * 自定义属性 dataType + */ + public static final String PROCESS_CUSTOM_DATA_TYPE = "dataType"; + + /** + * 自定义属性 userType + */ + public static final String PROCESS_CUSTOM_USER_TYPE = "userType"; + + /** + * 初始化人员 + */ + public static final String PROCESS_INITIATOR = "INITIATOR"; + + + /** + * 流程跳过 + */ + public static final String FLOWABLE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED"; + + +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/enums/FlowComment.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/enums/FlowComment.java new file mode 100644 index 0000000..d677170 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/enums/FlowComment.java @@ -0,0 +1,43 @@ +package com.ruoyi.flowable.common.enums; + +/** + * 流程意见类型 + * + * @author Tony + * @date 2021/4/19 + */ +public enum FlowComment { + + /** + * 说明 + */ + NORMAL("1", "正常意见"), + REBACK("2", "退回意见"), + REJECT("3", "驳回意见"), + DELEGATE("4", "委派意见"), + ASSIGN("5", "转办意见"), + STOP("6", "终止流程"); + + /** + * 类型 + */ + private final String type; + + /** + * 说明 + */ + private final String remark; + + FlowComment(String type, String remark) { + this.type = type; + this.remark = remark; + } + + public String getType() { + return type; + } + + public String getRemark() { + return remark; + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/expand/el/BaseEl.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/expand/el/BaseEl.java new file mode 100644 index 0000000..557429b --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/expand/el/BaseEl.java @@ -0,0 +1,12 @@ +package com.ruoyi.flowable.common.expand.el; + +/** + * 扩展表达式 + * + * @author Tony + * @date 2023-03-04 09:10 + */ +public interface BaseEl { + +} + diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/expand/el/FlowEl.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/expand/el/FlowEl.java new file mode 100644 index 0000000..c9a71fa --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/expand/el/FlowEl.java @@ -0,0 +1,34 @@ +package com.ruoyi.flowable.common.expand.el; + +import org.springframework.stereotype.Component; + +import com.ruoyi.system.service.ISysDeptService; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; + + +/** + * 扩展表达式 + * + * @author Tony + * @date 2023-03-04 12:10 + */ +@Component +@Slf4j +public class FlowEl implements BaseEl { + + @Resource + private ISysDeptService sysDeptService; + + public String findDeptLeader(String name){ + log.info("开始查询表达式变量值,getName"); + return name; + } + + public String getName(String name){ + log.info("开始查询表达式变量值,getName"); + return name; + } +} + diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/config/FlowableConfig.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/config/FlowableConfig.java new file mode 100644 index 0000000..186a48e --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/config/FlowableConfig.java @@ -0,0 +1,42 @@ +package com.ruoyi.flowable.config; + +import java.util.concurrent.ThreadPoolExecutor; + +import org.flowable.engine.impl.db.DbIdGenerator; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.flowable.spring.boot.EngineConfigurationConfigurer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +public class FlowableConfig implements EngineConfigurationConfigurer { + @Override + public void configure(SpringProcessEngineConfiguration engineConfiguration) { + engineConfiguration.setActivityFontName("宋体"); + engineConfiguration.setLabelFontName("宋体"); + engineConfiguration.setAnnotationFontName("宋体"); + engineConfiguration.setIdGenerator(new DbIdGenerator()); + } + + @Bean("applicationTaskExecutor") + public ThreadPoolTaskExecutor applicationTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + // 此方法返回可用处理器的虚拟机的最大数量; 不小于1 + int core = Runtime.getRuntime().availableProcessors(); + executor.setCorePoolSize(core);// 设置核心线程数 + executor.setMaxPoolSize(core * 2 + 1);// 设置最大线程数 + executor.setKeepAliveSeconds(120);// 除核心线程外的线程存活时间 + executor.setQueueCapacity(120);// 如果传入值大于0,底层队列使用的是LinkedBlockingQueue,否则默认使用SynchronousQueue + executor.setThreadNamePrefix("thread-default-execute");// 线程名称前缀 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());// 设置拒绝策略,抛出 + // RejectedExecutionException来拒绝新任务的处理。 + // executor.setRejectedExecutionHandler(new + // ThreadPoolExecutor.CallerRunsPolicy());//设置拒绝策略,使用主线程 + // executor.setRejectedExecutionHandler(new + // ThreadPoolExecutor.DiscardPolicy());//设置拒绝策略,直接丢弃掉 + // executor.setRejectedExecutionHandler(new + // ThreadPoolExecutor.DiscardOldestPolicy());//设置拒绝策略,丢弃最早的未处理的任务请求。 + return executor; + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowDefinitionController.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowDefinitionController.java new file mode 100644 index 0000000..a8abbed --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowDefinitionController.java @@ -0,0 +1,228 @@ +package com.ruoyi.flowable.controller; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import javax.imageio.ImageIO; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.flowable.domain.SysDeployForm; +import com.ruoyi.flowable.domain.SysExpression; +import com.ruoyi.flowable.domain.dto.FlowSaveXmlVo; +import com.ruoyi.flowable.service.IFlowDefinitionService; +import com.ruoyi.flowable.service.ISysDeployFormService; +import com.ruoyi.flowable.service.ISysExpressionService; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * 工作流程定义 + *

+ * + * @author Tony + * @date 2021-04-03 + */ +@Slf4j +@Tag(name = "流程定义") +@RestController +@RequestMapping("/flowable/definition") +public class FlowDefinitionController extends BaseController { + + @Autowired + private IFlowDefinitionService flowDefinitionService; + + @Autowired + private ISysUserService userService; + + @Resource + private ISysRoleService sysRoleService; + @Resource + private ISysExpressionService sysExpressionService; + + @Resource + private ISysDeployFormService sysDeployFormService; + + @GetMapping(value = "/list") + @Operation(summary = "流程定义列表") + public TableDataInfo list( + @RequestParam Integer pageNum, + @RequestParam Integer pageSize, + @RequestParam(required = false) String name) { + return getDataTable(flowDefinitionService.list(name, pageNum, pageSize)); + } + + @Operation(summary = "导入流程文件") + @PostMapping("/import") + public AjaxResult importFile( + @RequestParam(required = false) String name, + @RequestParam(required = false) String category, + MultipartFile file) { + InputStream in = null; + try { + in = file.getInputStream(); + flowDefinitionService.importFile(name, category, in); + } catch (Exception e) { + log.error("导入失败:", e); + return AjaxResult.success(e.getMessage()); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + log.error("关闭输入流出错", e); + } + } + + return AjaxResult.success("导入成功"); + } + + @Operation(summary = "读取xml文件") + @GetMapping("/readXml/{deployId}") + public AjaxResult readXml(@PathVariable String deployId) { + try { + return flowDefinitionService.readXml(deployId); + } catch (Exception e) { + return AjaxResult.error("加载xml文件异常"); + } + + } + + @Operation(summary = "读取图片文件") + @GetMapping("/readImage/{deployId}") + public void readImage(@PathVariable String deployId, HttpServletResponse response) { + OutputStream os = null; + BufferedImage image = null; + try { + image = ImageIO.read(flowDefinitionService.readImage(deployId)); + response.setContentType("image/png"); + os = response.getOutputStream(); + if (image != null) { + ImageIO.write(image, "png", os); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (os != null) { + os.flush(); + os.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + + @Operation(summary = "保存流程设计器内的xml文件") + @Log(title = "流程定义", businessType = BusinessType.INSERT) + @PostMapping("/save") + public AjaxResult save(@RequestBody FlowSaveXmlVo vo) { + InputStream in = null; + try { + in = new ByteArrayInputStream(vo.getXml().getBytes(StandardCharsets.UTF_8)); + flowDefinitionService.importFile(vo.getName(), vo.getCategory(), in); + } catch (Exception e) { + log.error("导入失败:", e); + return AjaxResult.error(e.getMessage()); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + log.error("关闭输入流出错", e); + } + } + + return AjaxResult.success("导入成功"); + } + + @Operation(summary = "发起流程") + @Log(title = "发起流程", businessType = BusinessType.INSERT) + @PostMapping("/start/{procDefId}") + public AjaxResult start(@PathVariable String procDefId, @RequestBody Map variables) { + return flowDefinitionService.startProcessInstanceById(procDefId, variables); + } + + @Operation(summary = "激活或挂起流程定义") + @Log(title = "激活/挂起流程", businessType = BusinessType.UPDATE) + @PutMapping(value = "/updateState") + public AjaxResult updateState(@RequestParam Integer state, @RequestParam String deployId) { + flowDefinitionService.updateState(state, deployId); + return AjaxResult.success(); + } + + @Operation(summary = "删除流程") + @Log(title = "删除流程", businessType = BusinessType.DELETE) + @DeleteMapping(value = "/{deployIds}") + public AjaxResult delete(@PathVariable String[] deployIds) { + for (String deployId : deployIds) { + flowDefinitionService.delete(deployId); + } + return AjaxResult.success(); + } + + @Operation(summary = "指定流程办理人员列表") + @GetMapping("/userList") + public AjaxResult userList(SysUser user) { + List list = userService.selectUserList(user); + return AjaxResult.success(list); + } + + @Operation(summary = "指定流程办理组列表") + @GetMapping("/roleList") + public AjaxResult roleList(SysRole role) { + List list = sysRoleService.selectRoleList(role); + return AjaxResult.success(list); + } + + @Operation(summary = "指定流程达式列表") + @GetMapping("/expList") + public AjaxResult expList(SysExpression sysExpression) { + List list = sysExpressionService.selectSysExpressionList(sysExpression); + return AjaxResult.success(list); + } + + /** + * 挂载流程表单 + */ + @Log(title = "流程表单", businessType = BusinessType.INSERT) + @PostMapping("/addDeployForm") + public AjaxResult addDeployForm(@RequestBody SysDeployForm sysDeployForm) { + return toAjax(sysDeployFormService.insertSysDeployForm(sysDeployForm)); + } + +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowInstanceController.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowInstanceController.java new file mode 100644 index 0000000..f0c4fb9 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowInstanceController.java @@ -0,0 +1,72 @@ +package com.ruoyi.flowable.controller; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.flowable.domain.vo.FlowTaskVo; +import com.ruoyi.flowable.service.IFlowInstanceService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * 工作流流程实例管理 + *

+ * + * @author Tony + * @date 2021-04-03 + */ +@Slf4j +@Tag(name = "工作流流程实例管理") +@RestController +@RequestMapping("/flowable/instance") +public class FlowInstanceController extends BaseController { + + @Autowired + private IFlowInstanceService flowInstanceService; + + @Operation(summary = "根据流程定义id启动流程实例") + @PostMapping("/startBy/{procDefId}") + public AjaxResult startById(@PathVariable String procDefId, @RequestBody Map variables) { + return flowInstanceService.startProcessInstanceById(procDefId, variables); + + } + + @Operation(summary = "激活或挂起流程实例") + @PostMapping(value = "/updateState") + public AjaxResult updateState(@RequestParam Integer state, @RequestParam String instanceId) { + flowInstanceService.updateState(state, instanceId); + return AjaxResult.success(); + } + + @Operation(summary = "结束流程实例") + @PostMapping(value = "/stopProcessInstance") + public AjaxResult stopProcessInstance(@RequestBody FlowTaskVo flowTaskVo) { + flowInstanceService.stopProcessInstance(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "删除流程实例") + @Log(title = "删除任务", businessType = BusinessType.DELETE) + @DeleteMapping(value = "/delete/{instanceIds}") + public AjaxResult delete(@PathVariable String[] instanceIds, @RequestParam(required = false) String deleteReason) { + for (String instanceId : instanceIds) { + flowInstanceService.delete(instanceId, deleteReason); + } + return AjaxResult.success(); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowTaskController.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowTaskController.java new file mode 100644 index 0000000..fbf202b --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowTaskController.java @@ -0,0 +1,289 @@ +package com.ruoyi.flowable.controller; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import javax.imageio.ImageIO; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.flowable.domain.dto.FlowTaskDto; +import com.ruoyi.flowable.domain.vo.FlowQueryVo; +import com.ruoyi.flowable.domain.vo.FlowTaskVo; +import com.ruoyi.flowable.service.IFlowTaskService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * 工作流任务管理 + *

+ * + * @author Tony + * @date 2021-04-03 + */ +@Slf4j +@Tag(name = "工作流流程任务管理") +@RestController +@RequestMapping("/flowable/task") +public class FlowTaskController extends BaseController { + + @Autowired + private IFlowTaskService flowTaskService; + + @Operation(summary = "我发起的流程") + @GetMapping(value = "/myProcess") + public TableDataInfo myProcess(FlowQueryVo queryVo) { + List myProcess = flowTaskService.myProcess(queryVo); + return getDataTable(myProcess); + } + + @Operation(summary = "取消申请") + @Log(title = "取消申请", businessType = BusinessType.UPDATE) + @PostMapping(value = "/stopProcess") + public AjaxResult stopProcess(@RequestBody FlowTaskVo flowTaskVo) { + return flowTaskService.stopProcess(flowTaskVo); + } + + @Operation(summary = "撤回流程") + @Log(title = "撤回流程", businessType = BusinessType.UPDATE) + @PostMapping(value = "/revokeProcess") + public AjaxResult revokeProcess(@RequestBody FlowTaskVo flowTaskVo) { + return flowTaskService.revokeProcess(flowTaskVo); + } + + @Operation(summary = "获取待办列表") + @GetMapping(value = "/todoList") + public TableDataInfo todoList(FlowQueryVo queryVo) { + return getDataTable(flowTaskService.todoList(queryVo)); + } + + @Operation(summary = "获取已办任务") + @GetMapping(value = "/finishedList") + public TableDataInfo finishedList(FlowQueryVo queryVo) { + return getDataTable(flowTaskService.finishedList(queryVo)); + } + + @Operation(summary = "流程历史流转记录") + @GetMapping(value = "/flowRecord") + public AjaxResult flowRecord(String procInsId, String deployId) { + return flowTaskService.flowRecord(procInsId, deployId); + } + + @Operation(summary = "根据任务ID查询挂载的表单信息") + @GetMapping(value = "/getTaskForm") + public AjaxResult getTaskForm(String taskId) { + return flowTaskService.getTaskForm(taskId); + } + + @Operation(summary = "流程初始化表单") + @GetMapping(value = "/flowFormData") + public AjaxResult flowFormData(String deployId) { + return flowTaskService.flowFormData(deployId); + } + + @Operation(summary = "获取流程变量") + @GetMapping(value = "/processVariables/{taskId}") + public AjaxResult processVariables(@PathVariable String taskId) { + return flowTaskService.processVariables(taskId); + } + + @Operation(summary = "审批任务") + @Log(title = "审批任务", businessType = BusinessType.UPDATE) + @PostMapping(value = "/complete") + public AjaxResult complete(@RequestBody FlowTaskVo flowTaskVo) { + return flowTaskService.complete(flowTaskVo); + } + + @Operation(summary = "驳回任务") + @Log(title = "驳回任务", businessType = BusinessType.UPDATE) + @PostMapping(value = "/reject") + public AjaxResult taskReject(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.taskReject(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "退回任务") + @Log(title = "退回任务", businessType = BusinessType.UPDATE) + @PostMapping(value = "/return") + public AjaxResult taskReturn(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.taskReturn(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "获取所有可回退的节点") + @PostMapping(value = "/returnList") + public AjaxResult findReturnTaskList(@RequestBody FlowTaskVo flowTaskVo) { + return flowTaskService.findReturnTaskList(flowTaskVo); + } + + @Operation(summary = "删除任务") + @Log(title = "删除任务", businessType = BusinessType.DELETE) + @DeleteMapping(value = "/delete") + public AjaxResult delete(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.deleteTask(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "认领/签收任务") + @PostMapping(value = "/claim") + public AjaxResult claim(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.claim(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "取消认领/签收任务") + @PostMapping(value = "/unClaim") + public AjaxResult unClaim(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.unClaim(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "委派任务") + @PostMapping(value = "/delegateTask") + public AjaxResult delegate(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.delegateTask(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "任务归还") + @PostMapping(value = "/resolveTask") + public AjaxResult resolveTask(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.resolveTask(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "转办任务") + @PostMapping(value = "/assignTask") + public AjaxResult assign(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.assignTask(flowTaskVo); + return AjaxResult.success(); + } + + @PostMapping(value = "/addMultiInstanceExecution") + @Operation(summary = "多实例加签") + public AjaxResult addMultiInstanceExecution(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.addMultiInstanceExecution(flowTaskVo); + return AjaxResult.success("加签成功"); + } + + @PostMapping(value = "/deleteMultiInstanceExecution") + @Operation(summary = "多实例减签") + public AjaxResult deleteMultiInstanceExecution(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.deleteMultiInstanceExecution(flowTaskVo); + return AjaxResult.success("减签成功"); + } + + @Operation(summary = "获取下一节点") + @PostMapping(value = "/nextFlowNode") + public AjaxResult getNextFlowNode(@RequestBody FlowTaskVo flowTaskVo) { + return flowTaskService.getNextFlowNode(flowTaskVo); + } + + @Operation(summary = "流程发起时获取下一节点") + @PostMapping(value = "/nextFlowNodeByStart") + public AjaxResult getNextFlowNodeByStart(@RequestBody FlowTaskVo flowTaskVo) { + return flowTaskService.getNextFlowNodeByStart(flowTaskVo); + } + + /** + * 生成流程图 + * + * @param processId 任务ID + */ + @GetMapping("/diagram/{processId}") + public void genProcessDiagram(HttpServletResponse response, @PathVariable("processId") String processId) { + InputStream inputStream = flowTaskService.diagram(processId); + OutputStream os = null; + BufferedImage image = null; + try { + image = ImageIO.read(inputStream); + response.setContentType("image/png"); + os = response.getOutputStream(); + if (image != null) { + ImageIO.write(image, "png", os); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (os != null) { + os.flush(); + os.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 获取流程执行节点 + * + * @param procInsId 流程实例编号 + * @param procInsId 任务执行编号 + */ + @GetMapping("/flowViewer/{procInsId}/{executionId}") + public AjaxResult getFlowViewer( + @PathVariable("procInsId") String procInsId, + @PathVariable("executionId") String executionId) { + return flowTaskService.getFlowViewer(procInsId, executionId); + } + + /** + * 流程节点信息 + * + * @param procInsId 流程实例id + * @return + */ + @GetMapping("/flowXmlAndNode") + public AjaxResult flowXmlAndNode(@RequestParam(value = "procInsId", required = false) String procInsId, + @RequestParam(value = "deployId", required = false) String deployId) { + return flowTaskService.flowXmlAndNode(procInsId, deployId); + } + + /** + * 流程节点表单 + * + * @param taskId 流程任务编号 + * @return + */ + @GetMapping("/flowTaskForm") + public AjaxResult flowTaskForm(@RequestParam(value = "taskId", required = false) String taskId) throws Exception { + return flowTaskService.flowTaskForm(taskId); + } + + /** + * 流程节点信息 + * + * @param procInsId 流程实例编号 + * @param elementId 流程节点编号 + * @return + */ + @GetMapping("/flowTaskInfo") + public AjaxResult flowTaskInfo( + @RequestParam(value = "procInsId") String procInsId, + @RequestParam(value = "elementId") String elementId) { + return flowTaskService.flowTaskInfo(procInsId, elementId); + } + +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/SysExpressionController.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/SysExpressionController.java new file mode 100644 index 0000000..5053fa0 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/SysExpressionController.java @@ -0,0 +1,100 @@ +package com.ruoyi.flowable.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.flowable.domain.SysExpression; +import com.ruoyi.flowable.service.ISysExpressionService; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 流程达式Controller + * + * @author ruoyi + * @date 2022-12-12 + */ +@RestController +@RequestMapping("/system/expression") +public class SysExpressionController extends BaseController { + @Autowired + private ISysExpressionService sysExpressionService; + + /** + * 查询流程达式列表 + */ + @PreAuthorize("@ss.hasPermi('system:expression:list')") + @GetMapping("/list") + public TableDataInfo list(SysExpression sysExpression) { + startPage(); + List list = sysExpressionService.selectSysExpressionList(sysExpression); + return getDataTable(list); + } + + /** + * 导出流程达式列表 + */ + @PreAuthorize("@ss.hasPermi('system:expression:export')") + @Log(title = "流程达式", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysExpression sysExpression) { + List list = sysExpressionService.selectSysExpressionList(sysExpression); + ExcelUtil util = new ExcelUtil(SysExpression.class); + util.exportExcel(response, list, "流程达式数据"); + } + + /** + * 获取流程达式详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:expression:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) { + return success(sysExpressionService.selectSysExpressionById(id)); + } + + /** + * 新增流程达式 + */ + @PreAuthorize("@ss.hasPermi('system:expression:add')") + @Log(title = "流程达式", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysExpression sysExpression) { + return toAjax(sysExpressionService.insertSysExpression(sysExpression)); + } + + /** + * 修改流程达式 + */ + @PreAuthorize("@ss.hasPermi('system:expression:edit')") + @Log(title = "流程达式", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysExpression sysExpression) { + return toAjax(sysExpressionService.updateSysExpression(sysExpression)); + } + + /** + * 删除流程达式 + */ + @PreAuthorize("@ss.hasPermi('system:expression:remove')") + @Log(title = "流程达式", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(sysExpressionService.deleteSysExpressionByIds(ids)); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/SysListenerController.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/SysListenerController.java new file mode 100644 index 0000000..71a36c1 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/SysListenerController.java @@ -0,0 +1,100 @@ +package com.ruoyi.flowable.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.flowable.domain.SysListener; +import com.ruoyi.flowable.service.ISysListenerService; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 流程监听Controller + * + * @author Tony + * @date 2022-12-25 + */ +@RestController +@RequestMapping("/system/listener") +public class SysListenerController extends BaseController { + @Autowired + private ISysListenerService sysListenerService; + + /** + * 查询流程监听列表 + */ + @PreAuthorize("@ss.hasPermi('system:listener:list')") + @GetMapping("/list") + public TableDataInfo list(SysListener sysListener) { + startPage(); + List list = sysListenerService.selectSysListenerList(sysListener); + return getDataTable(list); + } + + /** + * 导出流程监听列表 + */ + @PreAuthorize("@ss.hasPermi('system:listener:export')") + @Log(title = "流程监听", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysListener sysListener) { + List list = sysListenerService.selectSysListenerList(sysListener); + ExcelUtil util = new ExcelUtil(SysListener.class); + util.exportExcel(response, list, "流程监听数据"); + } + + /** + * 获取流程监听详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:listener:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) { + return success(sysListenerService.selectSysListenerById(id)); + } + + /** + * 新增流程监听 + */ + @PreAuthorize("@ss.hasPermi('system:listener:add')") + @Log(title = "流程监听", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysListener sysListener) { + return toAjax(sysListenerService.insertSysListener(sysListener)); + } + + /** + * 修改流程监听 + */ + @PreAuthorize("@ss.hasPermi('system:listener:edit')") + @Log(title = "流程监听", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysListener sysListener) { + return toAjax(sysListenerService.updateSysListener(sysListener)); + } + + /** + * 删除流程监听 + */ + @PreAuthorize("@ss.hasPermi('system:listener:remove')") + @Log(title = "流程监听", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(sysListenerService.deleteSysListenerByIds(ids)); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/SysDeployForm.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/SysDeployForm.java new file mode 100644 index 0000000..d0060f8 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/SysDeployForm.java @@ -0,0 +1,65 @@ +package com.ruoyi.flowable.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 流程实例关联表单对象 sys_instance_form + * + * @author Tony + * @date 2021-03-30 + */ +public class SysDeployForm extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键 */ + private Long id; + + /** 表单主键 */ + @Excel(name = "表单主键") + private Long formId; + + /** 流程定义主键 */ + @Excel(name = "流程定义主键") + private String deployId; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setFormId(Long formId) + { + this.formId = formId; + } + + public Long getFormId() + { + return formId; + } + + public String getDeployId() { + return deployId; + } + + public void setDeployId(String deployId) { + this.deployId = deployId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("formId", getFormId()) + .append("deployId", getDeployId()) + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/SysExpression.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/SysExpression.java new file mode 100644 index 0000000..c23ee41 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/SysExpression.java @@ -0,0 +1,96 @@ +package com.ruoyi.flowable.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 流程达式对象 sys_expression + * + * @author ruoyi + * @date 2022-12-12 + */ +public class SysExpression extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 表单主键 */ + private Long id; + + /** 表达式名称 */ + @Excel(name = "表达式名称") + private String name; + + /** 表达式内容 */ + @Excel(name = "表达式内容") + private String expression; + /** 表达式类型 */ + @Excel(name = "表达式类型") + private String dataType; + + /** 状态 */ + private Integer status; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setName(String name) + { + this.name = name; + } + + public String getName() + { + return name; + } + public void setExpression(String expression) + { + this.expression = expression; + } + + public String getExpression() + { + return expression; + } + public void setStatus(Integer status) + { + this.status = status; + } + + public Integer getStatus() + { + return status; + } + + public void setDataType(String dataType) { + this.dataType = dataType; + } + public String getDataType() { + return dataType; + } + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("name", getName()) + .append("expression", getExpression()) + .append("dataType", getDataType()) + .append("createTime", getCreateTime()) + .append("updateTime", getUpdateTime()) + .append("createBy", getCreateBy()) + .append("updateBy", getUpdateBy()) + .append("status", getStatus()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/SysListener.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/SysListener.java new file mode 100644 index 0000000..b8a2e2a --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/SysListener.java @@ -0,0 +1,127 @@ +package com.ruoyi.flowable.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 流程监听对象 sys_listener + * + * @author Tony + * @date 2022-12-25 + */ +public class SysListener extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 表单主键 */ + private Long id; + + /** 名称 */ + @Excel(name = "名称") + private String name; + + /** 监听类型 */ + @Excel(name = "监听类型") + private String type; + + /** 事件类型 */ + @Excel(name = "事件类型") + private String eventType; + + /** 值类型 */ + @Excel(name = "值类型") + private String valueType; + + /** 执行内容 */ + @Excel(name = "执行内容") + private String value; + + /** 状态 */ + @Excel(name = "状态") + private Integer status; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setName(String name) + { + this.name = name; + } + + public String getName() + { + return name; + } + public void setType(String type) + { + this.type = type; + } + + public String getType() + { + return type; + } + public void setEventType(String eventType) + { + this.eventType = eventType; + } + + public String getEventType() + { + return eventType; + } + public void setValueType(String valueType) + { + this.valueType = valueType; + } + + public String getValueType() + { + return valueType; + } + public void setValue(String value) + { + this.value = value; + } + + public String getValue() + { + return value; + } + public void setStatus(Integer status) + { + this.status = status; + } + + public Integer getStatus() + { + return status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("name", getName()) + .append("type", getType()) + .append("eventType", getEventType()) + .append("valueType", getValueType()) + .append("value", getValue()) + .append("createTime", getCreateTime()) + .append("updateTime", getUpdateTime()) + .append("createBy", getCreateBy()) + .append("updateBy", getUpdateBy()) + .append("status", getStatus()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/SysTaskForm.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/SysTaskForm.java new file mode 100644 index 0000000..ffc6a34 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/SysTaskForm.java @@ -0,0 +1,66 @@ +package com.ruoyi.flowable.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 流程任务关联单对象 sys_task_form + * + * @author Tony + * @date 2021-04-03 + */ +public class SysTaskForm extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键 */ + private Long id; + + /** 表单主键 */ + @Excel(name = "表单主键") + private Long formId; + + /** 所属任务 */ + @Excel(name = "所属任务") + private String taskId; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setFormId(Long formId) + { + this.formId = formId; + } + + public Long getFormId() + { + return formId; + } + public void setTaskId(String taskId) + { + this.taskId = taskId; + } + + public String getTaskId() + { + return taskId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("formId", getFormId()) + .append("taskId", getTaskId()) + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowCommentDto.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowCommentDto.java new file mode 100644 index 0000000..fdc4e06 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowCommentDto.java @@ -0,0 +1,25 @@ +package com.ruoyi.flowable.domain.dto; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Tony + * @date 2021/3/28 15:50 + */ +@Data +@Builder +public class FlowCommentDto implements Serializable { + + /** + * 意见类别 0 正常意见 1 退回意见 2 驳回意见 + */ + private String type; + + /** + * 意见内容 + */ + private String comment; +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowFromFieldDTO.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowFromFieldDTO.java new file mode 100644 index 0000000..a74b83b --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowFromFieldDTO.java @@ -0,0 +1,15 @@ +package com.ruoyi.flowable.domain.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Tony + * @date 2021/3/31 23:20 + */ +@Data +public class FlowFromFieldDTO implements Serializable { + + private Object fields; +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowNextDto.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowNextDto.java new file mode 100644 index 0000000..32df324 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowNextDto.java @@ -0,0 +1,30 @@ +package com.ruoyi.flowable.domain.dto; + +import java.io.Serializable; + +import lombok.Data; + +/** + * 动态人员、组 + * @author Tony + * @date 2021/4/17 22:59 + */ +@Data +public class FlowNextDto implements Serializable { + + /** + * 审批人类型 + */ + private String type; + + /** + * 是否需要动态指定任务审批人 + */ + private String dataType; + + /** + * 流程变量 + */ + private String vars; + +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowProcDefDto.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowProcDefDto.java new file mode 100644 index 0000000..eda6149 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowProcDefDto.java @@ -0,0 +1,56 @@ +package com.ruoyi.flowable.domain.dto; +import java.io.Serializable; +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

流程定义

+ * + * @author Tony + * @date 2021-04-03 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "流程定义") +public class FlowProcDefDto implements Serializable { + + @Schema(title = "流程id") + private String id; + + @Schema(title = "流程名称") + private String name; + + @Schema(title = "流程key") + private String flowKey; + + @Schema(title = "流程分类") + private String category; + + @Schema(title = "配置表单名称") + private String formName; + + @Schema(title = "配置表单id") + private Long formId; + + @Schema(title = "版本") + private int version; + + @Schema(title = "部署ID") + private String deploymentId; + + @Schema(title = "流程定义状态: 1:激活 , 2:中止") + private int suspensionState; + + @Schema(title = "部署时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date deploymentTime; + + +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowSaveXmlVo.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowSaveXmlVo.java new file mode 100644 index 0000000..6c98e71 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowSaveXmlVo.java @@ -0,0 +1,28 @@ +package com.ruoyi.flowable.domain.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Tony + * @date 2021/3/28 19:48 + */ +@Data +public class FlowSaveXmlVo implements Serializable { + + /** + * 流程名称 + */ + private String name; + + /** + * 流程分类 + */ + private String category; + + /** + * xml 文件 + */ + private String xml; +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowTaskDto.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowTaskDto.java new file mode 100644 index 0000000..4312420 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowTaskDto.java @@ -0,0 +1,104 @@ +package com.ruoyi.flowable.domain.dto; + +import java.io.Serializable; +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 工作流任务 + *

+ * + * @author Tony + * @date 2021-04-03 + */ +@Getter +@Setter +@Schema(description = "工作流任务相关-返回参数") +public class FlowTaskDto implements Serializable { + + @Schema(title = "任务编号") + private String taskId; + + @Schema(title = "任务执行编号") + private String executionId; + + @Schema(title = "任务名称") + private String taskName; + + @Schema(title = "任务Key") + private String taskDefKey; + + @Schema(title = "任务执行人Id") + private Long assigneeId; + + @Schema(title = "部门名称") + private String deptName; + + @Schema(title = "流程发起人部门名称") + private String startDeptName; + + @Schema(title = "任务执行人名称") + private String assigneeName; + @Schema(title = "任务执行人部门") + private String assigneeDeptName;; + + @Schema(title = "流程发起人Id") + private String startUserId; + + @Schema(title = "流程发起人名称") + private String startUserName; + + @Schema(title = "流程类型") + private String category; + + @Schema(title = "流程变量信息") + private Object variables; + + @Schema(title = "局部变量信息") + private Object taskLocalVars; + + @Schema(title = "流程部署编号") + private String deployId; + + @Schema(title = "流程ID") + private String procDefId; + + @Schema(title = "流程key") + private String procDefKey; + + @Schema(title = "流程定义名称") + private String procDefName; + + @Schema(title = "流程定义内置使用版本") + private int procDefVersion; + + @Schema(title = "流程实例ID") + private String procInsId; + + @Schema(title = "历史流程实例ID") + private String hisProcInsId; + + @Schema(title = "任务耗时") + private String duration; + + @Schema(title = "任务意见") + private FlowCommentDto comment; + + @Schema(title = "候选执行人") + private String candidate; + + @Schema(title = "任务创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + @Schema(title = "任务完成时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date finishTime; + +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowViewerDto.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowViewerDto.java new file mode 100644 index 0000000..5914d7d --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowViewerDto.java @@ -0,0 +1,23 @@ +package com.ruoyi.flowable.domain.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Tony + * @date 2021/4/21 20:55 + */ +@Data +public class FlowViewerDto implements Serializable { + + /** + * 流程key + */ + private String key; + + /** + * 是否完成(已经审批) + */ + private boolean completed; +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/vo/FlowQueryVo.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/vo/FlowQueryVo.java new file mode 100644 index 0000000..9e99255 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/vo/FlowQueryVo.java @@ -0,0 +1,32 @@ +package com.ruoyi.flowable.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + *

流程任务

+ * + * @author Tony + * @date 2021-04-03 + */ +@Data +@Schema(description = "工作流任务相关--请求参数") +public class FlowQueryVo { + + @Schema(title = "流程名称") + private String name; + + @Schema(title = "开始时间") + private String startTime; + + @Schema(title = "结束时间") + private String endTime; + + @Schema(title = "当前页码") + private Integer pageNum; + + @Schema(title = "每页条数") + private Integer pageSize; + + +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/vo/FlowTaskVo.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/vo/FlowTaskVo.java new file mode 100644 index 0000000..e0de8ce --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/vo/FlowTaskVo.java @@ -0,0 +1,55 @@ +package com.ruoyi.flowable.domain.vo; + +import java.util.List; +import java.util.Map; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + *

流程任务

+ * + * @author Tony + * @date 2021-04-03 + */ +@Data +@Schema(description = "工作流任务相关--请求参数") +public class FlowTaskVo { + + @Schema(title = "任务Id") + private String taskId; + + @Schema(title = "用户Id") + private String userId; + + @Schema(title = "任务意见") + private String comment; + + @Schema(title = "流程实例Id") + private String instanceId; + + @Schema(title = "节点") + private String targetKey; + + private String deploymentId; + @Schema(title = "流程环节定义ID") + private String defId; + + @Schema(title = "子执行流ID") + private String currentChildExecutionId; + + @Schema(title = "子执行流是否已执行") + private Boolean flag; + + @Schema(title = "流程变量信息") + private Map variables; + + @Schema(title = "审批人") + private String assignee; + + @Schema(title = "候选人") + private List candidateUsers; + + @Schema(title = "审批组") + private List candidateGroups; +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/vo/ReturnTaskNodeVo.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/vo/ReturnTaskNodeVo.java new file mode 100644 index 0000000..fbb3805 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/vo/ReturnTaskNodeVo.java @@ -0,0 +1,22 @@ +package com.ruoyi.flowable.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + *

可退回节点

+ * + * @author tony + * @date 2022-04-23 11:01:52 + */ +@Data +@Schema(description = "可退回节点") +public class ReturnTaskNodeVo { + + @Schema(title = "任务Id") + private String id; + + @Schema(title = "用户Id") + private String name; + +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/expression/FlowDelegationExpression.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/expression/FlowDelegationExpression.java new file mode 100644 index 0000000..372c434 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/expression/FlowDelegationExpression.java @@ -0,0 +1,16 @@ +package com.ruoyi.flowable.expression; + +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component("flowDelegationExpression") +public class FlowDelegationExpression implements JavaDelegate { + @Override + public void execute(DelegateExecution execution) { + log.info("代理表达式执行器:{}", execution); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/factory/FlowServiceFactory.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/factory/FlowServiceFactory.java new file mode 100644 index 0000000..18e9d83 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/factory/FlowServiceFactory.java @@ -0,0 +1,48 @@ +package com.ruoyi.flowable.factory; + +import org.flowable.engine.HistoryService; +import org.flowable.engine.IdentityService; +import org.flowable.engine.ManagementService; +import org.flowable.engine.ProcessEngine; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; +import lombok.Getter; + + +/** + * flowable 引擎注入封装 + * @author Tony + * @date 2021-04-03 + */ +@Component +@Getter +public class FlowServiceFactory { + + @Resource + protected RepositoryService repositoryService; + + @Resource + protected RuntimeService runtimeService; + + @Resource + protected IdentityService identityService; + + @Resource + protected TaskService taskService; + + @Resource + protected HistoryService historyService; + + @Resource + protected ManagementService managementService; + + @Qualifier("processEngine") + @Resource + protected ProcessEngine processEngine; + +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/CustomProcessDiagramCanvas.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/CustomProcessDiagramCanvas.java new file mode 100644 index 0000000..74b6c9c --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/CustomProcessDiagramCanvas.java @@ -0,0 +1,457 @@ +package com.ruoyi.flowable.flow; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Paint; +import java.awt.RenderingHints; +import java.awt.Stroke; +import java.awt.font.FontRenderContext; +import java.awt.font.LineBreakMeasurer; +import java.awt.font.TextAttribute; +import java.awt.font.TextLayout; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.text.AttributedCharacterIterator; +import java.text.AttributedString; + +import javax.imageio.ImageIO; + +import org.flowable.bpmn.model.AssociationDirection; +import org.flowable.bpmn.model.GraphicInfo; +import org.flowable.image.impl.DefaultProcessDiagramCanvas; +import org.flowable.image.util.ReflectUtil; + +/** + * @author Tony + * @date 2021/4/4 23:58 + */ +public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas { + // 定义走过流程连线颜色为绿色 + protected static Color HIGHLIGHT_SequenceFlow_COLOR = Color.GREEN; + // 设置未走过流程的连接线颜色 + protected static Color CONNECTION_COLOR = Color.BLACK; + // 设置flows连接线字体颜色red + protected static Color LABEL_COLOR = new Color(0, 0, 0); + // 高亮显示task框颜色 + protected static Color HIGHLIGHT_COLOR = Color.GREEN; + protected static Color HIGHLIGHT_COLOR1 = Color.RED; + + public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, + String activityFontName, String labelFontName, String annotationFontName, + ClassLoader customClassLoader) { + super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName, + customClassLoader); + this.initialize(imageType); + } + + /** + * 重写绘制连线的方式,设置绘制颜色 + * + * @param xPoints + * @param yPoints + * @param conditional + * @param isDefault + * @param connectionType + * @param associationDirection + * @param highLighted + * @param scaleFactor + */ + @Override + public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, + String connectionType, AssociationDirection associationDirection, boolean highLighted, + double scaleFactor) { + Paint originalPaint = this.g.getPaint(); + Stroke originalStroke = this.g.getStroke(); + this.g.setPaint(CONNECTION_COLOR); + if (connectionType.equals("association")) { + this.g.setStroke(ASSOCIATION_STROKE); + } else if (highLighted) { + this.g.setPaint(HIGHLIGHT_SequenceFlow_COLOR); + this.g.setStroke(HIGHLIGHT_FLOW_STROKE); + } + + for (int i = 1; i < xPoints.length; ++i) { + Integer sourceX = xPoints[i - 1]; + Integer sourceY = yPoints[i - 1]; + Integer targetX = xPoints[i]; + Integer targetY = yPoints[i]; + java.awt.geom.Line2D.Double line = new java.awt.geom.Line2D.Double((double) sourceX, + (double) sourceY, + (double) targetX, (double) targetY); + this.g.draw(line); + } + + java.awt.geom.Line2D.Double line; + if (isDefault) { + line = new java.awt.geom.Line2D.Double((double) xPoints[0], (double) yPoints[0], + (double) xPoints[1], + (double) yPoints[1]); + this.drawDefaultSequenceFlowIndicator(line, scaleFactor); + } + + if (conditional) { + line = new java.awt.geom.Line2D.Double((double) xPoints[0], (double) yPoints[0], + (double) xPoints[1], + (double) yPoints[1]); + this.drawConditionalSequenceFlowIndicator(line, scaleFactor); + } + + if (associationDirection.equals(AssociationDirection.ONE) + || associationDirection.equals(AssociationDirection.BOTH)) { + line = new java.awt.geom.Line2D.Double((double) xPoints[xPoints.length - 2], + (double) yPoints[xPoints.length - 2], (double) xPoints[xPoints.length - 1], + (double) yPoints[xPoints.length - 1]); + this.drawArrowHead(line, scaleFactor); + } + + if (associationDirection.equals(AssociationDirection.BOTH)) { + line = new java.awt.geom.Line2D.Double((double) xPoints[1], (double) yPoints[1], + (double) xPoints[0], + (double) yPoints[0]); + this.drawArrowHead(line, scaleFactor); + } + + this.g.setPaint(originalPaint); + this.g.setStroke(originalStroke); + } + + /** + * 设置字体大小图标颜色 + * + * @param imageType + */ + @Override + public void initialize(String imageType) { + if ("png".equalsIgnoreCase(imageType)) { + this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, 2); + } else { + this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, 1); + } + + this.g = this.processDiagram.createGraphics(); + if (!"png".equalsIgnoreCase(imageType)) { + this.g.setBackground(new Color(255, 255, 255, 0)); + this.g.clearRect(0, 0, this.canvasWidth, this.canvasHeight); + } + + this.g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + // 修改图标颜色,修改图标字体大小 + this.g.setPaint(Color.black); + Font font = new Font(this.activityFontName, 10, 14); + this.g.setFont(font); + this.fontMetrics = this.g.getFontMetrics(); + // 修改连接线字体大小 + LABEL_FONT = new Font(this.labelFontName, 10, 15); + ANNOTATION_FONT = new Font(this.annotationFontName, 0, 11); + + try { + USERTASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/userTask.png", + this.customClassLoader)); + SCRIPTTASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/scriptTask.png", + this.customClassLoader)); + SERVICETASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/serviceTask.png", + this.customClassLoader)); + RECEIVETASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/receiveTask.png", + this.customClassLoader)); + SENDTASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/sendTask.png", + this.customClassLoader)); + CASETASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/caseTask.png", + this.customClassLoader)); + MANUALTASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/manualTask.png", + this.customClassLoader)); + BUSINESS_RULE_TASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/businessRuleTask.png", + this.customClassLoader)); + SHELL_TASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/shellTask.png", + this.customClassLoader)); + DMN_TASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/dmnTask.png", + this.customClassLoader)); + CAMEL_TASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/camelTask.png", + this.customClassLoader)); + HTTP_TASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/httpTask.png", + this.customClassLoader)); + TIMER_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/timer.png", + this.customClassLoader)); + COMPENSATE_THROW_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/compensate-throw.png", + this.customClassLoader)); + COMPENSATE_CATCH_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/compensate.png", + this.customClassLoader)); + CONDITIONAL_CATCH_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/conditional.png", + this.customClassLoader)); + ERROR_THROW_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/error-throw.png", + this.customClassLoader)); + ERROR_CATCH_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/error.png", + this.customClassLoader)); + ESCALATION_THROW_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/escalation-throw.png", + this.customClassLoader)); + ESCALATION_CATCH_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/escalation.png", + this.customClassLoader)); + MESSAGE_THROW_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/message-throw.png", + this.customClassLoader)); + MESSAGE_CATCH_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/message.png", + this.customClassLoader)); + SIGNAL_THROW_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/signal-throw.png", + this.customClassLoader)); + SIGNAL_CATCH_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/signal.png", + this.customClassLoader)); + + } catch (IOException var4) { + LOGGER.warn("Could not load image for process diagram creation: {}", var4.getMessage()); + } + + } + + /** + * 设置连接线字体 + * + * @param text + * @param graphicInfo + * @param centered + */ + @Override + public void drawLabel(String text, GraphicInfo graphicInfo, boolean centered) { + float interline = 1.0f; + + // text + if (text != null && text.length() > 0) { + Paint originalPaint = g.getPaint(); + Font originalFont = g.getFont(); + + g.setPaint(LABEL_COLOR); + g.setFont(LABEL_FONT); + + int wrapWidth = 100; + int textY = (int) graphicInfo.getY(); + + AttributedString as = new AttributedString(text); + as.addAttribute(TextAttribute.FOREGROUND, g.getPaint()); + as.addAttribute(TextAttribute.FONT, g.getFont()); + AttributedCharacterIterator aci = as.getIterator(); + FontRenderContext frc = new FontRenderContext(null, true, false); + LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc); + + while (lbm.getPosition() < text.length()) { + TextLayout tl = lbm.nextLayout(wrapWidth); + textY += tl.getAscent(); + + Rectangle2D bb = tl.getBounds(); + double tX = graphicInfo.getX(); + + if (centered) { + tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2); + } + tl.draw(g, (float) tX, textY); + textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent(); + } + + // restore originals + g.setFont(originalFont); + g.setPaint(originalPaint); + } + } + + /** + * 高亮显示task框完成的 + * + * @param x + * @param y + * @param width + * @param height + */ + @Override + public void drawHighLight(int x, int y, int width, int height) { + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + + g.setPaint(HIGHLIGHT_COLOR); + g.setStroke(THICK_TASK_BORDER_STROKE); + + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20); + g.draw(rect); + + g.setPaint(originalPaint); + g.setStroke(originalStroke); + } + + /** + * 自定义task框当前的位置 + * + * @param x + * @param y + * @param width + * @param height + */ + public void drawHighLightNow(int x, int y, int width, int height) { + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + + g.setPaint(HIGHLIGHT_COLOR1); + g.setStroke(THICK_TASK_BORDER_STROKE); + + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20); + g.draw(rect); + + g.setPaint(originalPaint); + g.setStroke(originalStroke); + } + + /** + * 自定义结束节点 + * + * @param x + * @param y + * @param width + * @param height + */ + public void drawHighLightEnd(int x, int y, int width, int height) { + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + + g.setPaint(HIGHLIGHT_COLOR); + g.setStroke(THICK_TASK_BORDER_STROKE); + + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20); + g.draw(rect); + + g.setPaint(originalPaint); + g.setStroke(originalStroke); + } + + /** + * task框自定义文字 + * + * @param name + * @param graphicInfo + * @param thickBorder + * @param scaleFactor + */ + @Override + protected void drawTask(String name, GraphicInfo graphicInfo, boolean thickBorder, double scaleFactor) { + + Paint originalPaint = g.getPaint(); + int x = (int) graphicInfo.getX(); + int y = (int) graphicInfo.getY(); + int width = (int) graphicInfo.getWidth(); + int height = (int) graphicInfo.getHeight(); + + // Create a new gradient paint for every task box, gradient depends on x and y + // and is not relative + g.setPaint(TASK_BOX_COLOR); + + int arcR = 6; + if (thickBorder) { + arcR = 3; + } + + // shape + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcR, arcR); + g.fill(rect); + g.setPaint(TASK_BORDER_COLOR); + + if (thickBorder) { + Stroke originalStroke = g.getStroke(); + g.setStroke(THICK_TASK_BORDER_STROKE); + g.draw(rect); + g.setStroke(originalStroke); + } else { + g.draw(rect); + } + + g.setPaint(originalPaint); + // text + if (scaleFactor == 1.0 && name != null && name.length() > 0) { + int boxWidth = width - (2 * TEXT_PADDING); + int boxHeight = height - 16 - ICON_PADDING - ICON_PADDING - MARKER_WIDTH - 2 - 2; + int boxX = x + width / 2 - boxWidth / 2; + int boxY = y + height / 2 - boxHeight / 2 + ICON_PADDING + ICON_PADDING - 2 - 2; + + drawMultilineCentredText(name, boxX, boxY, boxWidth, boxHeight); + } + } + + protected static Color EVENT_COLOR = new Color(255, 255, 255); + + /** + * 重写开始事件 + * + * @param graphicInfo + * @param image + * @param scaleFactor + */ + @Override + public void drawStartEvent(GraphicInfo graphicInfo, BufferedImage image, double scaleFactor) { + Paint originalPaint = g.getPaint(); + g.setPaint(EVENT_COLOR); + Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), + graphicInfo.getWidth(), graphicInfo.getHeight()); + g.fill(circle); + g.setPaint(EVENT_BORDER_COLOR); + g.draw(circle); + g.setPaint(originalPaint); + if (image != null) { + // calculate coordinates to center image + int imageX = (int) Math + .round(graphicInfo.getX() + (graphicInfo.getWidth() / 2) + - (image.getWidth() / (2 * scaleFactor))); + int imageY = (int) Math.round( + graphicInfo.getY() + (graphicInfo.getHeight() / 2) + - (image.getHeight() / (2 * scaleFactor))); + g.drawImage(image, imageX, imageY, + (int) (image.getWidth() / scaleFactor), (int) (image.getHeight() / scaleFactor), + null); + } + + } + + /** + * 重写结束事件 + * + * @param graphicInfo + * @param scaleFactor + */ + @Override + public void drawNoneEndEvent(GraphicInfo graphicInfo, double scaleFactor) { + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + g.setPaint(EVENT_COLOR); + Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), + graphicInfo.getWidth(), graphicInfo.getHeight()); + g.fill(circle); + g.setPaint(EVENT_BORDER_COLOR); + // g.setPaint(HIGHLIGHT_COLOR); + if (scaleFactor == 1.0) { + g.setStroke(END_EVENT_STROKE); + } else { + g.setStroke(new BasicStroke(2.0f)); + } + g.draw(circle); + g.setStroke(originalStroke); + g.setPaint(originalPaint); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/CustomProcessDiagramGenerator.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/CustomProcessDiagramGenerator.java new file mode 100644 index 0000000..b17ae20 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/CustomProcessDiagramGenerator.java @@ -0,0 +1,431 @@ +package com.ruoyi.flowable.flow; + +import java.util.Iterator; +import java.util.List; + +import org.flowable.bpmn.model.Activity; +import org.flowable.bpmn.model.Artifact; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.CallActivity; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowElementsContainer; +import org.flowable.bpmn.model.FlowNode; +import org.flowable.bpmn.model.Gateway; +import org.flowable.bpmn.model.GraphicInfo; +import org.flowable.bpmn.model.Lane; +import org.flowable.bpmn.model.MultiInstanceLoopCharacteristics; +import org.flowable.bpmn.model.Pool; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.SequenceFlow; +import org.flowable.bpmn.model.SubProcess; +import org.flowable.image.impl.DefaultProcessDiagramCanvas; +import org.flowable.image.impl.DefaultProcessDiagramGenerator; + +/** + * @author Tony + * @date 2021/4/5 0:31 + */ +public class CustomProcessDiagramGenerator extends DefaultProcessDiagramGenerator { + @Override + protected DefaultProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType, + List highLightedActivities, List highLightedFlows, String activityFontName, + String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, + boolean drawSequenceFlowNameWithNoLabelDI) { + this.prepareBpmnModel(bpmnModel); + DefaultProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, imageType, + activityFontName, labelFontName, annotationFontName, customClassLoader); + Iterator var13 = bpmnModel.getPools().iterator(); + + while (var13.hasNext()) { + Pool process = (Pool) var13.next(); + GraphicInfo subProcesses = bpmnModel.getGraphicInfo(process.getId()); + processDiagramCanvas.drawPoolOrLane(process.getName(), subProcesses, scaleFactor); + } + + var13 = bpmnModel.getProcesses().iterator(); + + Process process1; + Iterator subProcesses1; + while (var13.hasNext()) { + process1 = (Process) var13.next(); + subProcesses1 = process1.getLanes().iterator(); + + while (subProcesses1.hasNext()) { + Lane artifact = (Lane) subProcesses1.next(); + GraphicInfo subProcess = bpmnModel.getGraphicInfo(artifact.getId()); + processDiagramCanvas.drawPoolOrLane(artifact.getName(), subProcess, scaleFactor); + } + } + + var13 = bpmnModel.getProcesses().iterator(); + + while (var13.hasNext()) { + process1 = (Process) var13.next(); + subProcesses1 = process1.findFlowElementsOfType(FlowNode.class).iterator(); + + while (subProcesses1.hasNext()) { + FlowNode artifact1 = (FlowNode) subProcesses1.next(); + if (!this.isPartOfCollapsedSubProcess(artifact1, bpmnModel)) { + this.drawActivity(processDiagramCanvas, bpmnModel, artifact1, highLightedActivities, + highLightedFlows, scaleFactor, Boolean.valueOf(drawSequenceFlowNameWithNoLabelDI)); + } + } + } + + var13 = bpmnModel.getProcesses().iterator(); + + label75: while (true) { + List subProcesses2; + do { + if (!var13.hasNext()) { + return processDiagramCanvas; + } + + process1 = (Process) var13.next(); + subProcesses1 = process1.getArtifacts().iterator(); + + while (subProcesses1.hasNext()) { + Artifact artifact2 = (Artifact) subProcesses1.next(); + this.drawArtifact(processDiagramCanvas, bpmnModel, artifact2); + } + + subProcesses2 = process1.findFlowElementsOfType(SubProcess.class, true); + } while (subProcesses2 == null); + + Iterator artifact3 = subProcesses2.iterator(); + + while (true) { + GraphicInfo graphicInfo; + SubProcess subProcess1; + do { + do { + if (!artifact3.hasNext()) { + continue label75; + } + + subProcess1 = (SubProcess) artifact3.next(); + graphicInfo = bpmnModel.getGraphicInfo(subProcess1.getId()); + } while (graphicInfo != null && graphicInfo.getExpanded() != null + && !graphicInfo.getExpanded().booleanValue()); + } while (this.isPartOfCollapsedSubProcess(subProcess1, bpmnModel)); + + Iterator var19 = subProcess1.getArtifacts().iterator(); + + while (var19.hasNext()) { + Artifact subProcessArtifact = (Artifact) var19.next(); + this.drawArtifact(processDiagramCanvas, bpmnModel, subProcessArtifact); + } + } + } + } + + protected static DefaultProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String imageType, + String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) { + double minX = 1.7976931348623157E308D; + double maxX = 0.0D; + double minY = 1.7976931348623157E308D; + double maxY = 0.0D; + + GraphicInfo nrOfLanes; + for (Iterator flowNodes = bpmnModel.getPools().iterator(); flowNodes + .hasNext(); maxY = nrOfLanes.getY() + nrOfLanes.getHeight()) { + Pool artifacts = (Pool) flowNodes.next(); + nrOfLanes = bpmnModel.getGraphicInfo(artifacts.getId()); + minX = nrOfLanes.getX(); + maxX = nrOfLanes.getX() + nrOfLanes.getWidth(); + minY = nrOfLanes.getY(); + } + + List var23 = gatherAllFlowNodes(bpmnModel); + Iterator var24 = var23.iterator(); + + label155: while (var24.hasNext()) { + FlowNode var26 = (FlowNode) var24.next(); + GraphicInfo artifact = bpmnModel.getGraphicInfo(var26.getId()); + if (artifact.getX() + artifact.getWidth() > maxX) { + maxX = artifact.getX() + artifact.getWidth(); + } + + if (artifact.getX() < minX) { + minX = artifact.getX(); + } + + if (artifact.getY() + artifact.getHeight() > maxY) { + maxY = artifact.getY() + artifact.getHeight(); + } + + if (artifact.getY() < minY) { + minY = artifact.getY(); + } + + Iterator process = var26.getOutgoingFlows().iterator(); + + while (true) { + List l; + do { + if (!process.hasNext()) { + continue label155; + } + + SequenceFlow graphicInfoList = (SequenceFlow) process.next(); + l = bpmnModel.getFlowLocationGraphicInfo(graphicInfoList.getId()); + } while (l == null); + + Iterator graphicInfo = l.iterator(); + + while (graphicInfo.hasNext()) { + GraphicInfo graphicInfo1 = (GraphicInfo) graphicInfo.next(); + if (graphicInfo1.getX() > maxX) { + maxX = graphicInfo1.getX(); + } + + if (graphicInfo1.getX() < minX) { + minX = graphicInfo1.getX(); + } + + if (graphicInfo1.getY() > maxY) { + maxY = graphicInfo1.getY(); + } + + if (graphicInfo1.getY() < minY) { + minY = graphicInfo1.getY(); + } + } + } + } + + List var25 = gatherAllArtifacts(bpmnModel); + Iterator var27 = var25.iterator(); + + GraphicInfo var37; + while (var27.hasNext()) { + Artifact var29 = (Artifact) var27.next(); + GraphicInfo var31 = bpmnModel.getGraphicInfo(var29.getId()); + if (var31 != null) { + if (var31.getX() + var31.getWidth() > maxX) { + maxX = var31.getX() + var31.getWidth(); + } + + if (var31.getX() < minX) { + minX = var31.getX(); + } + + if (var31.getY() + var31.getHeight() > maxY) { + maxY = var31.getY() + var31.getHeight(); + } + + if (var31.getY() < minY) { + minY = var31.getY(); + } + } + + List var33 = bpmnModel.getFlowLocationGraphicInfo(var29.getId()); + if (var33 != null) { + Iterator var35 = var33.iterator(); + + while (var35.hasNext()) { + var37 = (GraphicInfo) var35.next(); + if (var37.getX() > maxX) { + maxX = var37.getX(); + } + + if (var37.getX() < minX) { + minX = var37.getX(); + } + + if (var37.getY() > maxY) { + maxY = var37.getY(); + } + + if (var37.getY() < minY) { + minY = var37.getY(); + } + } + } + } + + int var28 = 0; + Iterator var30 = bpmnModel.getProcesses().iterator(); + + while (var30.hasNext()) { + Process var32 = (Process) var30.next(); + Iterator var34 = var32.getLanes().iterator(); + + while (var34.hasNext()) { + Lane var36 = (Lane) var34.next(); + ++var28; + var37 = bpmnModel.getGraphicInfo(var36.getId()); + if (var37.getX() + var37.getWidth() > maxX) { + maxX = var37.getX() + var37.getWidth(); + } + + if (var37.getX() < minX) { + minX = var37.getX(); + } + + if (var37.getY() + var37.getHeight() > maxY) { + maxY = var37.getY() + var37.getHeight(); + } + + if (var37.getY() < minY) { + minY = var37.getY(); + } + } + } + + if (var23.isEmpty() && bpmnModel.getPools().isEmpty() && var28 == 0) { + minX = 0.0D; + minY = 0.0D; + } + + return new CustomProcessDiagramCanvas((int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY, imageType, + activityFontName, labelFontName, annotationFontName, customClassLoader); + } + + private static void drawHighLight(DefaultProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) { + processDiagramCanvas.drawHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), + (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight()); + + } + + private static void drawHighLightNow(CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) { + processDiagramCanvas.drawHighLightNow((int) graphicInfo.getX(), (int) graphicInfo.getY(), + (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight()); + + } + + private static void drawHighLightEnd(CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) { + processDiagramCanvas.drawHighLightEnd((int) graphicInfo.getX(), (int) graphicInfo.getY(), + (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight()); + + } + + @Override + protected void drawActivity(DefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, + FlowNode flowNode, java.util.List highLightedActivities, java.util.List highLightedFlows, + double scaleFactor, Boolean drawSequenceFlowNameWithNoLabelDI) { + + DefaultProcessDiagramGenerator.ActivityDrawInstruction drawInstruction = activityDrawInstructions + .get(flowNode.getClass()); + if (drawInstruction != null) { + + drawInstruction.draw(processDiagramCanvas, bpmnModel, flowNode); + + // Gather info on the multi instance marker + boolean multiInstanceSequential = false; + boolean multiInstanceParallel = false; + boolean collapsed = false; + if (flowNode instanceof Activity) { + Activity activity = (Activity) flowNode; + MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = activity.getLoopCharacteristics(); + if (multiInstanceLoopCharacteristics != null) { + multiInstanceSequential = multiInstanceLoopCharacteristics.isSequential(); + multiInstanceParallel = !multiInstanceSequential; + } + } + + // Gather info on the collapsed marker + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + if (flowNode instanceof SubProcess) { + collapsed = graphicInfo.getExpanded() != null && !graphicInfo.getExpanded(); + } else if (flowNode instanceof CallActivity) { + collapsed = true; + } + + if (scaleFactor == 1.0) { + // Actually draw the markers + processDiagramCanvas.drawActivityMarkers((int) graphicInfo.getX(), (int) graphicInfo.getY(), + (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(), + multiInstanceSequential, multiInstanceParallel, collapsed); + } + + // Draw highlighted activities + if (highLightedActivities.contains(flowNode.getId())) { + + if (highLightedActivities.get(highLightedActivities.size() - 1).equals(flowNode.getId()) + && !"endenv".equals(flowNode.getId())) { + if ((flowNode.getId().contains("Event_"))) { + drawHighLightEnd((CustomProcessDiagramCanvas) processDiagramCanvas, + bpmnModel.getGraphicInfo(flowNode.getId())); + } else { + drawHighLightNow((CustomProcessDiagramCanvas) processDiagramCanvas, + bpmnModel.getGraphicInfo(flowNode.getId())); + } + } else { + drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId())); + } + + } + + } + + // Outgoing transitions of activity + for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) { + boolean highLighted = (highLightedFlows.contains(sequenceFlow.getId())); + String defaultFlow = null; + if (flowNode instanceof Activity) { + defaultFlow = ((Activity) flowNode).getDefaultFlow(); + } else if (flowNode instanceof Gateway) { + defaultFlow = ((Gateway) flowNode).getDefaultFlow(); + } + + boolean isDefault = false; + if (defaultFlow != null && defaultFlow.equalsIgnoreCase(sequenceFlow.getId())) { + isDefault = true; + } + boolean drawConditionalIndicator = sequenceFlow.getConditionExpression() != null + && !(flowNode instanceof Gateway); + + String sourceRef = sequenceFlow.getSourceRef(); + String targetRef = sequenceFlow.getTargetRef(); + FlowElement sourceElement = bpmnModel.getFlowElement(sourceRef); + FlowElement targetElement = bpmnModel.getFlowElement(targetRef); + java.util.List graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId()); + if (graphicInfoList != null && graphicInfoList.size() > 0) { + graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, + targetElement, graphicInfoList); + int xPoints[] = new int[graphicInfoList.size()]; + int yPoints[] = new int[graphicInfoList.size()]; + + for (int i = 1; i < graphicInfoList.size(); i++) { + GraphicInfo graphicInfo = graphicInfoList.get(i); + GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1); + + if (i == 1) { + xPoints[0] = (int) previousGraphicInfo.getX(); + yPoints[0] = (int) previousGraphicInfo.getY(); + } + xPoints[i] = (int) graphicInfo.getX(); + yPoints[i] = (int) graphicInfo.getY(); + + } + + processDiagramCanvas.drawSequenceflow(xPoints, yPoints, drawConditionalIndicator, isDefault, + highLighted, scaleFactor); + + // Draw sequenceflow label + GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(sequenceFlow.getId()); + if (labelGraphicInfo != null) { + processDiagramCanvas.drawLabel(sequenceFlow.getName(), labelGraphicInfo, false); + } else { + if (drawSequenceFlowNameWithNoLabelDI) { + GraphicInfo lineCenter = getLineCenter(graphicInfoList); + processDiagramCanvas.drawLabel(sequenceFlow.getName(), lineCenter, false); + } + + } + } + } + + // Nested elements + if (flowNode instanceof FlowElementsContainer) { + for (FlowElement nestedFlowElement : ((FlowElementsContainer) flowNode).getFlowElements()) { + if (nestedFlowElement instanceof FlowNode + && !isPartOfCollapsedSubProcess(nestedFlowElement, bpmnModel)) { + drawActivity(processDiagramCanvas, bpmnModel, (FlowNode) nestedFlowElement, + highLightedActivities, highLightedFlows, scaleFactor, drawSequenceFlowNameWithNoLabelDI); + } + } + } + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/FindNextNodeUtil.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/FindNextNodeUtil.java new file mode 100644 index 0000000..c76744f --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/FindNextNodeUtil.java @@ -0,0 +1,286 @@ +package com.ruoyi.flowable.flow; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.CallActivity; +import org.flowable.bpmn.model.EndEvent; +import org.flowable.bpmn.model.ExclusiveGateway; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.Gateway; +import org.flowable.bpmn.model.ParallelGateway; +//import com.greenpineyu.fel.FelEngine; +//import com.greenpineyu.fel.FelEngineImpl; +//import com.greenpineyu.fel.context.FelContext; +//import org.apache.commons.jexl2.JexlContext; +//import org.apache.commons.jexl2.JexlEngine; +//import org.apache.commons.jexl2.MapContext; +//import org.apache.commons.lang3.StringUtils; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.ReceiveTask; +import org.flowable.bpmn.model.SequenceFlow; +import org.flowable.bpmn.model.ServiceTask; +import org.flowable.bpmn.model.StartEvent; +import org.flowable.bpmn.model.SubProcess; +import org.flowable.bpmn.model.Task; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.repository.ProcessDefinition; + +import com.googlecode.aviator.AviatorEvaluator; +import com.googlecode.aviator.Expression; + +/** + * @author Tony + * @date 2021/4/19 20:51 + */ +public class FindNextNodeUtil { + + /** + * 获取下一步骤的用户任务 + * + * @param repositoryService + * @param map + * @return + */ + public static List getNextUserTasks(RepositoryService repositoryService, org.flowable.task.api.Task task, + Map map) { + List data = new ArrayList<>(); + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()).singleResult(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); + Process mainProcess = bpmnModel.getMainProcess(); + Collection flowElements = mainProcess.getFlowElements(); + String key = task.getTaskDefinitionKey(); + FlowElement flowElement = bpmnModel.getFlowElement(key); + next(flowElements, flowElement, map, data); + return data; + } + + /** + * 启动流程时获取下一步骤的用户任务 + * + * @param repositoryService + * @param map + * @return + */ + public static List getNextUserTasksByStart(RepositoryService repositoryService, + ProcessDefinition processDefinition, Map map) { + List data = new ArrayList<>(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); + Process mainProcess = bpmnModel.getMainProcess(); + Collection flowElements = mainProcess.getFlowElements(); + String key = null; + // 找到开始节点 并获取唯一key + for (FlowElement flowElement : flowElements) { + if (flowElement instanceof StartEvent) { + key = flowElement.getId(); + break; + } + } + FlowElement flowElement = bpmnModel.getFlowElement(key); + List sequenceFlows = ((StartEvent) flowElement).getOutgoingFlows(); + // 获取出口连线, 此时从开始节点往后,只能是一个出口 + if (!sequenceFlows.isEmpty()) { + SequenceFlow sequenceFlow = sequenceFlows.get(0); + FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement(); + next(flowElements, targetFlowElement, map, data); + } + return data; + } + + /** + * 查找下一节点 + * + * @param flowElements + * @param flowElement + * @param map + * @param nextUser + */ + public static void next(Collection flowElements, FlowElement flowElement, Map map, + List nextUser) { + // 如果是结束节点 + if (flowElement instanceof EndEvent) { + // 如果是子任务的结束节点 + if (getSubProcess(flowElements, flowElement) != null) { + flowElement = getSubProcess(flowElements, flowElement); + } + } + // 获取Task的出线信息--可以拥有多个 + List outGoingFlows = null; + if (flowElement instanceof Task) { + outGoingFlows = ((Task) flowElement).getOutgoingFlows(); + } else if (flowElement instanceof Gateway) { + outGoingFlows = ((Gateway) flowElement).getOutgoingFlows(); + } else if (flowElement instanceof StartEvent) { + outGoingFlows = ((StartEvent) flowElement).getOutgoingFlows(); + } else if (flowElement instanceof SubProcess) { + outGoingFlows = ((SubProcess) flowElement).getOutgoingFlows(); + } else if (flowElement instanceof CallActivity) { + outGoingFlows = ((CallActivity) flowElement).getOutgoingFlows(); + } + if (outGoingFlows != null && outGoingFlows.size() > 0) { + // 遍历所有的出线--找到可以正确执行的那一条 + for (SequenceFlow sequenceFlow : outGoingFlows) { + // 1.有表达式,且为true + // 2.无表达式 + String expression = sequenceFlow.getConditionExpression(); + if (expression == null || + expressionResult(map, + expression.substring(expression.lastIndexOf("{") + 1, expression.lastIndexOf("}")))) { + // 出线的下一节点 + String nextFlowElementID = sequenceFlow.getTargetRef(); + if (checkSubProcess(nextFlowElementID, flowElements, nextUser)) { + continue; + } + + // 查询下一节点的信息 + FlowElement nextFlowElement = getFlowElementById(nextFlowElementID, flowElements); + // 调用流程 + if (nextFlowElement instanceof CallActivity) { + CallActivity ca = (CallActivity) nextFlowElement; + if (ca.getLoopCharacteristics() != null) { + UserTask userTask = new UserTask(); + userTask.setId(ca.getId()); + + userTask.setId(ca.getId()); + userTask.setLoopCharacteristics(ca.getLoopCharacteristics()); + userTask.setName(ca.getName()); + nextUser.add(userTask); + } + next(flowElements, nextFlowElement, map, nextUser); + } + // 用户任务 + if (nextFlowElement instanceof UserTask) { + nextUser.add((UserTask) nextFlowElement); + } + // 排他网关 + else if (nextFlowElement instanceof ExclusiveGateway) { + next(flowElements, nextFlowElement, map, nextUser); + } + // 并行网关 + else if (nextFlowElement instanceof ParallelGateway) { + next(flowElements, nextFlowElement, map, nextUser); + } + // 接收任务 + else if (nextFlowElement instanceof ReceiveTask) { + next(flowElements, nextFlowElement, map, nextUser); + } + // 服务任务 + else if (nextFlowElement instanceof ServiceTask) { + next(flowElements, nextFlowElement, map, nextUser); + } + // 子任务的起点 + else if (nextFlowElement instanceof StartEvent) { + next(flowElements, nextFlowElement, map, nextUser); + } + // 结束节点 + else if (nextFlowElement instanceof EndEvent) { + next(flowElements, nextFlowElement, map, nextUser); + } + } + } + } + } + + /** + * 判断是否是多实例子流程并且需要设置集合类型变量 + */ + public static boolean checkSubProcess(String id, Collection flowElements, List nextUser) { + for (FlowElement flowElement1 : flowElements) { + if (flowElement1 instanceof SubProcess && flowElement1.getId().equals(id)) { + + SubProcess sp = (SubProcess) flowElement1; + if (sp.getLoopCharacteristics() != null) { + // String inputDataItem = sp.getLoopCharacteristics().getInputDataItem(); + UserTask userTask = new UserTask(); + userTask.setId(sp.getId()); + userTask.setLoopCharacteristics(sp.getLoopCharacteristics()); + userTask.setName(sp.getName()); + nextUser.add(userTask); + return true; + } + } + } + + return false; + + } + + /** + * 查询一个节点的是否子任务中的节点,如果是,返回子任务 + * + * @param flowElements 全流程的节点集合 + * @param flowElement 当前节点 + * @return + */ + public static FlowElement getSubProcess(Collection flowElements, FlowElement flowElement) { + for (FlowElement flowElement1 : flowElements) { + if (flowElement1 instanceof SubProcess) { + for (FlowElement flowElement2 : ((SubProcess) flowElement1).getFlowElements()) { + if (flowElement.equals(flowElement2)) { + return flowElement1; + } + } + } + } + return null; + } + + /** + * 根据ID查询流程节点对象, 如果是子任务,则返回子任务的开始节点 + * + * @param Id 节点ID + * @param flowElements 流程节点集合 + * @return + */ + public static FlowElement getFlowElementById(String Id, Collection flowElements) { + for (FlowElement flowElement : flowElements) { + if (flowElement.getId().equals(Id)) { + // 如果是子任务,则查询出子任务的开始节点 + if (flowElement instanceof SubProcess) { + return getStartFlowElement(((SubProcess) flowElement).getFlowElements()); + } + return flowElement; + } + if (flowElement instanceof SubProcess) { + FlowElement flowElement1 = getFlowElementById(Id, ((SubProcess) flowElement).getFlowElements()); + if (flowElement1 != null) { + return flowElement1; + } + } + } + return null; + } + + /** + * 返回流程的开始节点 + * + * @param flowElements 节点集合 + * @description: + */ + public static FlowElement getStartFlowElement(Collection flowElements) { + for (FlowElement flowElement : flowElements) { + if (flowElement instanceof StartEvent) { + return flowElement; + } + } + return null; + } + + /** + * 校验el表达式 + * + * @param map + * @param expression + * @return + */ + public static boolean expressionResult(Map map, String expression) { + Expression exp = AviatorEvaluator.compile(expression); + return (Boolean) exp.execute(map); + // return true; + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/FlowableUtils.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/FlowableUtils.java new file mode 100644 index 0000000..763b73c --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/FlowableUtils.java @@ -0,0 +1,772 @@ +package com.ruoyi.flowable.flow; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.Stack; +import java.util.stream.Collectors; + +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.EndEvent; +import org.flowable.bpmn.model.ExtensionAttribute; +import org.flowable.bpmn.model.ExtensionElement; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowNode; +import org.flowable.bpmn.model.Gateway; +import org.flowable.bpmn.model.SequenceFlow; +import org.flowable.bpmn.model.StartEvent; +import org.flowable.bpmn.model.SubProcess; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; +import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.task.api.history.HistoricTaskInstance; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author Tony + * @date 2021-04-03 23:57 + */ +@Slf4j +public class FlowableUtils { + + /** + * 根据节点,获取入口连线 + * + * @param source + * @return + */ + public static List getElementIncomingFlows(FlowElement source) { + List sequenceFlows = null; + if (source instanceof FlowNode) { + sequenceFlows = ((FlowNode) source).getIncomingFlows(); + } else if (source instanceof Gateway) { + sequenceFlows = ((Gateway) source).getIncomingFlows(); + } else if (source instanceof SubProcess) { + sequenceFlows = ((SubProcess) source).getIncomingFlows(); + } else if (source instanceof StartEvent) { + sequenceFlows = ((StartEvent) source).getIncomingFlows(); + } else if (source instanceof EndEvent) { + sequenceFlows = ((EndEvent) source).getIncomingFlows(); + } + return sequenceFlows; + } + + /** + * 根据节点,获取出口连线 + * + * @param source + * @return + */ + public static List getElementOutgoingFlows(FlowElement source) { + List sequenceFlows = null; + if (source instanceof FlowNode) { + sequenceFlows = ((FlowNode) source).getOutgoingFlows(); + } else if (source instanceof Gateway) { + sequenceFlows = ((Gateway) source).getOutgoingFlows(); + } else if (source instanceof SubProcess) { + sequenceFlows = ((SubProcess) source).getOutgoingFlows(); + } else if (source instanceof StartEvent) { + sequenceFlows = ((StartEvent) source).getOutgoingFlows(); + } else if (source instanceof EndEvent) { + sequenceFlows = ((EndEvent) source).getOutgoingFlows(); + } + return sequenceFlows; + } + + /** + * 获取全部节点列表,包含子流程节点 + * + * @param flowElements + * @param allElements + * @return + */ + public static Collection getAllElements(Collection flowElements, + Collection allElements) { + allElements = allElements == null ? new ArrayList<>() : allElements; + + for (FlowElement flowElement : flowElements) { + allElements.add(flowElement); + if (flowElement instanceof SubProcess) { + // 继续深入子流程,进一步获取子流程 + allElements = FlowableUtils.getAllElements(((SubProcess) flowElement).getFlowElements(), allElements); + } + } + return allElements; + } + + /** + * 迭代获取父级任务节点列表,向前找 + * + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 已找到的用户任务节点 + * @return + */ + public static List iteratorFindParentUserTasks(FlowElement source, Set hasSequenceFlow, + List userTaskList) { + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + userTaskList = iteratorFindParentUserTasks(source.getSubProcess(), hasSequenceFlow, userTaskList); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 类型为用户节点,则新增父级节点 + if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { + userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement()); + continue; + } + // 类型为子流程,则添加子流程开始节点出口处相连的节点 + if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { + // 获取子流程用户任务节点 + List childUserTaskList = findChildProcessUserTasks( + (StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements() + .toArray()[0], + null, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (childUserTaskList != null && childUserTaskList.size() > 0) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = iteratorFindParentUserTasks(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, + userTaskList); + } + } + return userTaskList; + } + + /** + * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找 + * + * @param source 起始节点(退回节点) + * @param runTaskKeyList 正在运行的任务 Key,用于校验任务节点是否是正在运行的节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 需要撤回的用户任务列表 + * @return + */ + public static List iteratorFindChildUserTasks(FlowElement source, List runTaskKeyList, + Set hasSequenceFlow, List userTaskList) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof EndEvent && source.getSubProcess() != null) { + userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, + userTaskList); + } + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask + && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) { + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); + continue; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + List childUserTaskList = iteratorFindChildUserTasks( + (FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements() + .toArray()[0]), + runTaskKeyList, hasSequenceFlow, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (childUserTaskList != null && childUserTaskList.size() > 0) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, + hasSequenceFlow, userTaskList); + } + } + return userTaskList; + } + + /** + * 迭代获取子流程用户任务节点 + * + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 需要撤回的用户任务列表 + * @return + */ + public static List findChildProcessUserTasks(FlowElement source, Set hasSequenceFlow, + List userTaskList) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask) { + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); + continue; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + List childUserTaskList = findChildProcessUserTasks( + (FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements() + .toArray()[0]), + hasSequenceFlow, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (childUserTaskList != null && childUserTaskList.size() > 0) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, + userTaskList); + } + } + return userTaskList; + } + + /** + * 从后向前寻路,获取所有脏线路上的点 + * + * @param source 起始节点 + * @param passRoads 已经经过的点集合 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param targets 目标脏线路终点 + * @param dirtyRoads 确定为脏数据的点,因为不需要重复,因此使用 set 存储 + * @return + */ + public static Set iteratorFindDirtyRoads(FlowElement source, List passRoads, + Set hasSequenceFlow, List targets, Set dirtyRoads) { + passRoads = passRoads == null ? new ArrayList<>() : passRoads; + dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + dirtyRoads = iteratorFindDirtyRoads(source.getSubProcess(), passRoads, hasSequenceFlow, targets, + dirtyRoads); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 新增经过的路线 + passRoads.add(sequenceFlow.getSourceFlowElement().getId()); + // 如果此点为目标点,确定经过的路线为脏线路,添加点到脏线路中,然后找下个连线 + if (targets.contains(sequenceFlow.getSourceFlowElement().getId())) { + dirtyRoads.addAll(passRoads); + continue; + } + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { + dirtyRoads = findChildProcessAllDirtyRoad( + (StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements() + .toArray()[0], + null, dirtyRoads); + // 是否存在子流程上,true 是,false 否 + Boolean isInChildProcess = dirtyTargetInChildProcess( + (StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements() + .toArray()[0], + null, targets, null); + if (isInChildProcess) { + // 已在子流程上找到,该路线结束 + continue; + } + } + // 继续迭代 + dirtyRoads = iteratorFindDirtyRoads(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, + targets, dirtyRoads); + } + } + return dirtyRoads; + } + + /** + * 迭代获取子流程脏路线 + * 说明,假如回退的点就是子流程,那么也肯定会回退到子流程最初的用户任务节点,因此子流程中的节点全是脏路线 + * + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param dirtyRoads 确定为脏数据的点,因为不需要重复,因此使用 set 存储 + * @return + */ + public static Set findChildProcessAllDirtyRoad(FlowElement source, Set hasSequenceFlow, + Set dirtyRoads) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 添加脏路线 + dirtyRoads.add(sequenceFlow.getTargetFlowElement().getId()); + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + dirtyRoads = findChildProcessAllDirtyRoad( + (FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements() + .toArray()[0]), + hasSequenceFlow, dirtyRoads); + } + // 继续迭代 + dirtyRoads = findChildProcessAllDirtyRoad(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, + dirtyRoads); + } + } + return dirtyRoads; + } + + /** + * 判断脏路线结束节点是否在子流程上 + * + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param targets 判断脏路线节点是否存在子流程上,只要存在一个,说明脏路线只到子流程为止 + * @param inChildProcess 是否存在子流程上,true 是,false 否 + * @return + */ + public static Boolean dirtyTargetInChildProcess(FlowElement source, Set hasSequenceFlow, + List targets, Boolean inChildProcess) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + inChildProcess = inChildProcess != null && inChildProcess; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null && !inChildProcess) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果发现目标点在子流程上存在,说明只到子流程为止 + if (targets.contains(sequenceFlow.getTargetFlowElement().getId())) { + inChildProcess = true; + break; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + inChildProcess = dirtyTargetInChildProcess( + (FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements() + .toArray()[0]), + hasSequenceFlow, targets, inChildProcess); + } + // 继续迭代 + inChildProcess = dirtyTargetInChildProcess(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, + targets, inChildProcess); + } + } + return inChildProcess; + } + + /** + * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 + * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 + * + * @param source 起始节点 + * @param isSequential 是否串行 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param targetKsy 目标节点 + * @return + */ + public static Boolean iteratorCheckSequentialReferTarget(FlowElement source, String targetKsy, + Set hasSequenceFlow, Boolean isSequential) { + isSequential = isSequential == null || isSequential; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + isSequential = iteratorCheckSequentialReferTarget(source.getSubProcess(), targetKsy, hasSequenceFlow, + isSequential); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果目标节点已被判断为并行,后面都不需要执行,直接返回 + if (!isSequential) { + break; + } + // 这条线路存在目标节点,这条线路完成,进入下个线路 + if (targetKsy.equals(sequenceFlow.getSourceFlowElement().getId())) { + continue; + } + if (sequenceFlow.getSourceFlowElement() instanceof StartEvent) { + isSequential = false; + break; + } + // 否则就继续迭代 + isSequential = iteratorCheckSequentialReferTarget(sequenceFlow.getSourceFlowElement(), targetKsy, + hasSequenceFlow, isSequential); + } + } + return isSequential; + } + + /** + * 从后向前寻路,获取到达节点的所有路线 + * 不存在直接回退到子流程,但是存在回退到父级流程的情况 + * + * @param source 起始节点 + * @param passRoads 已经经过的点集合 + * @param roads 路线 + * @return + */ + public static List> findRoad(FlowElement source, List passRoads, + Set hasSequenceFlow, List> roads) { + passRoads = passRoads == null ? new ArrayList<>() : passRoads; + roads = roads == null ? new ArrayList<>() : roads; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + roads = findRoad(source.getSubProcess(), passRoads, hasSequenceFlow, roads); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null && sequenceFlows.size() != 0) { + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 添加经过路线 + if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { + passRoads.add((UserTask) sequenceFlow.getSourceFlowElement()); + } + // 继续迭代 + roads = findRoad(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, roads); + } + } else { + // 添加路线 + roads.add(passRoads); + } + return roads; + } + + /** + * 历史节点数据清洗,清洗掉又回滚导致的脏数据 + * + * @param allElements 全部节点信息 + * @param historicTaskInstanceList 历史任务实例信息,数据采用开始时间升序 + * @return + */ + public static List historicTaskInstanceClean(Collection allElements, + List historicTaskInstanceList) { + // 会签节点收集 + List multiTask = new ArrayList<>(); + allElements.forEach(flowElement -> { + if (flowElement instanceof UserTask) { + // 如果该节点的行为为会签行为,说明该节点为会签节点 + if (((UserTask) flowElement).getBehavior() instanceof ParallelMultiInstanceBehavior + || ((UserTask) flowElement).getBehavior() instanceof SequentialMultiInstanceBehavior) { + multiTask.add(flowElement.getId()); + } + } + }); + // 循环放入栈,栈 LIFO:后进先出 + Stack stack = new Stack<>(); + historicTaskInstanceList.forEach(stack::push); + // 清洗后的历史任务实例 + List lastHistoricTaskInstanceList = new ArrayList<>(); + // 网关存在可能只走了部分分支情况,且还存在跳转废弃数据以及其他分支数据的干扰,因此需要对历史节点数据进行清洗 + // 临时用户任务 key + StringBuilder userTaskKey = null; + // 临时被删掉的任务 key,存在并行情况 + List deleteKeyList = new ArrayList<>(); + // 临时脏数据线路 + List> dirtyDataLineList = new ArrayList<>(); + // 由某个点跳到会签点,此时出现多个会签实例对应 1 个跳转情况,需要把这些连续脏数据都找到 + // 会签特殊处理下标 + int multiIndex = -1; + // 会签特殊处理 key + StringBuilder multiKey = null; + // 会签特殊处理操作标识 + boolean multiOpera = false; + while (!stack.empty()) { + // 从这里开始 userTaskKey 都还是上个栈的 key + // 是否是脏数据线路上的点 + final boolean[] isDirtyData = { false }; + for (Set oldDirtyDataLine : dirtyDataLineList) { + if (oldDirtyDataLine.contains(stack.peek().getTaskDefinitionKey())) { + isDirtyData[0] = true; + } + } + // 删除原因不为空,说明从这条数据开始回跳或者回退的 + // MI_END:会签完成后,其他未签到节点的删除原因,不在处理范围内 + if (stack.peek().getDeleteReason() != null && !"MI_END".equals(stack.peek().getDeleteReason())) { + // 可以理解为脏线路起点 + String dirtyPoint = ""; + if (stack.peek().getDeleteReason().contains("Change activity to ")) { + dirtyPoint = stack.peek().getDeleteReason().replace("Change activity to ", ""); + } + // 会签回退删除原因有点不同 + if (stack.peek().getDeleteReason().contains("Change parent activity to ")) { + dirtyPoint = stack.peek().getDeleteReason().replace("Change parent activity to ", ""); + } + FlowElement dirtyTask = null; + // 获取变更节点的对应的入口处连线 + // 如果是网关并行回退情况,会变成两条脏数据路线,效果一样 + for (FlowElement flowElement : allElements) { + if (flowElement.getId().equals(stack.peek().getTaskDefinitionKey())) { + dirtyTask = flowElement; + } + } + // 获取脏数据线路 + Set dirtyDataLine = FlowableUtils.iteratorFindDirtyRoads(dirtyTask, null, null, + Arrays.asList(dirtyPoint.split(",")), null); + // 自己本身也是脏线路上的点,加进去 + dirtyDataLine.add(stack.peek().getTaskDefinitionKey()); + log.info(stack.peek().getTaskDefinitionKey() + "点脏路线集合:" + dirtyDataLine); + // 是全新的需要添加的脏线路 + boolean isNewDirtyData = true; + for (int i = 0; i < dirtyDataLineList.size(); i++) { + // 如果发现他的上个节点在脏线路内,说明这个点可能是并行的节点,或者连续驳回 + // 这时,都以之前的脏线路节点为标准,只需合并脏线路即可,也就是路线补全 + if (dirtyDataLineList.get(i).contains(userTaskKey.toString())) { + isNewDirtyData = false; + dirtyDataLineList.get(i).addAll(dirtyDataLine); + } + } + // 已确定时全新的脏线路 + if (isNewDirtyData) { + // deleteKey 单一路线驳回到并行,这种同时生成多个新实例记录情况,这时 deleteKey 其实是由多个值组成 + // 按照逻辑,回退后立刻生成的实例记录就是回退的记录 + // 至于驳回所生成的 Key,直接从删除原因中获取,因为存在驳回到并行的情况 + deleteKeyList.add(dirtyPoint + ","); + dirtyDataLineList.add(dirtyDataLine); + } + // 添加后,现在这个点变成脏线路上的点了 + isDirtyData[0] = true; + } + // 如果不是脏线路上的点,说明是有效数据,添加历史实例 Key + if (!isDirtyData[0]) { + lastHistoricTaskInstanceList.add(stack.peek().getTaskDefinitionKey()); + } + // 校验脏线路是否结束 + for (int i = 0; i < deleteKeyList.size(); i++) { + // 如果发现脏数据属于会签,记录下下标与对应 Key,以备后续比对,会签脏数据范畴开始 + if (multiKey == null && multiTask.contains(stack.peek().getTaskDefinitionKey()) + && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) { + multiIndex = i; + multiKey = new StringBuilder(stack.peek().getTaskDefinitionKey()); + } + // 会签脏数据处理,节点退回会签清空 + // 如果在会签脏数据范畴中发现 Key改变,说明会签脏数据在上个节点就结束了,可以把会签脏数据删掉 + if (multiKey != null && !multiKey.toString().equals(stack.peek().getTaskDefinitionKey())) { + deleteKeyList.set(multiIndex, + deleteKeyList.get(multiIndex).replace(stack.peek().getTaskDefinitionKey() + ",", "")); + multiKey = null; + // 结束进行下校验删除 + multiOpera = true; + } + // 其他脏数据处理 + // 发现该路线最后一条脏数据,说明这条脏数据线路处理完了,删除脏数据信息 + // 脏数据产生的新实例中是否包含这条数据 + if (multiKey == null && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) { + // 删除匹配到的部分 + deleteKeyList.set(i, deleteKeyList.get(i).replace(stack.peek().getTaskDefinitionKey() + ",", "")); + } + // 如果每组中的元素都以匹配过,说明脏数据结束 + if ("".equals(deleteKeyList.get(i))) { + // 同时删除脏数据 + deleteKeyList.remove(i); + dirtyDataLineList.remove(i); + break; + } + } + // 会签数据处理需要在循环外处理,否则可能导致溢出 + // 会签的数据肯定是之前放进去的所以理论上不会溢出,但还是校验下 + if (multiOpera && deleteKeyList.size() > multiIndex && "".equals(deleteKeyList.get(multiIndex))) { + // 同时删除脏数据 + deleteKeyList.remove(multiIndex); + dirtyDataLineList.remove(multiIndex); + multiIndex = -1; + multiOpera = false; + } + // pop() 方法与 peek() 方法不同,在返回值的同时,会把值从栈中移除 + // 保存新的 userTaskKey 在下个循环中使用 + userTaskKey = new StringBuilder(stack.pop().getTaskDefinitionKey()); + } + log.info("清洗后的历史节点数据:" + lastHistoricTaskInstanceList); + return lastHistoricTaskInstanceList; + } + + /** + * 从 flowElement 获取 指定名称的 拓展元素 + * + * @param flowElement 元素 + * @param extensionElementName 拓展元素名称 + */ + public static ExtensionElement getExtensionElementFromFlowElementByName(FlowElement flowElement, + String extensionElementName) { + + if (flowElement == null) { + return null; + } + Map> extensionElements = flowElement.getExtensionElements(); + for (Map.Entry> stringEntry : extensionElements.entrySet()) { + if (stringEntry.getKey().equals(extensionElementName)) { + for (ExtensionElement extensionElement : stringEntry.getValue()) { + if (extensionElement.getName().equals(extensionElementName)) { + return extensionElement; + } + } + } + } + + return null; + } + + /** + * 获取当前任务节点扩展属性信息 + * + * @param repositoryService + * @param task 当前任务 + * @return 自定义属性列表 + */ + public static List getPropertyElement(RepositoryService repositoryService, + org.flowable.task.api.Task task) { + FlowElement flowElement = getCurrentElement(repositoryService, task); + ExtensionElement extensionElement = FlowableUtils.getExtensionElementFromFlowElementByName(flowElement, + "properties"); + if (extensionElement == null) { + return Collections.emptyList(); + } + return getPropertyExtensionElementByName(extensionElement, "property"); + } + + /** + * 获取当前任务节点 + * + * @param repositoryService + * @param task + * @return + */ + public static FlowElement getCurrentElement(RepositoryService repositoryService, org.flowable.task.api.Task task) { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()).singleResult(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); + return bpmnModel.getFlowElement(task.getTaskDefinitionKey()); + } + + /** + * 根据属性名获取扩展元素中的扩展属性列表 + * + * @param extensionElement 扩展元素 + * @param attributesName 属性名 + * @return 扩展属性列表 + */ + public static List getPropertyExtensionElementByName(ExtensionElement extensionElement, + String attributesName) { + try { + // 获取名称为attributesName的子元素 + return Optional.ofNullable(extensionElement.getChildElements().get(attributesName)) + .orElse(Collections.emptyList()) // 如果子元素不存在则返回空集合,避免null引用 + .stream() + .map(element -> { + // 获取子元素的属性 + Map> attributes = element.getAttributes(); + Object propertyDto = new Object(); + // 获取FlowPropertyDto的所有属性 + Arrays.stream(propertyDto.getClass().getDeclaredFields()) + .forEach(field -> { + field.setAccessible(true); + // 获取属性名称和值 + attributes.getOrDefault(field.getName(), Collections.emptyList()) + .stream() + .findFirst() + .ifPresent(attribute -> { + try { + // 反射设置属性值 + field.set(propertyDto, attribute.getValue()); + } catch (IllegalAccessException e) { + e.printStackTrace(); + // 如果反射设置失败则忽略该属性 + } + }); + }); + return propertyDto; + }).collect(Collectors.toList()); + } catch (Exception e) { + e.printStackTrace(); + return Collections.emptyList(); // 如果发生异常则返回空列表 + } + } + +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/listener/FlowExecutionListener.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/listener/FlowExecutionListener.java new file mode 100644 index 0000000..5de0511 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/listener/FlowExecutionListener.java @@ -0,0 +1,37 @@ +package com.ruoyi.flowable.listener; + +import org.flowable.common.engine.api.delegate.Expression; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +/** + * 执行监听器 + * + * 执行监听器允许在执行过程中执行Java代码。 + * 执行监听器可以捕获事件的类型: + * 流程实例启动,结束 + * 输出流捕获 + * 获取启动,结束 + * 路由开始,结束 + * 中间事件开始,结束 + * 触发开始事件,触发结束事件 + * + * @author Tony + * @date 2022/12/16 + */ +@Slf4j +@Component +public class FlowExecutionListener implements ExecutionListener { + /** + * 流程设计器添加的参数 + */ + private Expression param; + + @Override + public void notify(DelegateExecution execution) { + log.info("执行监听器:{},参数:{}", execution, param); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/listener/FlowTaskListener.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/listener/FlowTaskListener.java new file mode 100644 index 0000000..eafce92 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/listener/FlowTaskListener.java @@ -0,0 +1,38 @@ +package com.ruoyi.flowable.listener; + +import org.flowable.common.engine.api.delegate.Expression; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.service.delegate.DelegateTask; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +/** + * 任务监听器 + * + * create(创建):在任务被创建且所有的任务属性设置完成后才触发 + * assignment(指派):在任务被分配给某个办理人之后触发 + * complete(完成):在配置了监听器的上一个任务完成时触发 + * delete(删除):在任务即将被删除前触发。请注意任务由completeTask正常完成时也会触发 + * + * @author Tony + * @date 2021/4/20 + */ +@Slf4j +@Component +public class FlowTaskListener implements TaskListener { + + /** + * 流程设计器添加的参数 + */ + private Expression param; + + @Override + public void notify(DelegateTask delegateTask) { + + // 获取事件类型 delegateTask.getEventName(),可以通过监听器给任务执行人发送相应的通知消息 + log.info("任务监听器:{} 参数:{}", delegateTask, param.getValue(delegateTask)); + + } + +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/FlowDeployMapper.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/FlowDeployMapper.java new file mode 100644 index 0000000..793119a --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/FlowDeployMapper.java @@ -0,0 +1,23 @@ +package com.ruoyi.flowable.mapper; + +import java.util.List; + +import com.ruoyi.flowable.domain.dto.FlowProcDefDto; + +/** + * 流程定义查询 + * + * @author Tony + * @email + * @date 2022/1/29 5:44 下午 + **/ +public interface FlowDeployMapper { + + /** + * 流程定义列表 + * + * @param name + * @return + */ + List selectDeployList(String name); +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/SysDeployFormMapper.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/SysDeployFormMapper.java new file mode 100644 index 0000000..7c45951 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/SysDeployFormMapper.java @@ -0,0 +1,74 @@ +package com.ruoyi.flowable.mapper; + + + +import java.util.List; + +import com.ruoyi.flowable.domain.SysDeployForm; +import com.ruoyi.form.domain.FormTemplate; + +/** + * 流程实例关联表单Mapper接口 + * + * @author Tony + * @date 2021-03-30 + */ +public interface SysDeployFormMapper +{ + /** + * 查询流程实例关联表单 + * + * @param id 流程实例关联表单ID + * @return 流程实例关联表单 + */ + public SysDeployForm selectSysDeployFormById(Long id); + + /** + * 查询流程实例关联表单列表 + * + * @param SysDeployForm 流程实例关联表单 + * @return 流程实例关联表单集合 + */ + public List selectSysDeployFormList(SysDeployForm SysDeployForm); + + /** + * 新增流程实例关联表单 + * + * @param SysDeployForm 流程实例关联表单 + * @return 结果 + */ + public int insertSysDeployForm(SysDeployForm SysDeployForm); + + /** + * 修改流程实例关联表单 + * + * @param SysDeployForm 流程实例关联表单 + * @return 结果 + */ + public int updateSysDeployForm(SysDeployForm SysDeployForm); + + /** + * 删除流程实例关联表单 + * + * @param id 流程实例关联表单ID + * @return 结果 + */ + public int deleteSysDeployFormById(Long id); + + /** + * 批量删除流程实例关联表单 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteSysDeployFormByIds(Long[] ids); + + + + /** + * 查询流程挂着的表单 + * @param deployId + * @return + */ + FormTemplate selectSysDeployFormByDeployId(String deployId); +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/SysExpressionMapper.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/SysExpressionMapper.java new file mode 100644 index 0000000..1bb204e --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/SysExpressionMapper.java @@ -0,0 +1,63 @@ +package com.ruoyi.flowable.mapper; + +import java.util.List; + +import com.ruoyi.flowable.domain.SysExpression; + + +/** + * 流程达式Mapper接口 + * + * @author ruoyi + * @date 2022-12-12 + */ +public interface SysExpressionMapper +{ + /** + * 查询流程达式 + * + * @param id 流程达式主键 + * @return 流程达式 + */ + public SysExpression selectSysExpressionById(Long id); + + /** + * 查询流程达式列表 + * + * @param sysExpression 流程达式 + * @return 流程达式集合 + */ + public List selectSysExpressionList(SysExpression sysExpression); + + /** + * 新增流程达式 + * + * @param sysExpression 流程达式 + * @return 结果 + */ + public int insertSysExpression(SysExpression sysExpression); + + /** + * 修改流程达式 + * + * @param sysExpression 流程达式 + * @return 结果 + */ + public int updateSysExpression(SysExpression sysExpression); + + /** + * 删除流程达式 + * + * @param id 流程达式主键 + * @return 结果 + */ + public int deleteSysExpressionById(Long id); + + /** + * 批量删除流程达式 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSysExpressionByIds(Long[] ids); +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/SysListenerMapper.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/SysListenerMapper.java new file mode 100644 index 0000000..44165c2 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/SysListenerMapper.java @@ -0,0 +1,62 @@ +package com.ruoyi.flowable.mapper; + +import java.util.List; + +import com.ruoyi.flowable.domain.SysListener; + +/** + * 流程监听Mapper接口 + * + * @author Tony + * @date 2022-12-25 + */ +public interface SysListenerMapper +{ + /** + * 查询流程监听 + * + * @param id 流程监听主键 + * @return 流程监听 + */ + public SysListener selectSysListenerById(Long id); + + /** + * 查询流程监听列表 + * + * @param sysListener 流程监听 + * @return 流程监听集合 + */ + public List selectSysListenerList(SysListener sysListener); + + /** + * 新增流程监听 + * + * @param sysListener 流程监听 + * @return 结果 + */ + public int insertSysListener(SysListener sysListener); + + /** + * 修改流程监听 + * + * @param sysListener 流程监听 + * @return 结果 + */ + public int updateSysListener(SysListener sysListener); + + /** + * 删除流程监听 + * + * @param id 流程监听主键 + * @return 结果 + */ + public int deleteSysListenerById(Long id); + + /** + * 批量删除流程监听 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSysListenerByIds(Long[] ids); +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/SysTaskFormMapper.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/SysTaskFormMapper.java new file mode 100644 index 0000000..2458698 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/mapper/SysTaskFormMapper.java @@ -0,0 +1,64 @@ +package com.ruoyi.flowable.mapper; + + + +import java.util.List; + +import com.ruoyi.flowable.domain.SysTaskForm; + +/** + * 流程任务关联单Mapper接口 + * + * @author Tony + * @date 2021-04-03 + */ +public interface SysTaskFormMapper +{ + /** + * 查询流程任务关联单 + * + * @param id 流程任务关联单ID + * @return 流程任务关联单 + */ + public SysTaskForm selectSysTaskFormById(Long id); + + /** + * 查询流程任务关联单列表 + * + * @param sysTaskForm 流程任务关联单 + * @return 流程任务关联单集合 + */ + public List selectSysTaskFormList(SysTaskForm sysTaskForm); + + /** + * 新增流程任务关联单 + * + * @param sysTaskForm 流程任务关联单 + * @return 结果 + */ + public int insertSysTaskForm(SysTaskForm sysTaskForm); + + /** + * 修改流程任务关联单 + * + * @param sysTaskForm 流程任务关联单 + * @return 结果 + */ + public int updateSysTaskForm(SysTaskForm sysTaskForm); + + /** + * 删除流程任务关联单 + * + * @param id 流程任务关联单ID + * @return 结果 + */ + public int deleteSysTaskFormById(Long id); + + /** + * 批量删除流程任务关联单 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteSysTaskFormByIds(Long[] ids); +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowDefinitionService.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowDefinitionService.java new file mode 100644 index 0000000..7d5a864 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowDefinitionService.java @@ -0,0 +1,80 @@ +package com.ruoyi.flowable.service; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.flowable.domain.dto.FlowProcDefDto; + +/** + * @author Tony + * @date 2021-04-03 14:41 + */ +public interface IFlowDefinitionService { + + boolean exist(String processDefinitionKey); + + + /** + * 流程定义列表 + * + * @param pageNum 当前页码 + * @param pageSize 每页条数 + * @return 流程定义分页列表数据 + */ + List list(String name,Integer pageNum, Integer pageSize); + + /** + * 导入流程文件 + * 当每个key的流程第一次部署时,指定版本为1。对其后所有使用相同key的流程定义, + * 部署时版本会在该key当前已部署的最高版本号基础上加1。key参数用于区分流程定义 + * @param name + * @param category + * @param in + */ + void importFile(String name, String category, InputStream in); + + /** + * 读取xml + * @param deployId + * @return + */ + AjaxResult readXml(String deployId) throws IOException; + + /** + * 根据流程定义ID启动流程实例 + * + * @param procDefId + * @param variables + * @return + */ + + AjaxResult startProcessInstanceById(String procDefId, Map variables); + + + /** + * 激活或挂起流程定义 + * + * @param state 状态 + * @param deployId 流程部署ID + */ + void updateState(Integer state, String deployId); + + + /** + * 删除流程定义 + * + * @param deployId 流程部署ID act_ge_bytearray 表中 deployment_id值 + */ + void delete(String deployId); + + + /** + * 读取图片文件 + * @param deployId + * @return + */ + InputStream readImage(String deployId); +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowInstanceService.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowInstanceService.java new file mode 100644 index 0000000..8d91aaf --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowInstanceService.java @@ -0,0 +1,53 @@ +package com.ruoyi.flowable.service; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.flowable.domain.vo.FlowTaskVo; +import org.flowable.engine.history.HistoricProcessInstance; +import java.util.Map; + +/** + * @author Tony + * @date 2021-04-03 14:40 + */ +public interface IFlowInstanceService { + + /** + * 结束流程实例 + * + * @param vo + */ + void stopProcessInstance(FlowTaskVo vo); + + /** + * 激活或挂起流程实例 + * + * @param state 状态 + * @param instanceId 流程实例ID + */ + void updateState(Integer state, String instanceId); + + /** + * 删除流程实例ID + * + * @param instanceId 流程实例ID + * @param deleteReason 删除原因 + */ + void delete(String instanceId, String deleteReason); + + /** + * 根据实例ID查询历史实例数据 + * + * @param processInstanceId + * @return + */ + HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId); + + /** + * 根据流程定义ID启动流程实例 + * + * @param procDefId 流程定义Id + * @param variables 流程变量 + * @return + */ + AjaxResult startProcessInstanceById(String procDefId, Map variables); +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowTaskService.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowTaskService.java new file mode 100644 index 0000000..7732922 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowTaskService.java @@ -0,0 +1,218 @@ +package com.ruoyi.flowable.service; + +import java.io.InputStream; +import java.util.List; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.flowable.domain.dto.FlowTaskDto; +import com.ruoyi.flowable.domain.vo.FlowQueryVo; +import com.ruoyi.flowable.domain.vo.FlowTaskVo; + +/** + * @author Tony + * @date 2021-04-03 14:42 + */ +public interface IFlowTaskService { + + /** + * 审批任务 + * + * @param task 请求实体参数 + */ + AjaxResult complete(FlowTaskVo task); + + /** + * 驳回任务 + * + * @param flowTaskVo + */ + void taskReject(FlowTaskVo flowTaskVo); + + + /** + * 退回任务 + * + * @param flowTaskVo 请求实体参数 + */ + void taskReturn(FlowTaskVo flowTaskVo); + + /** + * 获取所有可回退的节点 + * + * @param flowTaskVo + * @return + */ + AjaxResult findReturnTaskList(FlowTaskVo flowTaskVo); + + /** + * 删除任务 + * + * @param flowTaskVo 请求实体参数 + */ + void deleteTask(FlowTaskVo flowTaskVo); + + /** + * 认领/签收任务 + * + * @param flowTaskVo 请求实体参数 + */ + void claim(FlowTaskVo flowTaskVo); + + /** + * 取消认领/签收任务 + * + * @param flowTaskVo 请求实体参数 + */ + void unClaim(FlowTaskVo flowTaskVo); + + /** + * 委派任务 + * + * @param flowTaskVo 请求实体参数 + */ + void delegateTask(FlowTaskVo flowTaskVo); + + /** + * 任务归还 + * + * @param flowTaskVo 请求实体参数 + */ + void resolveTask(FlowTaskVo flowTaskVo); + + + /** + * 转办任务 + * + * @param flowTaskVo 请求实体参数 + */ + void assignTask(FlowTaskVo flowTaskVo); + + + /** + * 多实例加签 + * @param flowTaskVo + */ + void addMultiInstanceExecution(FlowTaskVo flowTaskVo); + + /** + * 多实例减签 + * @param flowTaskVo + */ + void deleteMultiInstanceExecution(FlowTaskVo flowTaskVo); + + /** + * 我发起的流程 + * @param queryVo 请求参数 + * @return + */ + List myProcess(FlowQueryVo queryVo); + + /** + * 取消申请 + * 目前实现方式: 直接将当前流程变更为已完成 + * @param flowTaskVo + * @return + */ + AjaxResult stopProcess(FlowTaskVo flowTaskVo); + + /** + * 撤回流程 + * @param flowTaskVo + * @return + */ + AjaxResult revokeProcess(FlowTaskVo flowTaskVo); + + + /** + * 代办任务列表 + * + * @param queryVo 请求参数 + * @return + */ + List todoList(FlowQueryVo queryVo); + + + /** + * 已办任务列表 + * + * @param queryVo 请求参数 + * @return + */ + List finishedList(FlowQueryVo queryVo); + + /** + * 流程历史流转记录 + * + * @param procInsId 流程实例Id + * @return + */ + AjaxResult flowRecord(String procInsId,String deployId); + + /** + * 根据任务ID查询挂载的表单信息 + * + * @param taskId 任务Id + * @return + */ + AjaxResult getTaskForm(String taskId); + + /** + * 获取流程过程图 + * @param processId + * @return + */ + InputStream diagram(String processId); + + /** + * 获取流程执行节点 + * @param procInsId + * @return + */ + AjaxResult getFlowViewer(String procInsId,String executionId); + + /** + * 获取流程变量 + * @param taskId + * @return + */ + AjaxResult processVariables(String taskId); + + /** + * 获取下一节点 + * @param flowTaskVo 任务 + * @return + */ + AjaxResult getNextFlowNode(FlowTaskVo flowTaskVo); + + AjaxResult getNextFlowNodeByStart(FlowTaskVo flowTaskVo); + + /** + * 流程初始化表单 + * @param deployId + * @return + */ + AjaxResult flowFormData(String deployId); + + /** + * 流程节点信息 + * @param procInsId + * @return + */ + AjaxResult flowXmlAndNode(String procInsId,String deployId); + + /** + * 流程节点表单 + * @param taskId 流程任务编号 + * @return + */ + AjaxResult flowTaskForm(String taskId) throws Exception; + + + /** + * 流程节点信息 + * @param procInsId + * @param elementId + * @return + */ + AjaxResult flowTaskInfo(String procInsId, String elementId); +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/ISysDeployFormService.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/ISysDeployFormService.java new file mode 100644 index 0000000..13f31ac --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/ISysDeployFormService.java @@ -0,0 +1,69 @@ +package com.ruoyi.flowable.service; + +import java.util.List; + +import com.ruoyi.flowable.domain.SysDeployForm; +import com.ruoyi.form.domain.FormTemplate; +/** + * 流程实例关联表单Service接口 + * + * @author Tony + * @date 2021-04-03 + */ +public interface ISysDeployFormService +{ + /** + * 查询流程实例关联表单 + * + * @param id 流程实例关联表单ID + * @return 流程实例关联表单 + */ + public SysDeployForm selectSysDeployFormById(Long id); + + /** + * 查询流程实例关联表单列表 + * + * @param sysDeployForm 流程实例关联表单 + * @return 流程实例关联表单集合 + */ + public List selectSysDeployFormList(SysDeployForm sysDeployForm); + + /** + * 新增流程实例关联表单 + * + * @param sysDeployForm 流程实例关联表单 + * @return 结果 + */ + public int insertSysDeployForm(SysDeployForm sysDeployForm); + + /** + * 修改流程实例关联表单 + * + * @param sysDeployForm 流程实例关联表单 + * @return 结果 + */ + public int updateSysDeployForm(SysDeployForm sysDeployForm); + + /** + * 批量删除流程实例关联表单 + * + * @param ids 需要删除的流程实例关联表单ID + * @return 结果 + */ + public int deleteSysDeployFormByIds(Long[] ids); + + /** + * 删除流程实例关联表单信息 + * + * @param id 流程实例关联表单ID + * @return 结果 + */ + public int deleteSysDeployFormById(Long id); + + /** + * 查询流程挂着的表单 + * @param deployId + * @return + */ + FormTemplate selectSysDeployFormByDeployId(String deployId); +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/ISysExpressionService.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/ISysExpressionService.java new file mode 100644 index 0000000..aeb19cd --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/ISysExpressionService.java @@ -0,0 +1,62 @@ +package com.ruoyi.flowable.service; + +import java.util.List; + +import com.ruoyi.flowable.domain.SysExpression; + +/** + * 流程达式Service接口 + * + * @author ruoyi + * @date 2022-12-12 + */ +public interface ISysExpressionService +{ + /** + * 查询流程达式 + * + * @param id 流程达式主键 + * @return 流程达式 + */ + public SysExpression selectSysExpressionById(Long id); + + /** + * 查询流程达式列表 + * + * @param sysExpression 流程达式 + * @return 流程达式集合 + */ + public List selectSysExpressionList(SysExpression sysExpression); + + /** + * 新增流程达式 + * + * @param sysExpression 流程达式 + * @return 结果 + */ + public int insertSysExpression(SysExpression sysExpression); + + /** + * 修改流程达式 + * + * @param sysExpression 流程达式 + * @return 结果 + */ + public int updateSysExpression(SysExpression sysExpression); + + /** + * 批量删除流程达式 + * + * @param ids 需要删除的流程达式主键集合 + * @return 结果 + */ + public int deleteSysExpressionByIds(Long[] ids); + + /** + * 删除流程达式信息 + * + * @param id 流程达式主键 + * @return 结果 + */ + public int deleteSysExpressionById(Long id); +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/ISysListenerService.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/ISysListenerService.java new file mode 100644 index 0000000..9943fc5 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/ISysListenerService.java @@ -0,0 +1,62 @@ +package com.ruoyi.flowable.service; + +import java.util.List; + +import com.ruoyi.flowable.domain.SysListener; + +/** + * 流程监听Service接口 + * + * @author Tony + * @date 2022-12-25 + */ +public interface ISysListenerService +{ + /** + * 查询流程监听 + * + * @param id 流程监听主键 + * @return 流程监听 + */ + public SysListener selectSysListenerById(Long id); + + /** + * 查询流程监听列表 + * + * @param sysListener 流程监听 + * @return 流程监听集合 + */ + public List selectSysListenerList(SysListener sysListener); + + /** + * 新增流程监听 + * + * @param sysListener 流程监听 + * @return 结果 + */ + public int insertSysListener(SysListener sysListener); + + /** + * 修改流程监听 + * + * @param sysListener 流程监听 + * @return 结果 + */ + public int updateSysListener(SysListener sysListener); + + /** + * 批量删除流程监听 + * + * @param ids 需要删除的流程监听主键集合 + * @return 结果 + */ + public int deleteSysListenerByIds(Long[] ids); + + /** + * 删除流程监听信息 + * + * @param id 流程监听主键 + * @return 结果 + */ + public int deleteSysListenerById(Long id); +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowDefinitionServiceImpl.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowDefinitionServiceImpl.java new file mode 100644 index 0000000..ba80073 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowDefinitionServiceImpl.java @@ -0,0 +1,273 @@ +package com.ruoyi.flowable.service.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.io.IOUtils; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.FlowableIllegalArgumentException; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.repository.ProcessDefinitionQuery; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.image.impl.DefaultProcessDiagramGenerator; +import org.flowable.task.api.Task; +import org.springframework.stereotype.Service; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.PageUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.flowable.common.constant.ProcessConstants; +import com.ruoyi.flowable.common.enums.FlowComment; +import com.ruoyi.flowable.domain.dto.FlowProcDefDto; +import com.ruoyi.flowable.factory.FlowServiceFactory; +import com.ruoyi.flowable.mapper.FlowDeployMapper; +import com.ruoyi.flowable.service.IFlowDefinitionService; +import com.ruoyi.flowable.service.ISysDeployFormService; +import com.ruoyi.form.domain.FormTemplate; +import com.ruoyi.system.service.ISysDeptService; +import com.ruoyi.system.service.ISysUserService; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; + +/** + * 流程定义 + * + * @author Tony + * @date 2021-04-03 + */ +@Service +@Slf4j +public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFlowDefinitionService { + + @Resource + private ISysDeployFormService sysDeployFormService; + + @Resource + private ISysUserService sysUserService; + + @Resource + private ISysDeptService sysDeptService; + + @Resource + private FlowDeployMapper flowDeployMapper; + + private static final String BPMN_FILE_SUFFIX = ".bpmn"; + + @Override + public boolean exist(String processDefinitionKey) { + ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery() + .processDefinitionKey(processDefinitionKey); + long count = processDefinitionQuery.count(); + return count > 0 ? true : false; + } + + /** + * 流程定义列表 + * + * @param pageNum 当前页码 + * @param pageSize 每页条数 + * @return 流程定义分页列表数据 + */ + @Override + public List list(String name, Integer pageNum, Integer pageSize) { + // Page page = new Page(); + // // 流程定义列表数据查询 + // final ProcessDefinitionQuery processDefinitionQuery = + // repositoryService.createProcessDefinitionQuery(); + // if (StringUtils.isNotEmpty(name)) { + // processDefinitionQuery.processDefinitionNameLike(name); + // } + //// processDefinitionQuery.orderByProcessDefinitionKey().asc(); + // page.setTotal(processDefinitionQuery.count()); + // List processDefinitionList = + // processDefinitionQuery.listPage(pageSize * (pageNum - 1), pageSize); + // + // List dataList = new ArrayList<>(); + // for (ProcessDefinition processDefinition : processDefinitionList) { + // String deploymentId = processDefinition.getDeploymentId(); + // Deployment deployment = + // repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult(); + // FlowProcDefDto reProcDef = new FlowProcDefDto(); + // BeanUtils.copyProperties(processDefinition, reProcDef); + // SysForm sysForm = + // sysDeployFormService.selectSysDeployFormByDeployId(deploymentId); + // if (Objects.nonNull(sysForm)) { + // reProcDef.setFormName(sysForm.getFormName()); + // reProcDef.setFormId(sysForm.getFormId()); + // } + // // 流程定义时间 + // reProcDef.setDeploymentTime(deployment.getDeploymentTime()); + // dataList.add(reProcDef); + // } + PageUtils.startPage(pageNum, pageSize); + final List dataList = flowDeployMapper.selectDeployList(name); + // 加载挂表单 + for (FlowProcDefDto procDef : dataList) { + FormTemplate sysForm = sysDeployFormService.selectSysDeployFormByDeployId(procDef.getDeploymentId()); + if (Objects.nonNull(sysForm)) { + procDef.setFormName(sysForm.getFormName()); + procDef.setFormId(sysForm.getFormId()); + } + } + return dataList; + } + + /** + * 导入流程文件 + * + * 当每个key的流程第一次部署时,指定版本为1。对其后所有使用相同key的流程定义, + * 部署时版本会在该key当前已部署的最高版本号基础上加1。key参数用于区分流程定义 + * + * @param name + * @param category + * @param in + */ + @Override + public void importFile(String name, String category, InputStream in) { + Deployment deploy = repositoryService.createDeployment().addInputStream(name + BPMN_FILE_SUFFIX, in).name(name) + .category(category).deploy(); + ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()) + .singleResult(); + repositoryService.setProcessDefinitionCategory(definition.getId(), category); + + } + + /** + * 读取xml + * + * @param deployId + * @return + */ + @Override + public AjaxResult readXml(String deployId) throws IOException { + ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId) + .singleResult(); + InputStream inputStream = repositoryService.getResourceAsStream(definition.getDeploymentId(), + definition.getResourceName()); + String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name()); + return AjaxResult.success("", result); + } + + /** + * 读取xml + * + * @param deployId + * @return + */ + @Override + public InputStream readImage(String deployId) { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId) + .singleResult(); + // 获得图片流 + DefaultProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); + // 输出为图片 + return diagramGenerator.generateDiagram( + bpmnModel, + "png", + Collections.emptyList(), + Collections.emptyList(), + "宋体", + "宋体", + "宋体", + null, + 1.0, + false); + + } + + /** + * 根据流程定义ID启动流程实例 + * + * @param procDefId 流程模板ID + * @param variables 流程变量 + * @return + */ + @Override + public AjaxResult startProcessInstanceById(String procDefId, Map variables) { + try { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(procDefId) + .latestVersion().singleResult(); + if (Objects.nonNull(processDefinition) && processDefinition.isSuspended()) { + return AjaxResult.error("流程已被挂起,请先激活流程"); + } + // 设置流程发起人Id到流程中 + SysUser sysUser = SecurityUtils.getLoginUser().getUser(); + identityService.setAuthenticatedUserId(sysUser.getUserId().toString()); + variables.put(ProcessConstants.PROCESS_INITIATOR, sysUser.getUserId()); + + // 流程发起时 跳过发起人节点 + ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDefId, variables); + // 给第一步申请人节点设置任务执行人和意见 + Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()) + .singleResult(); + if (Objects.nonNull(task)) { + taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), + FlowComment.NORMAL.getType(), sysUser.getNickName() + "发起流程申请"); + taskService.complete(task.getId(), variables); + } + return AjaxResult.success("流程启动成功"); + } catch (FlowableIllegalArgumentException e) { + e.printStackTrace(); + if (e.getMessage().contains("Field definition uses non-existent field")) { + return AjaxResult.error("流程启动错误:流程定义中使用了不存在的字段,请检查流程定义"); + } else { + return AjaxResult.error("流程启动错误,请检查字段是否正确"); + } + } catch (FlowableException e) { + e.printStackTrace(); + if (e.getMessage().contains("Unknown property used in expression")) { + return AjaxResult.error("流程启动错误:表达式中使用了未知属性"); + } else if (e.getMessage().contains("Error while evaluating expression")) { + return AjaxResult.error("流程启动错误:表达式计算错误,请检查流程定义中的表达式"); + } else { + return AjaxResult.error("流程启动错误,请检查流程定义是否正确"); + } + } catch (Exception e) { + e.printStackTrace(); + return AjaxResult.error("流程启动错误"); + } + } + + /** + * 激活或挂起流程定义 + * + * @param state 状态 + * @param deployId 流程部署ID + */ + @Override + public void updateState(Integer state, String deployId) { + ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().deploymentId(deployId) + .singleResult(); + // 激活 + if (state == 1) { + repositoryService.activateProcessDefinitionById(procDef.getId(), true, null); + } + // 挂起 + if (state == 2) { + repositoryService.suspendProcessDefinitionById(procDef.getId(), true, null); + } + } + + /** + * 删除流程定义 + * + * @param deployId 流程部署ID act_ge_bytearray 表中 deployment_id值 + */ + @Override + public void delete(String deployId) { + // true 允许级联删除 ,不设置会导致数据库外键关联异常 + repositoryService.deleteDeployment(deployId, true); + } + +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowInstanceServiceImpl.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowInstanceServiceImpl.java new file mode 100644 index 0000000..a62c7b7 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowInstanceServiceImpl.java @@ -0,0 +1,140 @@ +package com.ruoyi.flowable.service.impl; + +import java.util.Map; +import java.util.Objects; + +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.task.api.Task; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.flowable.domain.vo.FlowTaskVo; +import com.ruoyi.flowable.factory.FlowServiceFactory; +import com.ruoyi.flowable.service.IFlowInstanceService; + +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * 工作流流程实例管理 + *

+ * + * @author Tony + * @date 2021-04-03 + */ +@Service +@Slf4j +public class FlowInstanceServiceImpl extends FlowServiceFactory implements IFlowInstanceService { + + /** + * 结束流程实例 + * + * @param vo + */ + @Override + public void stopProcessInstance(FlowTaskVo vo) { + String taskId = vo.getTaskId(); + + if (!StringUtils.hasText(taskId)) { + throw new IllegalArgumentException("任务ID不能为空。"); + } + Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); + if (task == null) { + throw new RuntimeException("未找到ID为 " + taskId + " 的任务,无法停止流程实例。可能任务已完成或不存在。"); + } + String processInstanceId = task.getProcessInstanceId(); + if (!StringUtils.hasText(processInstanceId)) { + throw new RuntimeException("任务 " + taskId + " 没有关联的流程实例ID。"); + } + String deleteReason = vo.getComment(); // 假设 FlowTaskVo 有 getComment() 方法获取原因 + if (!StringUtils.hasText(deleteReason)) { + deleteReason = "流程实例由用户通过任务ID " + taskId + " 手动停止。"; // 提供一个默认原因 + } + runtimeService.deleteProcessInstance(processInstanceId, deleteReason); + } + + /** + * 激活或挂起流程实例 + * + * @param state 状态 + * @param instanceId 流程实例ID + */ + @Override + public void updateState(Integer state, String instanceId) { + + // 激活 + if (state == 1) { + runtimeService.activateProcessInstanceById(instanceId); + } + // 挂起 + if (state == 2) { + runtimeService.suspendProcessInstanceById(instanceId); + } + } + + /** + * 删除流程实例ID + * + * @param instanceId 流程实例ID + * @param deleteReason 删除原因 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(String instanceId, String deleteReason) { + + // 查询历史数据 + HistoricProcessInstance historicProcessInstance = getHistoricProcessInstanceById(instanceId); + if (historicProcessInstance.getEndTime() != null) { + historyService.deleteHistoricProcessInstance(historicProcessInstance.getId()); + return; + } + // 删除流程实例 + runtimeService.deleteProcessInstance(instanceId, deleteReason); + // 删除历史流程实例 + historyService.deleteHistoricProcessInstance(instanceId); + } + + /** + * 根据实例ID查询历史实例数据 + * + * @param processInstanceId + * @return + */ + @Override + public HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId) { + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId).singleResult(); + if (Objects.isNull(historicProcessInstance)) { + throw new FlowableObjectNotFoundException("流程实例不存在: " + processInstanceId); + } + return historicProcessInstance; + } + + /** + * 根据流程定义ID启动流程实例 + * + * @param procDefId 流程定义Id + * @param variables 流程变量 + * @return + */ + @Override + public AjaxResult startProcessInstanceById(String procDefId, Map variables) { + + try { + // 设置流程发起人Id到流程中 + Long userId = SecurityUtils.getLoginUser().getUser().getUserId(); + // identityService.setAuthenticatedUserId(userId.toString()); + variables.put("initiator", userId); + variables.put("_FLOWABLE_SKIP_EXPRESSION_ENABLED", true); + runtimeService.startProcessInstanceById(procDefId, variables); + return AjaxResult.success("流程启动成功"); + } catch (Exception e) { + e.printStackTrace(); + return AjaxResult.error("流程启动错误"); + } + } +} \ No newline at end of file diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowTaskServiceImpl.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowTaskServiceImpl.java new file mode 100644 index 0000000..bb2a737 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowTaskServiceImpl.java @@ -0,0 +1,1321 @@ +package com.ruoyi.flowable.service.impl; + +import java.io.InputStream; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.EndEvent; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.MultiInstanceLoopCharacteristics; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.UserTask; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.common.engine.impl.identity.Authentication; +import org.flowable.engine.ProcessEngineConfiguration; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.history.HistoricProcessInstanceQuery; +import org.flowable.engine.impl.cmd.AddMultiInstanceExecutionCmd; +import org.flowable.engine.impl.cmd.DeleteMultiInstanceExecutionCmd; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.Execution; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.engine.task.Comment; +import org.flowable.identitylink.api.history.HistoricIdentityLink; +import org.flowable.image.ProcessDiagramGenerator; +import org.flowable.task.api.DelegationState; +import org.flowable.task.api.Task; +import org.flowable.task.api.TaskQuery; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.task.api.history.HistoricTaskInstanceQuery; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.TypeReference; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.flowable.common.constant.ProcessConstants; +import com.ruoyi.flowable.common.enums.FlowComment; +import com.ruoyi.flowable.domain.dto.FlowCommentDto; +import com.ruoyi.flowable.domain.dto.FlowNextDto; +import com.ruoyi.flowable.domain.dto.FlowTaskDto; +import com.ruoyi.flowable.domain.dto.FlowViewerDto; +import com.ruoyi.flowable.domain.vo.FlowQueryVo; +import com.ruoyi.flowable.domain.vo.FlowTaskVo; +import com.ruoyi.flowable.factory.FlowServiceFactory; +import com.ruoyi.flowable.flow.CustomProcessDiagramGenerator; +import com.ruoyi.flowable.flow.FindNextNodeUtil; +import com.ruoyi.flowable.flow.FlowableUtils; +import com.ruoyi.flowable.service.IFlowTaskService; +import com.ruoyi.flowable.service.ISysDeployFormService; +import com.ruoyi.form.domain.FormTemplate; +import com.ruoyi.form.service.IFormTemplateService; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; + +/** + * @author Tony + * @date 2021-04-03 + **/ +@Service +@Slf4j +public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTaskService { + + @Resource + private ISysUserService sysUserService; + @Resource + private ISysRoleService sysRoleService; + @Resource + private ISysDeployFormService sysInstanceFormService; + // @Resource + // private ISysFormService sysFormService; + @Resource + private IFormTemplateService formTemplateService; + + /** + * 完成任务 + * + * @param taskVo 请求实体参数 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public AjaxResult complete(FlowTaskVo taskVo) { + Task task = taskService.createTaskQuery().taskId(taskVo.getTaskId()).singleResult(); + if (Objects.isNull(task)) { + return AjaxResult.error("任务不存在"); + } + if (DelegationState.PENDING.equals(task.getDelegationState())) { + taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.DELEGATE.getType(), + taskVo.getComment()); + taskService.resolveTask(taskVo.getTaskId(), taskVo.getVariables()); + } else { + taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.NORMAL.getType(), + taskVo.getComment()); + Long userId = SecurityUtils.getLoginUser().getUser().getUserId(); + taskService.setAssignee(taskVo.getTaskId(), userId.toString()); + taskService.complete(taskVo.getTaskId(), taskVo.getVariables()); + } + return AjaxResult.success(); + } + + /** + * 驳回任务 + * + * @param flowTaskVo + */ + @Override + public void taskReject(FlowTaskVo flowTaskVo) { + if (taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult().isSuspended()) { + throw new ServiceException("任务处于挂起状态!"); + } + // 当前任务 task + Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult(); + // 获取流程定义信息 + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()).singleResult(); + // 获取所有节点信息 + Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0); + // 获取全部节点列表,包含子节点 + Collection allElements = FlowableUtils.getAllElements(process.getFlowElements(), null); + // 获取当前任务节点元素 + FlowElement source = null; + if (allElements != null) { + for (FlowElement flowElement : allElements) { + // 类型为用户节点 + if (flowElement.getId().equals(task.getTaskDefinitionKey())) { + // 获取节点信息 + source = flowElement; + } + } + } + + // 目的获取所有跳转到的节点 targetIds + // 获取当前节点的所有父级用户任务节点 + // 深度优先算法思想:延边迭代深入 + List parentUserTaskList = FlowableUtils.iteratorFindParentUserTasks(source, null, null); + if (parentUserTaskList == null || parentUserTaskList.size() == 0) { + throw new ServiceException("当前节点为初始任务节点,不能驳回"); + } + // 获取活动 ID 即节点 Key + List parentUserTaskKeyList = new ArrayList<>(); + parentUserTaskList.forEach(item -> parentUserTaskKeyList.add(item.getId())); + // 获取全部历史节点活动实例,即已经走过的节点历史,数据采用开始时间升序 + List historicTaskInstanceList = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(task.getProcessInstanceId()).orderByHistoricTaskInstanceStartTime().asc().list(); + // 数据清洗,将回滚导致的脏数据清洗掉 + List lastHistoricTaskInstanceList = FlowableUtils.historicTaskInstanceClean(allElements, + historicTaskInstanceList); + // 此时历史任务实例为倒序,获取最后走的节点 + List targetIds = new ArrayList<>(); + // 循环结束标识,遇到当前目标节点的次数 + int number = 0; + StringBuilder parentHistoricTaskKey = new StringBuilder(); + for (String historicTaskInstanceKey : lastHistoricTaskInstanceList) { + // 当会签时候会出现特殊的,连续都是同一个节点历史数据的情况,这种时候跳过 + if (parentHistoricTaskKey.toString().equals(historicTaskInstanceKey)) { + continue; + } + parentHistoricTaskKey = new StringBuilder(historicTaskInstanceKey); + if (historicTaskInstanceKey.equals(task.getTaskDefinitionKey())) { + number++; + } + // 在数据清洗后,历史节点就是唯一一条从起始到当前节点的历史记录,理论上每个点只会出现一次 + // 在流程中如果出现循环,那么每次循环中间的点也只会出现一次,再出现就是下次循环 + // number == 1,第一次遇到当前节点 + // number == 2,第二次遇到,代表最后一次的循环范围 + if (number == 2) { + break; + } + // 如果当前历史节点,属于父级的节点,说明最后一次经过了这个点,需要退回这个点 + if (parentUserTaskKeyList.contains(historicTaskInstanceKey)) { + targetIds.add(historicTaskInstanceKey); + } + } + + // 目的获取所有需要被跳转的节点 currentIds + // 取其中一个父级任务,因为后续要么存在公共网关,要么就是串行公共线路 + UserTask oneUserTask = parentUserTaskList.get(0); + // 获取所有正常进行的任务节点 Key,这些任务不能直接使用,需要找出其中需要撤回的任务 + List runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list(); + List runTaskKeyList = new ArrayList<>(); + runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey())); + // 需驳回任务列表 + List currentIds = new ArrayList<>(); + // 通过父级网关的出口连线,结合 runTaskList 比对,获取需要撤回的任务 + List currentUserTaskList = FlowableUtils.iteratorFindChildUserTasks(oneUserTask, runTaskKeyList, null, + null); + currentUserTaskList.forEach(item -> currentIds.add(item.getId())); + + // 规定:并行网关之前节点必须需存在唯一用户任务节点,如果出现多个任务节点,则并行网关节点默认为结束节点,原因为不考虑多对多情况 + if (targetIds.size() > 1 && currentIds.size() > 1) { + throw new ServiceException("任务出现多对多情况,无法撤回"); + } + + // 循环获取那些需要被撤回的节点的ID,用来设置驳回原因 + List currentTaskIds = new ArrayList<>(); + currentIds.forEach(currentId -> runTaskList.forEach(runTask -> { + if (currentId.equals(runTask.getTaskDefinitionKey())) { + currentTaskIds.add(runTask.getId()); + } + })); + // 设置驳回意见 + currentTaskIds.forEach(item -> taskService.addComment(item, task.getProcessInstanceId(), + FlowComment.REJECT.getType(), flowTaskVo.getComment())); + + try { + // 如果父级任务多于 1 个,说明当前节点不是并行节点,原因为不考虑多对多情况 + if (targetIds.size() > 1) { + // 1 对 多任务跳转,currentIds 当前节点(1),targetIds 跳转到的节点(多) + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(task.getProcessInstanceId()) + .moveSingleActivityIdToActivityIds(currentIds.get(0), targetIds).changeState(); + } + // 如果父级任务只有一个,因此当前任务可能为网关中的任务 + if (targetIds.size() == 1) { + // 1 对 1 或 多 对 1 情况,currentIds 当前要跳转的节点列表(1或多),targetIds.get(0) 跳转到的节点(1) + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(task.getProcessInstanceId()) + .moveActivityIdsToSingleActivityId(currentIds, targetIds.get(0)).changeState(); + } + } catch (FlowableObjectNotFoundException e) { + throw new ServiceException("未找到流程实例,流程可能已发生变化"); + } catch (FlowableException e) { + throw new ServiceException("无法取消或开始活动"); + } + + } + + /** + * 退回任务 + * + * @param flowTaskVo 请求实体参数 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void taskReturn(FlowTaskVo flowTaskVo) { + if (taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult().isSuspended()) { + throw new ServiceException("任务处于挂起状态"); + } + // 当前任务 task + Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult(); + // 获取流程定义信息 + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()).singleResult(); + // 获取所有节点信息 + Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0); + // 获取全部节点列表,包含子节点 + Collection allElements = FlowableUtils.getAllElements(process.getFlowElements(), null); + // 获取当前任务节点元素 + FlowElement source = null; + // 获取跳转的节点元素 + FlowElement target = null; + if (allElements != null) { + for (FlowElement flowElement : allElements) { + // 当前任务节点元素 + if (flowElement.getId().equals(task.getTaskDefinitionKey())) { + source = flowElement; + } + // 跳转的节点元素 + if (flowElement.getId().equals(flowTaskVo.getTargetKey())) { + target = flowElement; + } + } + } + + // 从当前节点向前扫描 + // 如果存在路线上不存在目标节点,说明目标节点是在网关上或非同一路线上,不可跳转 + // 否则目标节点相对于当前节点,属于串行 + Boolean isSequential = FlowableUtils.iteratorCheckSequentialReferTarget(source, flowTaskVo.getTargetKey(), null, + null); + if (!isSequential) { + throw new ServiceException("当前节点相对于目标节点,不属于串行关系,无法回退"); + } + + // 获取所有正常进行的任务节点 Key,这些任务不能直接使用,需要找出其中需要撤回的任务 + List runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list(); + List runTaskKeyList = new ArrayList<>(); + runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey())); + // 需退回任务列表 + List currentIds = new ArrayList<>(); + // 通过父级网关的出口连线,结合 runTaskList 比对,获取需要撤回的任务 + List currentUserTaskList = FlowableUtils.iteratorFindChildUserTasks(target, runTaskKeyList, null, + null); + currentUserTaskList.forEach(item -> currentIds.add(item.getId())); + + // 循环获取那些需要被撤回的节点的ID,用来设置驳回原因 + List currentTaskIds = new ArrayList<>(); + currentIds.forEach(currentId -> runTaskList.forEach(runTask -> { + if (currentId.equals(runTask.getTaskDefinitionKey())) { + currentTaskIds.add(runTask.getId()); + } + })); + // 设置回退意见 + currentTaskIds.forEach(currentTaskId -> taskService.addComment(currentTaskId, task.getProcessInstanceId(), + FlowComment.REBACK.getType(), flowTaskVo.getComment())); + + try { + // 1 对 1 或 多 对 1 情况,currentIds 当前要跳转的节点列表(1或多),targetKey 跳转到的节点(1) + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(task.getProcessInstanceId()) + .moveActivityIdsToSingleActivityId(currentIds, flowTaskVo.getTargetKey()).changeState(); + } catch (FlowableObjectNotFoundException e) { + throw new ServiceException("未找到流程实例,流程可能已发生变化"); + } catch (FlowableException e) { + throw new ServiceException("无法取消或开始活动"); + } + } + + /** + * 获取所有可回退的节点 + * + * @param flowTaskVo + * @return + */ + @Override + public AjaxResult findReturnTaskList(FlowTaskVo flowTaskVo) { + // 当前任务 task + Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult(); + // 获取流程定义信息 + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()).singleResult(); + // 获取所有节点信息,暂不考虑子流程情况 + Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0); + Collection flowElements = process.getFlowElements(); + // 获取当前任务节点元素 + UserTask source = null; + if (flowElements != null) { + for (FlowElement flowElement : flowElements) { + // 类型为用户节点 + if (flowElement.getId().equals(task.getTaskDefinitionKey())) { + source = (UserTask) flowElement; + } + } + } + // 获取节点的所有路线 + List> roads = FlowableUtils.findRoad(source, null, null, null); + // 可回退的节点列表 + List userTaskList = new ArrayList<>(); + for (List road : roads) { + if (userTaskList.size() == 0) { + // 还没有可回退节点直接添加 + userTaskList = road; + } else { + // 如果已有回退节点,则比对取交集部分 + userTaskList.retainAll(road); + } + } + return AjaxResult.success(userTaskList); + } + + /** + * 删除任务 + * + * @param flowTaskVo 请求实体参数 + */ + @Override + public void deleteTask(FlowTaskVo flowTaskVo) { + // todo 待确认删除任务是物理删除任务 还是逻辑删除,让这个任务直接通过? + taskService.deleteTask(flowTaskVo.getTaskId(), flowTaskVo.getComment()); + } + + /** + * 认领/签收任务 + * 认领以后,这个用户就会成为任务的执行人,任务会从其他成员的任务列表中消失 + * + * @param flowTaskVo 请求实体参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void claim(FlowTaskVo flowTaskVo) { + taskService.claim(flowTaskVo.getTaskId(), flowTaskVo.getUserId()); + } + + /** + * 取消认领/签收任务 + * + * @param flowTaskVo 请求实体参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void unClaim(FlowTaskVo flowTaskVo) { + taskService.unclaim(flowTaskVo.getTaskId()); + } + + /** + * 委派任务 + * 任务委派只是委派人将当前的任务交给被委派人进行审批,处理任务后又重新回到委派人身上。 + * + * @param flowTaskVo 请求实体参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void delegateTask(FlowTaskVo flowTaskVo) { + taskService.delegateTask(flowTaskVo.getTaskId(), flowTaskVo.getAssignee()); + } + + /** + * 任务归还 + * 被委派人完成任务之后,将任务归还委派人 + * + * @param flowTaskVo 请求实体参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void resolveTask(FlowTaskVo flowTaskVo) { + taskService.resolveTask(flowTaskVo.getTaskId()); + } + + /** + * 转办任务 + * 直接将办理人换成别人,这时任务的拥有者不再是转办人 + * + * @param flowTaskVo 请求实体参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void assignTask(FlowTaskVo flowTaskVo) { + // 直接转派就可以覆盖掉之前的 + taskService.setAssignee(flowTaskVo.getTaskId(), flowTaskVo.getAssignee()); + // // 删除指派人重新指派 + // taskService.deleteCandidateUser(flowTaskVo.getTaskId(),flowTaskVo.getAssignee()); + // taskService.addCandidateUser(flowTaskVo.getTaskId(),flowTaskVo.getAssignee()); + // // 如果要查询转给他人处理的任务,可以同时将OWNER进行设置: + // taskService.setOwner(flowTaskVo.getTaskId(), flowTaskVo.getAssignee()); + + } + + /** + * 多实例加签 + * act_ru_task、act_ru_identitylink各生成一条记录 + * + * @param flowTaskVo + */ + @Override + public void addMultiInstanceExecution(FlowTaskVo flowTaskVo) { + managementService.executeCommand(new AddMultiInstanceExecutionCmd(flowTaskVo.getDefId(), + flowTaskVo.getInstanceId(), flowTaskVo.getVariables())); + } + + /** + * 多实例减签 + * act_ru_task减1、act_ru_identitylink不变 + * + * @param flowTaskVo + */ + @Override + public void deleteMultiInstanceExecution(FlowTaskVo flowTaskVo) { + managementService.executeCommand( + new DeleteMultiInstanceExecutionCmd(flowTaskVo.getCurrentChildExecutionId(), flowTaskVo.getFlag())); + } + + /** + * 我发起的流程 + * + * @param queryVo 请求参数 + * @return + */ + @Override + public List myProcess(FlowQueryVo queryVo) { + // List page = new Page<>(); + Long userId = SecurityUtils.getLoginUser().getUser().getUserId(); + HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery() + .startedBy(userId.toString()) + .orderByProcessInstanceStartTime() + .desc(); + List historicProcessInstances = historicProcessInstanceQuery + .listPage(queryVo.getPageSize() * (queryVo.getPageNum() - 1), queryVo.getPageSize()); + List flowList = new ArrayList<>(); + for (HistoricProcessInstance hisIns : historicProcessInstances) { + FlowTaskDto flowTask = new FlowTaskDto(); + flowTask.setCreateTime(hisIns.getStartTime()); + flowTask.setFinishTime(hisIns.getEndTime()); + flowTask.setProcInsId(hisIns.getId()); + + // 计算耗时 + if (Objects.nonNull(hisIns.getEndTime())) { + long time = hisIns.getEndTime().getTime() - hisIns.getStartTime().getTime(); + flowTask.setDuration(getDate(time)); + } else { + long time = System.currentTimeMillis() - hisIns.getStartTime().getTime(); + flowTask.setDuration(getDate(time)); + } + // 流程定义信息 + ProcessDefinition pd = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(hisIns.getProcessDefinitionId()) + .singleResult(); + flowTask.setDeployId(pd.getDeploymentId()); + flowTask.setProcDefName(pd.getName()); + flowTask.setProcDefVersion(pd.getVersion()); + flowTask.setCategory(pd.getCategory()); + flowTask.setProcDefVersion(pd.getVersion()); + // 当前所处流程 + List taskList = taskService.createTaskQuery().processInstanceId(hisIns.getId()).list(); + try { + if (CollectionUtils.isNotEmpty(taskList)) { + flowTask.setTaskId(taskList.get(0).getId()); + flowTask.setTaskName(taskList.get(0).getName()); + if (StringUtils.isNotBlank(taskList.get(0).getAssignee())) { + // 当前任务节点办理人信息 + SysUser sysUser = sysUserService.selectUserById(Long.parseLong(taskList.get(0).getAssignee())); + if (Objects.nonNull(sysUser)) { + flowTask.setAssigneeId(sysUser.getUserId()); + flowTask.setAssigneeName(sysUser.getNickName()); + flowTask.setAssigneeDeptName( + Objects.nonNull(sysUser.getDept()) ? sysUser.getDept().getDeptName() : ""); + } + } + } else { + List historicTaskInstance = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(hisIns.getId()).orderByHistoricTaskInstanceEndTime().desc().list(); + flowTask.setTaskId(historicTaskInstance.get(0).getId()); + flowTask.setTaskName(historicTaskInstance.get(0).getName()); + if (StringUtils.isNotBlank(historicTaskInstance.get(0).getAssignee())) { + // 当前任务节点办理人信息 + SysUser sysUser = sysUserService + .selectUserById(Long.parseLong(historicTaskInstance.get(0).getAssignee())); + if (Objects.nonNull(sysUser)) { + flowTask.setAssigneeId(sysUser.getUserId()); + flowTask.setAssigneeName(sysUser.getNickName()); + flowTask.setAssigneeDeptName( + Objects.nonNull(sysUser.getDept()) ? sysUser.getDept().getDeptName() : ""); + } + } + } + } catch (Exception e) { + log.error("获取流程任务信息异常: {}", e.getMessage()); + flowTask.setAssigneeDeptName("ERROR"); + flowTask.setAssigneeName("ERROR"); + } + flowList.add(flowTask); + } + + return flowList; + } + + /** + * 取消申请 + * 目前实现方式: 直接将当前流程变更为已完成 + * + * @param flowTaskVo + * @return + */ + @Override + public AjaxResult stopProcess(FlowTaskVo flowTaskVo) { + List task = taskService.createTaskQuery().processInstanceId(flowTaskVo.getInstanceId()).list(); + if (CollectionUtils.isEmpty(task)) { + throw new ServiceException("流程未启动或已执行完成,取消申请失败"); + } + // 获取当前流程实例 + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(flowTaskVo.getInstanceId()) + .singleResult(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); + if (Objects.nonNull(bpmnModel)) { + Process process = bpmnModel.getMainProcess(); + List endNodes = process.findFlowElementsOfType(EndEvent.class, false); + if (CollectionUtils.isNotEmpty(endNodes)) { + SysUser loginUser = SecurityUtils.getLoginUser().getUser(); + Authentication.setAuthenticatedUserId(loginUser.getUserId().toString()); + taskService.addComment(null, processInstance.getProcessInstanceId(), + FlowComment.STOP.getType(), + StringUtils.isBlank(flowTaskVo.getComment()) ? "取消申请" : flowTaskVo.getComment()); + // 获取当前流程最后一个节点 + String endId = endNodes.get(0).getId(); + List executions = runtimeService.createExecutionQuery() + .parentId(processInstance.getProcessInstanceId()).list(); + List executionIds = new ArrayList<>(); + executions.forEach(execution -> executionIds.add(execution.getId())); + // 变更流程为已结束状态 + runtimeService.createChangeActivityStateBuilder() + .moveExecutionsToSingleActivityId(executionIds, endId).changeState(); + } + } + + return AjaxResult.success(); + } + + /** + * 撤回流程 目前存在错误 + * + * @param flowTaskVo + * @return + */ + @Override + public AjaxResult revokeProcess(FlowTaskVo flowTaskVo) { + Task task = taskService.createTaskQuery() + .processInstanceId(flowTaskVo.getInstanceId()) + .singleResult(); + if (task == null) { + throw new ServiceException("流程未启动或已执行完成,无法撤回"); + } + + SysUser loginUser = SecurityUtils.getLoginUser().getUser(); + List htiList = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(task.getProcessInstanceId()) + .orderByTaskCreateTime() + .asc() + .list(); + String myTaskId = null; + for (HistoricTaskInstance hti : htiList) { + if (loginUser.getUserId().toString().equals(hti.getAssignee())) { + myTaskId = hti.getId(); + break; + } + } + if (null == myTaskId) { + throw new ServiceException("该任务非当前用户提交,无法撤回"); + } + List historicTaskInstanceList = historyService + .createHistoricTaskInstanceQuery() + .processInstanceId(task.getProcessInstanceId()) + .orderByHistoricTaskInstanceStartTime() + .asc() + .list(); + Iterator it = historicTaskInstanceList.iterator(); + // 循环节点,获取当前节点的上一节点的key + String tarKey = ""; + while (it.hasNext()) { + HistoricTaskInstance his = it.next(); + if (!task.getTaskDefinitionKey().equals(his.getTaskDefinitionKey())) { + tarKey = his.getTaskDefinitionKey(); + } + } + // 跳转节点 + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(flowTaskVo.getInstanceId()) + .moveActivityIdTo(task.getTaskDefinitionKey(), tarKey) + .changeState(); + + return AjaxResult.success(); + } + + /** + * 代办任务列表 + * + * @param queryVo 请求参数 + * @return + */ + @Override + public List todoList(FlowQueryVo queryVo) { + SysUser sysUser = SecurityUtils.getLoginUser().getUser(); + TaskQuery taskQuery = taskService.createTaskQuery() + .active() + .includeProcessVariables() + .taskCandidateGroupIn(sysUser.getRoles().stream().map(role -> role.getRoleId().toString()) + .collect(Collectors.toList())) + .taskCandidateOrAssigned(sysUser.getUserId().toString()) + .orderByTaskCreateTime().desc(); + + if (StringUtils.isNotBlank(queryVo.getName())) { + String likePattern = "%" + queryVo.getName() + "%"; + taskQuery.processDefinitionNameLike(likePattern); + } + List taskList = taskQuery.listPage(queryVo.getPageSize() * (queryVo.getPageNum() - 1), + queryVo.getPageSize()); + List flowList = new ArrayList<>(); + for (Task task : taskList) { + FlowTaskDto flowTask = new FlowTaskDto(); + // 当前流程信息 + flowTask.setTaskId(task.getId()); + flowTask.setTaskDefKey(task.getTaskDefinitionKey()); + flowTask.setCreateTime(task.getCreateTime()); + flowTask.setProcDefId(task.getProcessDefinitionId()); + flowTask.setExecutionId(task.getExecutionId()); + flowTask.setTaskName(task.getName()); + // 流程定义信息 + ProcessDefinition pd = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()) + .singleResult(); + flowTask.setDeployId(pd.getDeploymentId()); + flowTask.setProcDefName(pd.getName()); + flowTask.setProcDefVersion(pd.getVersion()); + flowTask.setProcInsId(task.getProcessInstanceId()); + + // 流程发起人信息 + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(task.getProcessInstanceId()) + .singleResult(); + SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId())); + flowTask.setStartUserId(startUser.getUserId().toString()); + flowTask.setStartUserName(startUser.getNickName()); + flowTask.setStartDeptName(Objects.nonNull(startUser.getDept()) ? startUser.getDept().getDeptName() : ""); + flowList.add(flowTask); + } + return flowList; + } + + /** + * 已办任务列表 + * + * @param queryVo 请求参数 + * @return + */ + @Override + public List finishedList(FlowQueryVo queryVo) { + // List page = new ArrayList<>(); + Long userId = SecurityUtils.getLoginUser().getUser().getUserId(); + HistoricTaskInstanceQuery taskInstanceQuery = historyService.createHistoricTaskInstanceQuery() + .includeProcessVariables() + .finished() + .taskAssignee(userId.toString()) + .orderByHistoricTaskInstanceEndTime() + .desc(); + List historicTaskInstanceList = taskInstanceQuery + .listPage(queryVo.getPageSize() * (queryVo.getPageNum() - 1), queryVo.getPageSize()); + List hisTaskList = new ArrayList<>(); + for (HistoricTaskInstance histTask : historicTaskInstanceList) { + FlowTaskDto flowTask = new FlowTaskDto(); + // 当前流程信息 + flowTask.setTaskId(histTask.getId()); + // 审批人员信息 + flowTask.setCreateTime(histTask.getCreateTime()); + flowTask.setFinishTime(histTask.getEndTime()); + flowTask.setDuration(getDate(histTask.getDurationInMillis())); + flowTask.setProcDefId(histTask.getProcessDefinitionId()); + flowTask.setTaskDefKey(histTask.getTaskDefinitionKey()); + flowTask.setTaskName(histTask.getName()); + + // 流程定义信息 + ProcessDefinition pd = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(histTask.getProcessDefinitionId()) + .singleResult(); + flowTask.setDeployId(pd.getDeploymentId()); + flowTask.setProcDefName(pd.getName()); + flowTask.setProcDefVersion(pd.getVersion()); + flowTask.setProcInsId(histTask.getProcessInstanceId()); + flowTask.setHisProcInsId(histTask.getProcessInstanceId()); + + // 流程发起人信息 + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(histTask.getProcessInstanceId()) + .singleResult(); + SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId())); + flowTask.setStartUserId(startUser.getNickName()); + flowTask.setStartUserName(startUser.getNickName()); + flowTask.setStartDeptName(Objects.nonNull(startUser.getDept()) ? startUser.getDept().getDeptName() : ""); + hisTaskList.add(flowTask); + } + return hisTaskList; + } + + // private static Predicate distinctByKey(Function + // keyExtractor) { + // Set seen = ConcurrentHashMap.newKeySet(); + // return t -> seen.add(keyExtractor.apply(t)); + // } + + /** + * 流程历史流转记录 + * + * @param procInsId 流程实例Id + * @return + */ + @Override + public AjaxResult flowRecord(String procInsId, String deployId) { + Map map = new HashMap(); + if (StringUtils.isNotBlank(procInsId)) { + List list = historyService + .createHistoricActivityInstanceQuery() + .processInstanceId(procInsId) + .orderByHistoricActivityInstanceStartTime() + .desc().list(); + List hisFlowList = new ArrayList<>(); + for (HistoricActivityInstance histIns : list) { + // 展示开始节点 + // if ("startEvent".equals(histIns.getActivityType())) { + // FlowTaskDto flowTask = new FlowTaskDto(); + // // 流程发起人信息 + // HistoricProcessInstance historicProcessInstance = + // historyService.createHistoricProcessInstanceQuery() + // .processInstanceId(histIns.getProcessInstanceId()) + // .singleResult(); + // SysUser startUser = + // sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId())); + // flowTask.setTaskName(startUser.getNickName() + "(" + + // startUser.getDept().getDeptName() + ")发起申请"); + // flowTask.setFinishTime(histIns.getEndTime()); + // hisFlowList.add(flowTask); + // } else if ("endEvent".equals(histIns.getActivityType())) { + // FlowTaskDto flowTask = new FlowTaskDto(); + // flowTask.setTaskName(StringUtils.isNotBlank(histIns.getActivityName()) ? + // histIns.getActivityName() : "结束"); + // flowTask.setFinishTime(histIns.getEndTime()); + // hisFlowList.add(flowTask); + // } else + if (StringUtils.isNotBlank(histIns.getTaskId())) { + FlowTaskDto flowTask = new FlowTaskDto(); + flowTask.setTaskId(histIns.getTaskId()); + flowTask.setTaskName(histIns.getActivityName()); + flowTask.setCreateTime(histIns.getStartTime()); + flowTask.setFinishTime(histIns.getEndTime()); + if (StringUtils.isNotBlank(histIns.getAssignee())) { + SysUser sysUser = sysUserService.selectUserById(Long.parseLong(histIns.getAssignee())); + flowTask.setAssigneeId(sysUser.getUserId()); + flowTask.setAssigneeName(sysUser.getNickName()); + flowTask.setDeptName(Objects.nonNull(sysUser.getDept()) ? sysUser.getDept().getDeptName() : ""); + } + // 展示审批人员 + List linksForTask = historyService + .getHistoricIdentityLinksForTask(histIns.getTaskId()); + StringBuilder stringBuilder = new StringBuilder(); + for (HistoricIdentityLink identityLink : linksForTask) { + // 获选人,候选组/角色(多个) + if ("candidate".equals(identityLink.getType())) { + if (StringUtils.isNotBlank(identityLink.getUserId())) { + SysUser sysUser = sysUserService + .selectUserById(Long.parseLong(identityLink.getUserId())); + stringBuilder.append(sysUser.getNickName()).append(","); + } + if (StringUtils.isNotBlank(identityLink.getGroupId())) { + SysRole sysRole = sysRoleService + .selectRoleById(Long.parseLong(identityLink.getGroupId())); + stringBuilder.append(sysRole.getRoleName()).append(","); + } + } + } + if (StringUtils.isNotBlank(stringBuilder)) { + flowTask.setCandidate(stringBuilder.substring(0, stringBuilder.length() - 1)); + } + + flowTask.setDuration( + histIns.getDurationInMillis() == null || histIns.getDurationInMillis() == 0 ? null + : getDate(histIns.getDurationInMillis())); + // 获取意见评论内容 + List commentList = taskService.getProcessInstanceComments(histIns.getProcessInstanceId()); + commentList.forEach(comment -> { + if (histIns.getTaskId().equals(comment.getTaskId())) { + flowTask.setComment(FlowCommentDto.builder().type(comment.getType()) + .comment(comment.getFullMessage()).build()); + } + }); + hisFlowList.add(flowTask); + } + } + map.put("flowList", hisFlowList); + } + // 第一次申请获取初始化表单 + if (StringUtils.isNotBlank(deployId)) { + FormTemplate sysForm = sysInstanceFormService.selectSysDeployFormByDeployId(deployId); + if (Objects.isNull(sysForm)) { + return AjaxResult.error("请先配置流程表单"); + } + map.put("formData", JSONObject.parseObject(sysForm.getFormSchema())); + } + return AjaxResult.success(map); + } + + /** + * 根据任务ID查询挂载的表单信息 + * + * @param taskId 任务Id + * @return + */ + @Override + public AjaxResult getTaskForm(String taskId) { + Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); + FormTemplate sysForm = formTemplateService.selectFormTemplateByFormId(Long.parseLong(task.getFormKey())); + return AjaxResult.success(sysForm.getFormSchema()); + } + + /** + * 获取流程过程图 + * + * @param processId + * @return + */ + @Override + public InputStream diagram(String processId) { + String processDefinitionId; + // 获取当前的流程实例 + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processId) + .singleResult(); + // 如果流程已经结束,则得到结束节点 + if (Objects.isNull(processInstance)) { + HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processId).singleResult(); + + processDefinitionId = pi.getProcessDefinitionId(); + } else {// 如果流程没有结束,则取当前活动节点 + // 根据流程实例ID获得当前处于活动状态的ActivityId合集 + ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId) + .singleResult(); + processDefinitionId = pi.getProcessDefinitionId(); + } + + // 获得活动的节点 + List highLightedFlowList = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(processId).orderByHistoricActivityInstanceStartTime().asc().list(); + + List highLightedFlows = new ArrayList<>(); + List highLightedNodes = new ArrayList<>(); + // 高亮线 + for (HistoricActivityInstance tempActivity : highLightedFlowList) { + if ("sequenceFlow".equals(tempActivity.getActivityType())) { + // 高亮线 + highLightedFlows.add(tempActivity.getActivityId()); + } else { + // 高亮节点 + highLightedNodes.add(tempActivity.getActivityId()); + } + } + + // 获取流程图 + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); + ProcessEngineConfiguration configuration = processEngine.getProcessEngineConfiguration(); + // 获取自定义图片生成器 + ProcessDiagramGenerator diagramGenerator = new CustomProcessDiagramGenerator(); + InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedNodes, highLightedFlows, + configuration.getActivityFontName(), + configuration.getLabelFontName(), configuration.getAnnotationFontName(), configuration.getClassLoader(), + 1.0, true); + return in; + + } + + /** + * 获取流程执行节点 + * + * @param procInsId 流程实例id + * @return + */ + @Override + public AjaxResult getFlowViewer(String procInsId, String executionId) { + List flowViewerList = new ArrayList<>(); + FlowViewerDto flowViewerDto; + // 获取任务开始节点(临时处理方式) + List startNodeList = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(procInsId) + .orderByHistoricActivityInstanceStartTime() + .asc().listPage(0, 3); + for (HistoricActivityInstance startInstance : startNodeList) { + if (!"sequenceFlow".equals(startInstance.getActivityType())) { + flowViewerDto = new FlowViewerDto(); + if (!"sequenceFlow".equals(startInstance.getActivityType())) { + flowViewerDto.setKey(startInstance.getActivityId()); + // 根据流程节点处理时间校验改节点是否已完成 + flowViewerDto.setCompleted(!Objects.isNull(startInstance.getEndTime())); + flowViewerList.add(flowViewerDto); + } + } + } + // 历史节点 + List hisActIns = historyService.createHistoricActivityInstanceQuery() + .executionId(executionId) + .orderByHistoricActivityInstanceStartTime() + .asc().list(); + for (HistoricActivityInstance activityInstance : hisActIns) { + if (!"sequenceFlow".equals(activityInstance.getActivityType())) { + flowViewerDto = new FlowViewerDto(); + flowViewerDto.setKey(activityInstance.getActivityId()); + // 根据流程节点处理时间校验改节点是否已完成 + flowViewerDto.setCompleted(!Objects.isNull(activityInstance.getEndTime())); + flowViewerList.add(flowViewerDto); + } + } + return AjaxResult.success(flowViewerList); + } + + /** + * 获取流程变量 + * + * @param taskId + * @return + */ + @Override + public AjaxResult processVariables(String taskId) { + // 流程变量 + HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery() + .includeProcessVariables().finished().taskId(taskId).singleResult(); + if (Objects.nonNull(historicTaskInstance)) { + return AjaxResult.success(historicTaskInstance.getProcessVariables()); + } else { + Map variables = taskService.getVariables(taskId); + return AjaxResult.success(variables); + } + } + + /** + * 审批任务获取下一节点 + * + * @param flowTaskVo 任务 + * @return + */ + @Override + public AjaxResult getNextFlowNode(FlowTaskVo flowTaskVo) { + // Step 1. 获取当前节点并找到下一步节点 + Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult(); + if (Objects.isNull(task)) { + return AjaxResult.error("任务不存在或已被审批!"); + } + // Step 2. 获取当前流程所有流程变量(网关节点时需要校验表达式) + Map variables = taskService.getVariables(task.getId()); + List nextUserTask = FindNextNodeUtil.getNextUserTasks(repositoryService, task, variables); + if (CollectionUtils.isEmpty(nextUserTask)) { + return AjaxResult.success("流程已完结!", null); + } + return getFlowAttribute(nextUserTask); + } + + /** + * 发起流程获取下一节点 + * + * @param flowTaskVo 任务 + * @return + */ + @Override + public AjaxResult getNextFlowNodeByStart(FlowTaskVo flowTaskVo) { + // Step 1. 查找流程定义信息 + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .deploymentId(flowTaskVo.getDeploymentId()).singleResult(); + if (Objects.isNull(processDefinition)) { + return AjaxResult.error("流程信息不存在!"); + } + // Step 2. 获取下一任务节点(网关节点时需要校验表达式) + List nextUserTask = FindNextNodeUtil.getNextUserTasksByStart(repositoryService, processDefinition, + flowTaskVo.getVariables()); + if (CollectionUtils.isEmpty(nextUserTask)) { + return AjaxResult.error("暂未查找到下一任务,请检查流程设计是否正确!"); + } + return getFlowAttribute(nextUserTask); + } + + /** + * 获取任务节点属性,包含自定义属性等 + * + * @param nextUserTask + */ + private AjaxResult getFlowAttribute(List nextUserTask) { + FlowNextDto flowNextDto = new FlowNextDto(); + for (UserTask userTask : nextUserTask) { + MultiInstanceLoopCharacteristics multiInstance = userTask.getLoopCharacteristics(); + // 会签节点 + if (Objects.nonNull(multiInstance)) { + flowNextDto.setVars(multiInstance.getInputDataItem()); + flowNextDto.setType(ProcessConstants.PROCESS_MULTI_INSTANCE); + flowNextDto.setDataType(ProcessConstants.DYNAMIC); + } else { + // 读取自定义节点属性 判断是否是否需要动态指定任务接收人员、组 + String dataType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, + ProcessConstants.PROCESS_CUSTOM_DATA_TYPE); + String userType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, + ProcessConstants.PROCESS_CUSTOM_USER_TYPE); + flowNextDto.setVars(ProcessConstants.PROCESS_APPROVAL); + flowNextDto.setType(userType); + flowNextDto.setDataType(dataType); + } + } + return AjaxResult.success(flowNextDto); + } + + /** + * 流程初始化表单 + * + * @param deployId + * @return + */ + @Override + public AjaxResult flowFormData(String deployId) { + // 第一次申请获取初始化表单 + if (StringUtils.isNotBlank(deployId)) { + FormTemplate sysForm = sysInstanceFormService.selectSysDeployFormByDeployId(deployId); + if (Objects.isNull(sysForm)) { + return AjaxResult.error("请先配置流程表单!"); + } + return AjaxResult.success(JSONObject.parseObject(sysForm.getFormSchema())); + } else { + return AjaxResult.error("参数错误!"); + } + } + + /** + * 流程节点信息 + * + * @param procInsId + * @return + */ + @Override + public AjaxResult flowXmlAndNode(String procInsId, String deployId) { + try { + List flowViewerList = new ArrayList<>(); + // 获取已经完成的节点 + List listFinished = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(procInsId) + .finished() + .list(); + + // 保存已经完成的流程节点编号 + listFinished.forEach(s -> { + FlowViewerDto flowViewerDto = new FlowViewerDto(); + flowViewerDto.setKey(s.getActivityId()); + flowViewerDto.setCompleted(true); + // 退回节点不进行展示 + if (StringUtils.isBlank(s.getDeleteReason())) { + flowViewerList.add(flowViewerDto); + } + }); + + // 获取代办节点 + List listUnFinished = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(procInsId) + .unfinished() + .list(); + + // 保存需要代办的节点编号 + listUnFinished.forEach(s -> { + // 删除已退回节点 + flowViewerList.removeIf(task -> task.getKey().equals(s.getActivityId())); + FlowViewerDto flowViewerDto = new FlowViewerDto(); + flowViewerDto.setKey(s.getActivityId()); + flowViewerDto.setCompleted(false); + flowViewerList.add(flowViewerDto); + }); + Map result = new HashMap<>(); + // xmlData 数据 + ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId) + .singleResult(); + InputStream inputStream = repositoryService.getResourceAsStream(definition.getDeploymentId(), + definition.getResourceName()); + String xmlData = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + result.put("nodeData", flowViewerList); + result.put("xmlData", xmlData); + return AjaxResult.success(result); + } catch (Exception e) { + return AjaxResult.error("高亮历史任务失败"); + } + } + + /** + * 流程节点表单 + * + * @param taskId 流程任务编号 + * @return + */ + @Override + public AjaxResult flowTaskForm(String taskId) throws Exception { + Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); + // 流程变量 + Map parameters = new HashMap<>(); + HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery() + .includeProcessVariables().finished().taskId(taskId).singleResult(); + if (Objects.nonNull(historicTaskInstance)) { + parameters = historicTaskInstance.getProcessVariables(); + } else { + parameters = taskService.getVariables(taskId); + } + JSONObject oldVariables = JSONObject.parseObject(JSON.toJSONString(parameters.get("formJson"))); + List oldFields = JSON.parseObject(JSON.toJSONString(oldVariables.get("widgetList")), + new TypeReference>() { + }); + // 设置已填写的表单为禁用状态 + for (JSONObject oldField : oldFields) { + JSONObject options = oldField.getJSONObject("options"); + options.put("disabled", true); + } + // 暂时只处理用户任务上的表单 + if (StringUtils.isNotBlank(task.getFormKey())) { + FormTemplate sysForm = formTemplateService.selectFormTemplateByFormId(Long.parseLong(task.getFormKey())); + if (Objects.isNull(sysForm)) { + throw new ServiceException("当前流程配置的表单不存在或已被删除"); + } + JSONObject data = JSONObject.parseObject(sysForm.getFormSchema()); + List newFields = JSON.parseObject( + JSON.toJSONString(data.get("widgetList")), + new TypeReference>() { + }); + // 表单回显时 加入子表单信息到流程变量中 + for (JSONObject newField : newFields) { + String key = newField.getString("id"); + // 处理图片上传组件回显问题 + if ("picture-upload".equals(newField.getString("type"))) { + parameters.put(key, new ArrayList<>()); + } else { + parameters.put(key, null); + } + } + oldFields.addAll(newFields); + } + oldVariables.put("widgetList", oldFields); + parameters.put("formJson", oldVariables); + return AjaxResult.success(parameters); + } + + /** + * 流程节点信息 + * + * @param procInsId + * @param elementId + * @return + */ + @Override + public AjaxResult flowTaskInfo(String procInsId, String elementId) { + List list = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(procInsId) + .activityId(elementId) + .list(); + // 退回任务后有多条数据 只取待办任务进行展示 + list.removeIf(task -> StringUtils.isNotBlank(task.getDeleteReason())); + if (CollectionUtils.isEmpty(list)) { + return AjaxResult.success(); + } + if (list.size() > 1) { + list.removeIf(task -> Objects.nonNull(task.getEndTime())); + } + HistoricActivityInstance histIns = list.get(0); + FlowTaskDto flowTask = new FlowTaskDto(); + flowTask.setTaskId(histIns.getTaskId()); + flowTask.setTaskName(histIns.getActivityName()); + flowTask.setCreateTime(histIns.getStartTime()); + flowTask.setFinishTime(histIns.getEndTime()); + if (StringUtils.isNotBlank(histIns.getAssignee())) { + SysUser sysUser = sysUserService.selectUserById(Long.parseLong(histIns.getAssignee())); + flowTask.setAssigneeId(sysUser.getUserId()); + flowTask.setAssigneeName(sysUser.getNickName()); + flowTask.setDeptName(Objects.nonNull(sysUser.getDept()) ? sysUser.getDept().getDeptName() : ""); + + } + // 流程变量信息 + // HistoricTaskInstance historicTaskInstance = + // historyService.createHistoricTaskInstanceQuery() + // .includeProcessVariables().finished().taskId(histIns.getTaskId()).singleResult(); + // flowTask.setVariables(historicTaskInstance.getProcessVariables()); + + // 展示审批人员 + List linksForTask = historyService.getHistoricIdentityLinksForTask(histIns.getTaskId()); + StringBuilder stringBuilder = new StringBuilder(); + for (HistoricIdentityLink identityLink : linksForTask) { + // 获选人,候选组/角色(多个) + if ("candidate".equals(identityLink.getType())) { + if (StringUtils.isNotBlank(identityLink.getUserId())) { + SysUser sysUser = sysUserService.selectUserById(Long.parseLong(identityLink.getUserId())); + stringBuilder.append(sysUser.getNickName()).append(","); + } + if (StringUtils.isNotBlank(identityLink.getGroupId())) { + SysRole sysRole = sysRoleService.selectRoleById(Long.parseLong(identityLink.getGroupId())); + stringBuilder.append(sysRole.getRoleName()).append(","); + } + } + } + if (StringUtils.isNotBlank(stringBuilder)) { + flowTask.setCandidate(stringBuilder.substring(0, stringBuilder.length() - 1)); + } + + flowTask.setDuration(histIns.getDurationInMillis() == null || histIns.getDurationInMillis() == 0 ? null + : getDate(histIns.getDurationInMillis())); + // 获取意见评论内容 + List commentList = taskService.getProcessInstanceComments(histIns.getProcessInstanceId()); + commentList.forEach(comment -> { + if (histIns.getTaskId().equals(comment.getTaskId())) { + flowTask.setComment( + FlowCommentDto.builder().type(comment.getType()).comment(comment.getFullMessage()).build()); + } + }); + return AjaxResult.success(flowTask); + } + + /** + * 将Object类型的数据转化成Map + * + * @param obj + * @return + * @throws Exception + */ + public Map obj2Map(Object obj) throws Exception { + Map map = new HashMap(); + Field[] fields = obj.getClass().getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + map.put(field.getName(), field.get(obj)); + } + return map; + } + + /** + * 流程完成时间处理 + * + * @param ms + * @return + */ + private String getDate(long ms) { + + long day = ms / (24 * 60 * 60 * 1000); + long hour = (ms / (60 * 60 * 1000) - day * 24); + long minute = ((ms / (60 * 1000)) - day * 24 * 60 - hour * 60); + long second = (ms / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60); + + if (day > 0) { + return day + "天" + hour + "小时" + minute + "分钟"; + } + if (hour > 0) { + return hour + "小时" + minute + "分钟"; + } + if (minute > 0) { + return minute + "分钟"; + } + if (second > 0) { + return second + "秒"; + } else { + return 0 + "秒"; + } + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/SysDeployFormServiceImpl.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/SysDeployFormServiceImpl.java new file mode 100644 index 0000000..9f5e661 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/SysDeployFormServiceImpl.java @@ -0,0 +1,106 @@ +package com.ruoyi.flowable.service.impl; + +import java.util.List; +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.flowable.domain.SysDeployForm; +import com.ruoyi.flowable.mapper.SysDeployFormMapper; +import com.ruoyi.flowable.service.ISysDeployFormService; +import com.ruoyi.form.domain.FormTemplate; + +/** + * 流程实例关联表单Service业务层处理 + * + * @author Tony + * @date 2021-04-03 + */ +@Service +public class SysDeployFormServiceImpl implements ISysDeployFormService { + @Autowired + private SysDeployFormMapper sysDeployFormMapper; + + /** + * 查询流程实例关联表单 + * + * @param id 流程实例关联表单ID + * @return 流程实例关联表单 + */ + @Override + public SysDeployForm selectSysDeployFormById(Long id) { + return sysDeployFormMapper.selectSysDeployFormById(id); + } + + /** + * 查询流程实例关联表单列表 + * + * @param sysDeployForm 流程实例关联表单 + * @return 流程实例关联表单 + */ + @Override + public List selectSysDeployFormList(SysDeployForm sysDeployForm) { + return sysDeployFormMapper.selectSysDeployFormList(sysDeployForm); + } + + /** + * 新增流程实例关联表单 + * + * @param sysDeployForm 流程实例关联表单 + * @return 结果 + */ + @Override + public int insertSysDeployForm(SysDeployForm sysDeployForm) { + FormTemplate sysForm = sysDeployFormMapper.selectSysDeployFormByDeployId(sysDeployForm.getDeployId()); + if (Objects.isNull(sysForm)) { + return sysDeployFormMapper.insertSysDeployForm(sysDeployForm); + } else { + return updateSysDeployForm(sysDeployForm); + } + } + + /** + * 修改流程实例关联表单 + * + * @param sysDeployForm 流程实例关联表单 + * @return 结果 + */ + @Override + public int updateSysDeployForm(SysDeployForm sysDeployForm) { + return sysDeployFormMapper.updateSysDeployForm(sysDeployForm); + } + + /** + * 批量删除流程实例关联表单 + * + * @param ids 需要删除的流程实例关联表单ID + * @return 结果 + */ + @Override + public int deleteSysDeployFormByIds(Long[] ids) { + return sysDeployFormMapper.deleteSysDeployFormByIds(ids); + } + + /** + * 删除流程实例关联表单信息 + * + * @param id 流程实例关联表单ID + * @return 结果 + */ + @Override + public int deleteSysDeployFormById(Long id) { + return sysDeployFormMapper.deleteSysDeployFormById(id); + } + + /** + * 查询流程挂着的表单 + * + * @param deployId + * @return + */ + @Override + public FormTemplate selectSysDeployFormByDeployId(String deployId) { + return sysDeployFormMapper.selectSysDeployFormByDeployId(deployId); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/SysExpressionServiceImpl.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/SysExpressionServiceImpl.java new file mode 100644 index 0000000..b7589df --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/SysExpressionServiceImpl.java @@ -0,0 +1,98 @@ +package com.ruoyi.flowable.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.flowable.domain.SysExpression; +import com.ruoyi.flowable.mapper.SysExpressionMapper; +import com.ruoyi.flowable.service.ISysExpressionService; + +/** + * 流程达式Service业务层处理 + * + * @author ruoyi + * @date 2022-12-12 + */ +@Service +public class SysExpressionServiceImpl implements ISysExpressionService +{ + @Autowired + private SysExpressionMapper sysExpressionMapper; + + /** + * 查询流程达式 + * + * @param id 流程达式主键 + * @return 流程达式 + */ + @Override + public SysExpression selectSysExpressionById(Long id) + { + return sysExpressionMapper.selectSysExpressionById(id); + } + + /** + * 查询流程达式列表 + * + * @param sysExpression 流程达式 + * @return 流程达式 + */ + @Override + public List selectSysExpressionList(SysExpression sysExpression) + { + return sysExpressionMapper.selectSysExpressionList(sysExpression); + } + + /** + * 新增流程达式 + * + * @param sysExpression 流程达式 + * @return 结果 + */ + @Override + public int insertSysExpression(SysExpression sysExpression) + { + sysExpression.setCreateTime(DateUtils.getNowDate()); + return sysExpressionMapper.insertSysExpression(sysExpression); + } + + /** + * 修改流程达式 + * + * @param sysExpression 流程达式 + * @return 结果 + */ + @Override + public int updateSysExpression(SysExpression sysExpression) + { + sysExpression.setUpdateTime(DateUtils.getNowDate()); + return sysExpressionMapper.updateSysExpression(sysExpression); + } + + /** + * 批量删除流程达式 + * + * @param ids 需要删除的流程达式主键 + * @return 结果 + */ + @Override + public int deleteSysExpressionByIds(Long[] ids) + { + return sysExpressionMapper.deleteSysExpressionByIds(ids); + } + + /** + * 删除流程达式信息 + * + * @param id 流程达式主键 + * @return 结果 + */ + @Override + public int deleteSysExpressionById(Long id) + { + return sysExpressionMapper.deleteSysExpressionById(id); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/SysListenerServiceImpl.java b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/SysListenerServiceImpl.java new file mode 100644 index 0000000..10cb500 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/SysListenerServiceImpl.java @@ -0,0 +1,98 @@ +package com.ruoyi.flowable.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.flowable.domain.SysListener; +import com.ruoyi.flowable.mapper.SysListenerMapper; +import com.ruoyi.flowable.service.ISysListenerService; + +/** + * 流程监听Service业务层处理 + * + * @author Tony + * @date 2022-12-25 + */ +@Service +public class SysListenerServiceImpl implements ISysListenerService +{ + @Autowired + private SysListenerMapper sysListenerMapper; + + /** + * 查询流程监听 + * + * @param id 流程监听主键 + * @return 流程监听 + */ + @Override + public SysListener selectSysListenerById(Long id) + { + return sysListenerMapper.selectSysListenerById(id); + } + + /** + * 查询流程监听列表 + * + * @param sysListener 流程监听 + * @return 流程监听 + */ + @Override + public List selectSysListenerList(SysListener sysListener) + { + return sysListenerMapper.selectSysListenerList(sysListener); + } + + /** + * 新增流程监听 + * + * @param sysListener 流程监听 + * @return 结果 + */ + @Override + public int insertSysListener(SysListener sysListener) + { + sysListener.setCreateTime(DateUtils.getNowDate()); + return sysListenerMapper.insertSysListener(sysListener); + } + + /** + * 修改流程监听 + * + * @param sysListener 流程监听 + * @return 结果 + */ + @Override + public int updateSysListener(SysListener sysListener) + { + sysListener.setUpdateTime(DateUtils.getNowDate()); + return sysListenerMapper.updateSysListener(sysListener); + } + + /** + * 批量删除流程监听 + * + * @param ids 需要删除的流程监听主键 + * @return 结果 + */ + @Override + public int deleteSysListenerByIds(Long[] ids) + { + return sysListenerMapper.deleteSysListenerByIds(ids); + } + + /** + * 删除流程监听信息 + * + * @param id 流程监听主键 + * @return 结果 + */ + @Override + public int deleteSysListenerById(Long id) + { + return sysListenerMapper.deleteSysListenerById(id); + } +} diff --git a/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/FlowDeployMapper.xml b/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/FlowDeployMapper.xml new file mode 100644 index 0000000..dc4c2da --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/FlowDeployMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/SysDeployFormMapper.xml b/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/SysDeployFormMapper.xml new file mode 100644 index 0000000..b011c19 --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/SysDeployFormMapper.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + select id, form_id, deploy_id from sys_deploy_form + + + + + + + + + + insert into sys_deploy_form + + form_id, + deploy_id, + + + #{formId}, + #{deployId}, + + + + + update sys_deploy_form + + + form_id = #{formId}, + deploy_id = #{deployId}, + + where id = #{id} + + + + form_id = #{formId}, + + where deploy_id = #{deployId} + + + + + delete from sys_deploy_form where id = #{id} + + + + delete from sys_deploy_form where id in + + #{id} + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/SysExpressionMapper.xml b/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/SysExpressionMapper.xml new file mode 100644 index 0000000..8e2dc1e --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/SysExpressionMapper.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + select id, name, expression, data_type,create_time, update_time, create_by, update_by, status, remark from sys_expression + + + + + + + + insert into sys_expression + + name, + expression, + data_type, + create_time, + update_time, + create_by, + update_by, + status, + remark, + + + #{name}, + #{expression}, + #{dataType}, + #{createTime}, + #{updateTime}, + #{createBy}, + #{updateBy}, + #{status}, + #{remark}, + + + + + update sys_expression + + name = #{name}, + expression = #{expression}, + data_type = #{dataType}, + create_time = #{createTime}, + update_time = #{updateTime}, + create_by = #{createBy}, + update_by = #{updateBy}, + status = #{status}, + remark = #{remark}, + + where id = #{id} + + + + delete from sys_expression where id = #{id} + + + + delete from sys_expression where id in + + #{id} + + + diff --git a/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/SysListenerMapper.xml b/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/SysListenerMapper.xml new file mode 100644 index 0000000..d1e498b --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/SysListenerMapper.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + select id, + name, + type, + event_type, + value_type, + value, + create_time, + update_time, + create_by, + update_by, + status, + remark + from sys_listener + + + + + + + + insert into sys_listener + + name, + type, + event_type, + value_type, + value, + create_time, + update_time, + create_by, + update_by, + status, + remark, + + + #{name}, + #{type}, + #{eventType}, + #{valueType}, + #{value}, + #{createTime}, + #{updateTime}, + #{createBy}, + #{updateBy}, + #{status}, + #{remark}, + + + + + update sys_listener + + name = #{name}, + type = #{type}, + event_type = #{eventType}, + value_type = #{valueType}, + value = #{value}, + create_time = #{createTime}, + update_time = #{updateTime}, + create_by = #{createBy}, + update_by = #{updateBy}, + status = #{status}, + remark = #{remark}, + + where id = #{id} + + + + delete + from sys_listener + where id = #{id} + + + + delete from sys_listener where id in + + #{id} + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/SysTaskFormMapper.xml b/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/SysTaskFormMapper.xml new file mode 100644 index 0000000..9b7bbcc --- /dev/null +++ b/ruoyi-models/ruoyi-flowable/src/main/resources/mapper/flowable/SysTaskFormMapper.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + select id, form_id, task_id from sys_task_form + + + + + + + + insert into sys_task_form + + form_id, + task_id, + + + #{formId}, + #{taskId}, + + + + + update sys_task_form + + form_id = #{formId}, + task_id = #{taskId}, + + where id = #{id} + + + + delete from sys_task_form where id = #{id} + + + + delete from sys_task_form where id in + + #{id} + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-form/pom.xml b/ruoyi-models/ruoyi-form/pom.xml new file mode 100644 index 0000000..9be7ac9 --- /dev/null +++ b/ruoyi-models/ruoyi-form/pom.xml @@ -0,0 +1,27 @@ + + + + ruoyi-models + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-form + + + form表单 + + + + + + + com.ruoyi.geekxd + ruoyi-common + + + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/controller/FormDataController.java b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/controller/FormDataController.java new file mode 100644 index 0000000..791affc --- /dev/null +++ b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/controller/FormDataController.java @@ -0,0 +1,118 @@ +package com.ruoyi.form.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.form.domain.FormData; +import com.ruoyi.form.service.IFormDataService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 单数据Controller + * + * @author ruoyi + * @date 2025-05-12 + */ +@RestController +@RequestMapping("/form/data") +@Tag(name = "【单数据】管理") +public class FormDataController extends BaseController +{ + @Autowired + private IFormDataService formDataService; + + /** + * 查询单数据列表 + */ + @Operation(summary = "查询单数据列表") + @PreAuthorize("@ss.hasPermi('form:data:list')") + @GetMapping("/list") + public TableDataInfo list(FormData formData) + { + startPage(); + List list = formDataService.selectFormDataList(formData); + return getDataTable(list); + } + + /** + * 导出单数据列表 + */ + @Operation(summary = "导出单数据列表") + @PreAuthorize("@ss.hasPermi('form:data:export')") + @Log(title = "单数据", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, FormData formData) + { + List list = formDataService.selectFormDataList(formData); + ExcelUtil util = new ExcelUtil(FormData.class); + util.exportExcel(response, list, "单数据数据"); + } + + /** + * 获取单数据详细信息 + */ + @Operation(summary = "获取单数据详细信息") + @PreAuthorize("@ss.hasPermi('form:data:query')") + @GetMapping(value = "/{dataId}") + public AjaxResult getInfo(@PathVariable("dataId") Long dataId) + { + return success(formDataService.selectFormDataByDataId(dataId)); + } + + /** + * 新增单数据 + */ + @Operation(summary = "新增单数据") + @PreAuthorize("@ss.hasPermi('form:data:add')") + @Log(title = "单数据", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody FormData formData) + { + formData.setCreateBy(getUsername()); + return toAjax(formDataService.insertFormData(formData)); + } + + /** + * 修改单数据 + */ + @Operation(summary = "修改单数据") + @PreAuthorize("@ss.hasPermi('form:data:edit')") + @Log(title = "单数据", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody FormData formData) + { + formData.setUpdateBy(getUsername()); + return toAjax(formDataService.updateFormData(formData)); + } + + /** + * 删除单数据 + */ + @Operation(summary = "删除单数据") + @PreAuthorize("@ss.hasPermi('form:data:remove')") + @Log(title = "单数据", businessType = BusinessType.DELETE) + @DeleteMapping("/{dataIds}") + public AjaxResult remove(@PathVariable( name = "dataIds" ) Long[] dataIds) + { + return toAjax(formDataService.deleteFormDataByDataIds(dataIds)); + } +} diff --git a/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/controller/FormTemplateController.java b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/controller/FormTemplateController.java new file mode 100644 index 0000000..4bfab4e --- /dev/null +++ b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/controller/FormTemplateController.java @@ -0,0 +1,113 @@ +package com.ruoyi.form.controller; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.form.domain.FormTemplate; +import com.ruoyi.form.service.IFormTemplateService; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; + +/** + * 单模板Controller + * + * @author ruoyi + * @date 2025-05-12 + */ +@RestController +@RequestMapping("/form/template") +@Tag(name = "【单模板】管理") +public class FormTemplateController extends BaseController +{ + @Autowired + private IFormTemplateService formTemplateService; + + /** + * 查询单模板列表 + */ + @Operation(summary = "查询单模板列表") + @PreAuthorize("@ss.hasPermi('form:template:list')") + @GetMapping("/list") + public TableDataInfo list(FormTemplate formTemplate) + { + startPage(); + List list = formTemplateService.selectFormTemplateList(formTemplate); + return getDataTable(list); + } + + /** + * 导出单模板列表 + */ + @Operation(summary = "导出单模板列表") + @PreAuthorize("@ss.hasPermi('form:template:export')") + @Log(title = "单模板", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, FormTemplate formTemplate) + { + List list = formTemplateService.selectFormTemplateList(formTemplate); + ExcelUtil util = new ExcelUtil(FormTemplate.class); + util.exportExcel(response, list, "单模板数据"); + } + + /** + * 获取单模板详细信息 + */ + @Operation(summary = "获取单模板详细信息") + @PreAuthorize("@ss.hasPermi('form:template:query')") + @GetMapping(value = "/{formId}") + public AjaxResult getInfo(@PathVariable("formId") Long formId) + { + return success(formTemplateService.selectFormTemplateByFormId(formId)); + } + + /** + * 新增单模板 + */ + @Operation(summary = "新增单模板") + @PreAuthorize("@ss.hasPermi('form:template:add')") + @Log(title = "单模板", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody FormTemplate formTemplate) + { + return toAjax(formTemplateService.insertFormTemplate(formTemplate)); + } + + /** + * 修改单模板 + */ + @Operation(summary = "修改单模板") + @PreAuthorize("@ss.hasPermi('form:template:edit')") + @Log(title = "单模板", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody FormTemplate formTemplate) + { + return toAjax(formTemplateService.updateFormTemplate(formTemplate)); + } + + /** + * 删除单模板 + */ + @Operation(summary = "删除单模板") + @PreAuthorize("@ss.hasPermi('form:template:remove')") + @Log(title = "单模板", businessType = BusinessType.DELETE) + @DeleteMapping("/{formIds}") + public AjaxResult remove(@PathVariable( name = "formIds" ) Long[] formIds) + { + return toAjax(formTemplateService.deleteFormTemplateByFormIds(formIds)); + } +} diff --git a/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/domain/FormData.java b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/domain/FormData.java new file mode 100644 index 0000000..cd8066e --- /dev/null +++ b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/domain/FormData.java @@ -0,0 +1,140 @@ +package com.ruoyi.form.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 单数据对象 form_data + * + * @author ruoyi + * @date 2025-05-12 + */ +@Schema(description = "单数据对象") +public class FormData extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 数据ID */ + @Schema(title = "数据ID") + private Long dataId; + + /** 关联的表单ID */ + @Schema(title = "关联的表单ID") + @Excel(name = "关联的表单ID") + private Long formId; + + /** 表单版本(与模板表版本一致) */ + @Schema(title = "表单版本(与模板表版本一致)") + @Excel(name = "表单版本", readConverterExp = "与=模板表版本一致") + private String formVersion; + + /** 表单数据内容(JSON格式) */ + @Schema(title = "表单数据内容(JSON格式)") + @Excel(name = "表单数据内容", readConverterExp = "J=SON格式") + private String dataContent; + + /** 数据状态(draft, submitted, approved, rejected) */ + @Schema(title = "数据状态(draft, submitted, approved, rejected)") + @Excel(name = "数据状态", readConverterExp = "d=raft,,s=ubmitted,,a=pproved,,r=ejected") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + @Schema(title = "删除标志(0代表存在 2代表删除)") + private String delFlag; + + /** 表单名称 */ + @Schema(title = "表单名称") + @Excel(name = "表单名称") + private String formName; + + /** 表单JSON Schema(vForm配置) */ + @Schema(title = "表单JSON Schema(vForm配置)") + @Excel(name = "表单JSON Schema", readConverterExp = "v=Form配置") + private String formSchema; + + public void setDataId(Long dataId) { + this.dataId = dataId; + } + + public Long getDataId() { + return dataId; + } + + public String getFormSchema() { + return formSchema; + } + + public void setFormSchema(String formSchema) { + this.formSchema = formSchema; + } + + public void setFormId(Long formId) { + this.formId = formId; + } + + public Long getFormId() { + return formId; + } + + public void setFormVersion(String formVersion) { + this.formVersion = formVersion; + } + + public String getFormVersion() { + return formVersion; + } + + public void setDataContent(String dataContent) { + this.dataContent = dataContent; + } + + public String getDataContent() { + return dataContent; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getStatus() { + return status; + } + + public void setDelFlag(String delFlag) { + this.delFlag = delFlag; + } + + public String getDelFlag() { + return delFlag; + } + + public void setFormName(String formName) { + this.formName = formName; + } + + public String getFormName() { + return formName; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("dataId", getDataId()) + .append("formId", getFormId()) + .append("formVersion", getFormVersion()) + .append("dataContent", getDataContent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("delFlag", getDelFlag()) + .append("formName", getFormName()) + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/domain/FormTemplate.java b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/domain/FormTemplate.java new file mode 100644 index 0000000..8cb908c --- /dev/null +++ b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/domain/FormTemplate.java @@ -0,0 +1,131 @@ +package com.ruoyi.form.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 单模板对象 form_template + * + * @author ruoyi + * @date 2025-05-12 + */ +@Schema(description = "单模板对象") +public class FormTemplate extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + + /** 表单ID */ + @Schema(title = "表单ID") + private Long formId; + + /** 表单名称 */ + @Schema(title = "表单名称") + @Excel(name = "表单名称") + private String formName; + + /** 表单JSON Schema(vForm配置) */ + @Schema(title = "表单JSON Schema(vForm配置)") + @Excel(name = "表单JSON Schema", readConverterExp = "v=Form配置") + private String formSchema; + + /** 表单版本(语义化版本) */ + @Schema(title = "表单版本(语义化版本)") + @Excel(name = "表单版本", readConverterExp = "语=义化版本") + private String formVersion; + + /** 发布状态(0: 草稿, 1: 已发布, 2: 已停用) */ + @Schema(title = "发布状态(0: 草稿, 1: 已发布, 2: 已停用)") + @Excel(name = "发布状态", readConverterExp = "0=:,草=稿,,1=:,已=发布,,2=:,已=停用") + private String formStatus; + + /** 删除标志(0代表存在 2代表删除) */ + @Schema(title = "删除标志(0代表存在 2代表删除)") + private String delFlag; + public void setFormId(Long formId) + { + this.formId = formId; + } + + public Long getFormId() + { + return formId; + } + + + public void setFormName(String formName) + { + this.formName = formName; + } + + public String getFormName() + { + return formName; + } + + + public void setFormSchema(String formSchema) + { + this.formSchema = formSchema; + } + + public String getFormSchema() + { + return formSchema; + } + + + public void setFormVersion(String formVersion) + { + this.formVersion = formVersion; + } + + public String getFormVersion() + { + return formVersion; + } + + + public void setFormStatus(String formStatus) + { + this.formStatus = formStatus; + } + + public String getFormStatus() + { + return formStatus; + } + + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("formId", getFormId()) + .append("formName", getFormName()) + .append("formSchema", getFormSchema()) + .append("formVersion", getFormVersion()) + .append("formStatus", getFormStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("delFlag", getDelFlag()) + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/mapper/FormDataMapper.java b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/mapper/FormDataMapper.java new file mode 100644 index 0000000..da2c583 --- /dev/null +++ b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/mapper/FormDataMapper.java @@ -0,0 +1,61 @@ +package com.ruoyi.form.mapper; + +import java.util.List; +import com.ruoyi.form.domain.FormData; + +/** + * 单数据Mapper接口 + * + * @author ruoyi + * @date 2025-05-12 + */ +public interface FormDataMapper +{ + /** + * 查询单数据 + * + * @param dataId 单数据主键 + * @return 单数据 + */ + public FormData selectFormDataByDataId(Long dataId); + + /** + * 查询单数据列表 + * + * @param formData 单数据 + * @return 单数据集合 + */ + public List selectFormDataList(FormData formData); + + /** + * 新增单数据 + * + * @param formData 单数据 + * @return 结果 + */ + public int insertFormData(FormData formData); + + /** + * 修改单数据 + * + * @param formData 单数据 + * @return 结果 + */ + public int updateFormData(FormData formData); + + /** + * 删除单数据 + * + * @param dataId 单数据主键 + * @return 结果 + */ + public int deleteFormDataByDataId(Long dataId); + + /** + * 批量删除单数据 + * + * @param dataIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteFormDataByDataIds(Long[] dataIds); +} diff --git a/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/mapper/FormTemplateMapper.java b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/mapper/FormTemplateMapper.java new file mode 100644 index 0000000..aa146e7 --- /dev/null +++ b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/mapper/FormTemplateMapper.java @@ -0,0 +1,61 @@ +package com.ruoyi.form.mapper; + +import java.util.List; +import com.ruoyi.form.domain.FormTemplate; + +/** + * 单模板Mapper接口 + * + * @author ruoyi + * @date 2025-05-12 + */ +public interface FormTemplateMapper +{ + /** + * 查询单模板 + * + * @param formId 单模板主键 + * @return 单模板 + */ + public FormTemplate selectFormTemplateByFormId(Long formId); + + /** + * 查询单模板列表 + * + * @param formTemplate 单模板 + * @return 单模板集合 + */ + public List selectFormTemplateList(FormTemplate formTemplate); + + /** + * 新增单模板 + * + * @param formTemplate 单模板 + * @return 结果 + */ + public int insertFormTemplate(FormTemplate formTemplate); + + /** + * 修改单模板 + * + * @param formTemplate 单模板 + * @return 结果 + */ + public int updateFormTemplate(FormTemplate formTemplate); + + /** + * 删除单模板 + * + * @param formId 单模板主键 + * @return 结果 + */ + public int deleteFormTemplateByFormId(Long formId); + + /** + * 批量删除单模板 + * + * @param formIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteFormTemplateByFormIds(Long[] formIds); +} diff --git a/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/service/IFormDataService.java b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/service/IFormDataService.java new file mode 100644 index 0000000..8f7f597 --- /dev/null +++ b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/service/IFormDataService.java @@ -0,0 +1,61 @@ +package com.ruoyi.form.service; + +import java.util.List; +import com.ruoyi.form.domain.FormData; + +/** + * 单数据Service接口 + * + * @author ruoyi + * @date 2025-05-12 + */ +public interface IFormDataService +{ + /** + * 查询单数据 + * + * @param dataId 单数据主键 + * @return 单数据 + */ + public FormData selectFormDataByDataId(Long dataId); + + /** + * 查询单数据列表 + * + * @param formData 单数据 + * @return 单数据集合 + */ + public List selectFormDataList(FormData formData); + + /** + * 新增单数据 + * + * @param formData 单数据 + * @return 结果 + */ + public int insertFormData(FormData formData); + + /** + * 修改单数据 + * + * @param formData 单数据 + * @return 结果 + */ + public int updateFormData(FormData formData); + + /** + * 批量删除单数据 + * + * @param dataIds 需要删除的单数据主键集合 + * @return 结果 + */ + public int deleteFormDataByDataIds(Long[] dataIds); + + /** + * 删除单数据信息 + * + * @param dataId 单数据主键 + * @return 结果 + */ + public int deleteFormDataByDataId(Long dataId); +} diff --git a/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/service/IFormTemplateService.java b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/service/IFormTemplateService.java new file mode 100644 index 0000000..4c1bfb7 --- /dev/null +++ b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/service/IFormTemplateService.java @@ -0,0 +1,61 @@ +package com.ruoyi.form.service; + +import java.util.List; +import com.ruoyi.form.domain.FormTemplate; + +/** + * 单模板Service接口 + * + * @author ruoyi + * @date 2025-05-12 + */ +public interface IFormTemplateService +{ + /** + * 查询单模板 + * + * @param formId 单模板主键 + * @return 单模板 + */ + public FormTemplate selectFormTemplateByFormId(Long formId); + + /** + * 查询单模板列表 + * + * @param formTemplate 单模板 + * @return 单模板集合 + */ + public List selectFormTemplateList(FormTemplate formTemplate); + + /** + * 新增单模板 + * + * @param formTemplate 单模板 + * @return 结果 + */ + public int insertFormTemplate(FormTemplate formTemplate); + + /** + * 修改单模板 + * + * @param formTemplate 单模板 + * @return 结果 + */ + public int updateFormTemplate(FormTemplate formTemplate); + + /** + * 批量删除单模板 + * + * @param formIds 需要删除的单模板主键集合 + * @return 结果 + */ + public int deleteFormTemplateByFormIds(Long[] formIds); + + /** + * 删除单模板信息 + * + * @param formId 单模板主键 + * @return 结果 + */ + public int deleteFormTemplateByFormId(Long formId); +} diff --git a/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/service/impl/FormDataServiceImpl.java b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/service/impl/FormDataServiceImpl.java new file mode 100644 index 0000000..94c057f --- /dev/null +++ b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/service/impl/FormDataServiceImpl.java @@ -0,0 +1,96 @@ +package com.ruoyi.form.service.impl; + +import java.util.List; +import com.ruoyi.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.form.mapper.FormDataMapper; +import com.ruoyi.form.domain.FormData; +import com.ruoyi.form.service.IFormDataService; + +/** + * 单数据Service业务层处理 + * + * @author ruoyi + * @date 2025-05-12 + */ +@Service +public class FormDataServiceImpl implements IFormDataService +{ + @Autowired + private FormDataMapper formDataMapper; + + /** + * 查询单数据 + * + * @param dataId 单数据主键 + * @return 单数据 + */ + @Override + public FormData selectFormDataByDataId(Long dataId) + { + return formDataMapper.selectFormDataByDataId(dataId); + } + + /** + * 查询单数据列表 + * + * @param formData 单数据 + * @return 单数据 + */ + @Override + public List selectFormDataList(FormData formData) + { + return formDataMapper.selectFormDataList(formData); + } + + /** + * 新增单数据 + * + * @param formData 单数据 + * @return 结果 + */ + @Override + public int insertFormData(FormData formData) + { + formData.setCreateTime(DateUtils.getNowDate()); + return formDataMapper.insertFormData(formData); + } + + /** + * 修改单数据 + * + * @param formData 单数据 + * @return 结果 + */ + @Override + public int updateFormData(FormData formData) + { + formData.setUpdateTime(DateUtils.getNowDate()); + return formDataMapper.updateFormData(formData); + } + + /** + * 批量删除单数据 + * + * @param dataIds 需要删除的单数据主键 + * @return 结果 + */ + @Override + public int deleteFormDataByDataIds(Long[] dataIds) + { + return formDataMapper.deleteFormDataByDataIds(dataIds); + } + + /** + * 删除单数据信息 + * + * @param dataId 单数据主键 + * @return 结果 + */ + @Override + public int deleteFormDataByDataId(Long dataId) + { + return formDataMapper.deleteFormDataByDataId(dataId); + } +} diff --git a/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/service/impl/FormTemplateServiceImpl.java b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/service/impl/FormTemplateServiceImpl.java new file mode 100644 index 0000000..5cbb00c --- /dev/null +++ b/ruoyi-models/ruoyi-form/src/main/java/com/ruoyi/form/service/impl/FormTemplateServiceImpl.java @@ -0,0 +1,96 @@ +package com.ruoyi.form.service.impl; + +import java.util.List; +import com.ruoyi.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.form.mapper.FormTemplateMapper; +import com.ruoyi.form.domain.FormTemplate; +import com.ruoyi.form.service.IFormTemplateService; + +/** + * 单模板Service业务层处理 + * + * @author ruoyi + * @date 2025-05-12 + */ +@Service +public class FormTemplateServiceImpl implements IFormTemplateService +{ + @Autowired + private FormTemplateMapper formTemplateMapper; + + /** + * 查询单模板 + * + * @param formId 单模板主键 + * @return 单模板 + */ + @Override + public FormTemplate selectFormTemplateByFormId(Long formId) + { + return formTemplateMapper.selectFormTemplateByFormId(formId); + } + + /** + * 查询单模板列表 + * + * @param formTemplate 单模板 + * @return 单模板 + */ + @Override + public List selectFormTemplateList(FormTemplate formTemplate) + { + return formTemplateMapper.selectFormTemplateList(formTemplate); + } + + /** + * 新增单模板 + * + * @param formTemplate 单模板 + * @return 结果 + */ + @Override + public int insertFormTemplate(FormTemplate formTemplate) + { + formTemplate.setCreateTime(DateUtils.getNowDate()); + return formTemplateMapper.insertFormTemplate(formTemplate); + } + + /** + * 修改单模板 + * + * @param formTemplate 单模板 + * @return 结果 + */ + @Override + public int updateFormTemplate(FormTemplate formTemplate) + { + formTemplate.setUpdateTime(DateUtils.getNowDate()); + return formTemplateMapper.updateFormTemplate(formTemplate); + } + + /** + * 批量删除单模板 + * + * @param formIds 需要删除的单模板主键 + * @return 结果 + */ + @Override + public int deleteFormTemplateByFormIds(Long[] formIds) + { + return formTemplateMapper.deleteFormTemplateByFormIds(formIds); + } + + /** + * 删除单模板信息 + * + * @param formId 单模板主键 + * @return 结果 + */ + @Override + public int deleteFormTemplateByFormId(Long formId) + { + return formTemplateMapper.deleteFormTemplateByFormId(formId); + } +} diff --git a/ruoyi-models/ruoyi-form/src/main/resources/mapper/form/FormDataMapper.xml b/ruoyi-models/ruoyi-form/src/main/resources/mapper/form/FormDataMapper.xml new file mode 100644 index 0000000..ba9a359 --- /dev/null +++ b/ruoyi-models/ruoyi-form/src/main/resources/mapper/form/FormDataMapper.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + select + fd.data_id, + fd.form_id, + fd.form_version, + fd.data_content, + fd.status, + fd.create_by, + fd.create_time, + fd.update_by, + fd.update_time, + fd.remark, + fd.del_flag, + ft.form_name + from form_data fd + left join form_template ft on ft.form_id = fd.form_id + left join sys_user su on su.user_name = fd.create_by + + + + + + + + insert into form_data + + form_id, + form_version, + data_content, + status, + create_by, + create_time, + update_by, + update_time, + remark, + del_flag, + + + #{formId}, + #{formVersion}, + #{dataContent}, + #{status}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{delFlag}, + + + + + update form_data + + form_id = #{formId}, + form_version = #{formVersion}, + data_content = #{dataContent}, + status = #{status}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + del_flag = #{delFlag}, + + where form_data.data_id = #{dataId} + + + + delete from form_data where data_id = #{dataId} + + + + delete from form_data where data_id in + + #{dataId} + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-form/src/main/resources/mapper/form/FormTemplateMapper.xml b/ruoyi-models/ruoyi-form/src/main/resources/mapper/form/FormTemplateMapper.xml new file mode 100644 index 0000000..fff9669 --- /dev/null +++ b/ruoyi-models/ruoyi-form/src/main/resources/mapper/form/FormTemplateMapper.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + select + ft.form_id, + ft.form_name, + ft.form_schema, + ft.form_version, + ft.form_status, + ft.create_by, + ft.create_time, + ft.update_by, + ft.update_time, + ft.remark, + ft.del_flag + from form_template ft + left join sys_user su on su.user_name = ft.create_by + + + + + + + + insert into form_template + + form_name, + form_schema, + form_version, + form_status, + create_by, + create_time, + update_by, + update_time, + remark, + del_flag, + + + #{formName}, + #{formSchema}, + #{formVersion}, + #{formStatus}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{delFlag}, + + + + + update form_template + + form_name = #{formName}, + form_schema = #{formSchema}, + form_version = #{formVersion}, + form_status = #{formStatus}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + del_flag = #{delFlag}, + + where form_template.form_id = #{formId} + + + + delete from form_template where form_id = #{formId} + + + + delete from form_template where form_id in + + #{formId} + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-generator/pom.xml b/ruoyi-models/ruoyi-generator/pom.xml new file mode 100644 index 0000000..361919e --- /dev/null +++ b/ruoyi-models/ruoyi-generator/pom.xml @@ -0,0 +1,40 @@ + + + + ruoyi-models + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-generator + + + generator代码生成 + + + + + + + org.apache.velocity + velocity-engine-core + + + + + com.ruoyi.geekxd + ruoyi-common + + + + + com.alibaba + druid-spring-boot-3-starter + + + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java new file mode 100644 index 0000000..f2e5867 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java @@ -0,0 +1,66 @@ +package com.ruoyi.generator.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 读取代码生成相关配置 + * + * @author ruoyi + */ +@Configuration +@ConfigurationProperties(prefix = "gen") +public class GenConfig +{ + /** 作者 */ + public static String author; + + /** 生成包路径 */ + public static String packageName; + + /** 自动去除表前缀,默认是false */ + public static boolean autoRemovePre; + + /** 表前缀(类名不会包含表前缀) */ + public static String tablePrefix; + + public static String getAuthor() + { + return author; + } + + public void setAuthor(String author) + { + GenConfig.author = author; + } + + public static String getPackageName() + { + return packageName; + } + + public void setPackageName(String packageName) + { + GenConfig.packageName = packageName; + } + + public static boolean getAutoRemovePre() + { + return autoRemovePre; + } + + public void setAutoRemovePre(boolean autoRemovePre) + { + GenConfig.autoRemovePre = autoRemovePre; + } + + public static String getTablePrefix() + { + return tablePrefix; + } + + public void setTablePrefix(String tablePrefix) + { + GenConfig.tablePrefix = tablePrefix; + } +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/constant/GenConstants.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/constant/GenConstants.java new file mode 100644 index 0000000..f68cf69 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/constant/GenConstants.java @@ -0,0 +1,129 @@ +package com.ruoyi.generator.constant; + +/** + * 代码生成通用常量 + * + * @author ruoyi + */ +public class GenConstants +{ + /** 单表(增删改查) */ + public static final String TPL_CRUD = "crud"; + + /** 树表(增删改查) */ + public static final String TPL_TREE = "tree"; + + /** 主子表(增删改查) */ + public static final String TPL_SUB = "sub"; + + /** 树编码字段 */ + public static final String TREE_CODE = "treeCode"; + + /** 树父编码字段 */ + public static final String TREE_PARENT_CODE = "treeParentCode"; + + /** 树名称字段 */ + public static final String TREE_NAME = "treeName"; + + /** 上级菜单ID字段 */ + public static final String PARENT_MENU_ID = "parentMenuId"; + + /** 上级菜单名称字段 */ + public static final String PARENT_MENU_NAME = "parentMenuName"; + + /** 数据库字符串类型 */ + public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" }; + + /** 数据库文本类型 */ + public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" }; + + /** 数据库时间类型 */ + public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" }; + + /** 数据库数字类型 */ + public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer", + "bit", "bigint", "float", "double", "decimal" }; + + /** 页面不需要编辑字段 */ + public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" }; + + /** 页面不需要显示的列表字段 */ + public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by", + "update_time" }; + + /** 页面不需要查询字段 */ + public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by", + "update_time", "remark" }; + + /** Entity基类字段 */ + public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" }; + + /** Tree基类字段 */ + public static final String[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors", "children" }; + + /** 文本框 */ + public static final String HTML_INPUT = "input"; + + /** 文本域 */ + public static final String HTML_TEXTAREA = "textarea"; + + /** 下拉框 */ + public static final String HTML_SELECT = "select"; + + /** 单选框 */ + public static final String HTML_RADIO = "radio"; + + /** 复选框 */ + public static final String HTML_CHECKBOX = "checkbox"; + + /** 日期控件 */ + public static final String HTML_DATE = "date"; + + /** 时间控件 */ + public static final String HTML_TIME = "time"; + + /** 日期时间控件 */ + public static final String HTML_DATETIME = "datetime"; + + /** 图片上传控件 */ + public static final String HTML_IMAGE_UPLOAD = "imageUpload"; + + /** 文件上传控件 */ + public static final String HTML_FILE_UPLOAD = "fileUpload"; + + /** 富文本控件 */ + public static final String HTML_EDITOR = "editor"; + + /** 字符串类型 */ + public static final String TYPE_STRING = "String"; + + /** 整型 */ + public static final String TYPE_INTEGER = "Integer"; + + /** 长整型 */ + public static final String TYPE_LONG = "Long"; + + /** 浮点型 */ + public static final String TYPE_DOUBLE = "Double"; + + /** 高精度计算类型 */ + public static final String TYPE_BIGDECIMAL = "BigDecimal"; + + /** 日期类型 */ + public static final String TYPE_DATE = "LocalDate"; + + /** 时间类型 */ + public static final String TYPE_TIME = "LocalTime"; + + /** 日期时间类型 */ + public static final String TYPE_DATETIME = "LocalDateTime"; + + /** 模糊查询 */ + public static final String QUERY_LIKE = "LIKE"; + + /** 相等查询 */ + public static final String QUERY_EQ = "EQ"; + + /** 需要 */ + public static final String REQUIRE = "1"; +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java new file mode 100644 index 0000000..4fa3253 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java @@ -0,0 +1,265 @@ +package com.ruoyi.generator.controller; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.sql.SqlUtil; +import com.ruoyi.generator.domain.GenColumn; +import com.ruoyi.generator.domain.GenJoin; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.generator.domain.vo.GenTableVo; +import com.ruoyi.generator.service.IGenJoinService; +import com.ruoyi.generator.service.IGenColumnService; +import com.ruoyi.generator.service.IGenTableService; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 代码生成 操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/tool/gen") +public class GenController extends BaseController { + @Autowired + private IGenTableService genTableService; + + @Autowired + private IGenColumnService genTableColumnService; + + @Autowired + private IGenJoinService genJoinTableService; + + /** + * 查询代码生成列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/list") + public TableDataInfo genList(GenTable genTable) { + startPage(); + List list = genTableService.selectGenTableList(genTable); + return getDataTable(list); + } + + /** + * 获取代码生成信息 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:query')") + @GetMapping(value = "/{tableId}") + public AjaxResult getInfo(@PathVariable(name = "tableId") Long tableId) { + GenTable table = genTableService.selectGenTableById(tableId); + GenTableVo genTableVo = new GenTableVo(); + genTableVo.setTable(table); + genTableVo.setColumns(table.getColumns()); + GenJoin genJoinTable = new GenJoin(); + genJoinTable.setTableId(tableId); + List selectGenJoinTableList = genJoinTableService.selectGenJoinTableList(genJoinTable); + genTableVo.setJoinTablesMate(selectGenJoinTableList); + Map joinTableMap = new HashMap(); + joinTableMap.put(tableId, table); + selectGenJoinTableList.forEach(i -> { + if (Objects.isNull(joinTableMap.get(i.getLeftTableId()))) { + joinTableMap.put(i.getLeftTableId(), genTableService.selectGenTableById(i.getLeftTableId())); + } + if (Objects.isNull(joinTableMap.get(i.getRightTableId()))) { + joinTableMap.put(i.getRightTableId(), genTableService.selectGenTableById(i.getRightTableId())); + } + }); + genTableVo.setJoinTables(joinTableMap.values()); + return success(genTableVo); + } + + /** + * 查询数据库列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/db/list") + public TableDataInfo dataList(GenTable genTable) { + startPage(); + List list = genTableService.selectDbTableList(genTable); + return getDataTable(list); + } + + /** + * 查询数据表字段列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping(value = "/column/{tableId}") + public TableDataInfo columnList(@PathVariable(name = "tableId") Long tableId) { + TableDataInfo dataInfo = new TableDataInfo(); + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + dataInfo.setRows(list); + dataInfo.setTotal(list.size()); + return dataInfo; + } + + /** + * 导入表结构(保存) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:import')") + @Log(title = "代码生成", businessType = BusinessType.IMPORT) + @PostMapping("/importTable") + public AjaxResult importTableSave(@RequestParam("tables") String tables) { + String[] tableNames = Convert.toStrArray(tables); + // 查询表信息 + List tableList = genTableService.selectDbTableListByNames(tableNames); + genTableService.importGenTable(tableList, SecurityUtils.getUsername()); + return success(); + } + + /** + * 创建表结构(保存) + */ + @PreAuthorize("@ss.hasRole('admin')") + @Log(title = "创建表", businessType = BusinessType.OTHER) + @PostMapping("/createTable") + public AjaxResult createTableSave(String sql) { + try { + SqlUtil.filterKeyword(sql); + List sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql); + List tableNames = new ArrayList<>(); + for (SQLStatement sqlStatement : sqlStatements) { + if (sqlStatement instanceof MySqlCreateTableStatement) { + MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) sqlStatement; + if (genTableService.createTable(createTableStatement.toString())) { + String tableName = createTableStatement.getTableName().replaceAll("`", ""); + tableNames.add(tableName); + } + } + } + List tableList = genTableService + .selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()])); + String operName = SecurityUtils.getUsername(); + genTableService.importGenTable(tableList, operName); + return AjaxResult.success(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + return AjaxResult.error("创建表结构异常"); + } + } + + /** + * 修改保存代码生成业务 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult editSave(@Validated @RequestBody GenTableVo genTableVo) { + GenTable genTable = genTableVo.getTable(); + genTableService.validateEdit(genTable); + genTableService.updateGenTable(genTable); + genJoinTableService.deleteGenJoinTableByTableId(genTable.getTableId()); + genTableVo.getJoinTablesMate().forEach(i -> genJoinTableService.insertGenJoinTable(i)); + return success(); + } + + /** + * 删除代码生成 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:remove')") + @Log(title = "代码生成", businessType = BusinessType.DELETE) + @DeleteMapping("/{tableIds}") + public AjaxResult remove(@PathVariable(name = "tableIds") Long[] tableIds) { + genTableService.deleteGenTableByIds(tableIds); + return success(); + } + + /** + * 预览代码 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:preview')") + @GetMapping("/preview/{tableId}") + public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException { + Map dataMap = genTableService.previewCode(tableId); + return success(dataMap); + } + + /** + * 生成代码(下载方式) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/download/{tableName}") + public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException { + byte[] data = genTableService.downloadCode(tableName); + genCode(response, data); + } + + /** + * 生成代码(自定义路径) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/genCode/{tableName}") + public AjaxResult genCode(@PathVariable("tableName") String tableName) { + genTableService.generatorCode(tableName); + return success(); + } + + /** + * 同步数据库 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @GetMapping("/synchDb/{tableName}") + public AjaxResult synchDb(@PathVariable("tableName") String tableName) { + genTableService.synchDb(tableName); + return success(); + } + + /** + * 批量生成代码 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/batchGenCode") + public void batchGenCode(HttpServletResponse response, @RequestParam(name = "tables") String tables) + throws IOException { + String[] tableNames = Convert.toStrArray(tables); + byte[] data = genTableService.downloadCode(tableNames); + genCode(response, data); + } + + /** + * 生成zip文件 + */ + private void genCode(HttpServletResponse response, byte[] data) throws IOException { + response.reset(); + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); + response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\""); + response.addHeader("Content-Length", "" + data.length); + response.setContentType("application/octet-stream; charset=UTF-8"); + IOUtils.write(data, response.getOutputStream()); + } +} \ No newline at end of file diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenColumn.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenColumn.java new file mode 100644 index 0000000..a14a780 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenColumn.java @@ -0,0 +1,194 @@ +package com.ruoyi.generator.domain; + +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.StringUtils; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 代码生成业务字段表 gen_table_column + * + * @author ruoyi + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class GenColumn extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 编号 */ + private Long columnId; + + /** 归属表编号 */ + private Long tableId; + + /** 列名称 */ + private String columnName; + + /** 列描述 */ + private String columnComment; + + /** 列类型 */ + private String columnType; + + /** JAVA类型 */ + private String javaType; + + /** JAVA字段名 */ + @NotBlank(message = "Java属性不能为空") + private String javaField; + + /** 是否主键(1是) */ + private String isPk; + + /** 是否自增(1是) */ + private String isIncrement; + + /** 是否必填(1是) */ + private String isRequired; + + /** 是否为插入字段(1是) */ + private String isInsert; + + /** 是否编辑字段(1是) */ + private String isEdit; + + /** 是否列表字段(1是) */ + private String isList; + + /** 是否查询字段(1是) */ + private String isQuery; + + /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */ + private String queryType; + + /** + * 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) + */ + private String htmlType; + + /** 字典类型 */ + private String dictType; + + /** 排序 */ + private Integer sort; + + /** 关联表名称 */ + @Deprecated + private String subColumnTableName; + + /** 关联字段名称 */ + @Deprecated + private String subColumnFkName; + + /** 映射字段名称 */ + @Deprecated + private String subColumnName; + + /** 映射字段Java字段名 */ + @Deprecated + private String subColumnJavaField; + + /** 映射字段Java类型 */ + @Deprecated + private String subColumnJavaType; + + public String getCapJavaField() { + return StringUtils.capitalize(javaField); + } + + public boolean isPk() { + return isPk(this.isPk); + } + + public boolean isPk(String isPk) { + return isPk != null && StringUtils.equals("1", isPk); + } + + public boolean isIncrement() { + return isIncrement(this.isIncrement); + } + + public boolean isIncrement(String isIncrement) { + return isIncrement != null && StringUtils.equals("1", isIncrement); + } + + public boolean isRequired() { + return isRequired(this.isRequired); + } + + public boolean isRequired(String isRequired) { + return isRequired != null && StringUtils.equals("1", isRequired); + } + + public boolean isInsert() { + return isInsert(this.isInsert); + } + + public boolean isInsert(String isInsert) { + return isInsert != null && StringUtils.equals("1", isInsert); + } + + public boolean isEdit() { + return isInsert(this.isEdit); + } + + public boolean isEdit(String isEdit) { + return isEdit != null && StringUtils.equals("1", isEdit); + } + + public boolean isList() { + return isList(this.isList); + } + + public boolean isList(String isList) { + return isList != null && StringUtils.equals("1", isList); + } + + public boolean isQuery() { + return isQuery(this.isQuery); + } + + public boolean isQuery(String isQuery) { + return isQuery != null && StringUtils.equals("1", isQuery); + } + + public boolean isSuperColumn() { + return isSuperColumn(this.javaField); + } + + public static boolean isSuperColumn(String javaField) { + return StringUtils.equalsAnyIgnoreCase(javaField, + // BaseEntity + "createBy", "createTime", "updateBy", "updateTime", "remark", + // TreeEntity + "parentName", "parentId", "orderNum", "ancestors"); + } + + public boolean isUsableColumn() { + return isUsableColumn(javaField); + } + + public static boolean isUsableColumn(String javaField) { + // isSuperColumn()中的名单用于避免生成多余Domain属性,若某些属性在生成页面时需要用到不能忽略,则放在此处白名单 + return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark"); + } + + public String readConverterExp() { + String remarks = StringUtils.substringBetween(this.columnComment, "(", ")"); + StringBuffer sb = new StringBuffer(); + if (StringUtils.isNotEmpty(remarks)) { + for (String value : remarks.split(" ")) { + if (StringUtils.isNotEmpty(value)) { + Object startStr = value.subSequence(0, 1); + String endStr = value.substring(1); + sb.append("").append(startStr).append("=").append(endStr).append(","); + } + } + return sb.deleteCharAt(sb.length() - 1).toString(); + } else { + return this.columnComment; + } + } +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenJoin.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenJoin.java new file mode 100644 index 0000000..28c5d40 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenJoin.java @@ -0,0 +1,46 @@ +package com.ruoyi.generator.domain; + +import java.util.List; + +import com.ruoyi.common.core.domain.BaseEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class GenJoin extends BaseEntity { + + /** 表编号 */ + private Long tableId; + + /** 关联左表编号 */ + private Long leftTableId; + + /** 关联右表编号 */ + private Long rightTableId; + + /** 关联关系中新引入的表 */ + private Long newTableId; + + /** 关联左表别名 */ + private String leftTableAlias; + + /** 关联右表别名 */ + private String rightTableAlias; + + /** 关联左表外键 */ + private Long leftTableFk; + + /** 关联右表外键 */ + private Long rightTableFk; + + /** 关联类型 */ + private String joinType; + + /** 关联字段 */ + private List joinColumns; + + /** 关联顺序 */ + private Long orderNum; +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java new file mode 100644 index 0000000..291621d --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java @@ -0,0 +1,149 @@ +package com.ruoyi.generator.domain; + +import java.util.List; + +import org.apache.commons.lang3.ArrayUtils; + +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.generator.constant.GenConstants; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 业务表 gen_table + * + * @author ruoyi + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class GenTable extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 编号 */ + private Long tableId; + + /** 表名称 */ + @NotBlank(message = "表名称不能为空") + private String tableName; + + /** 表别名 */ + private String tableAlias; + + /** 表描述 */ + @NotBlank(message = "表描述不能为空") + private String tableComment; + + /** 关联父表的表名 */ + private String subTableName; + + /** 本表关联父表的外键名 */ + private String subTableFkName; + + /** 实体类名称(首字母大写) */ + @NotBlank(message = "实体类名称不能为空") + private String className; + + /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */ + private String tplCategory; + + /** 前端类型(element-ui模版 element-plus模版) */ + private String tplWebType; + + /** 生成包路径 */ + @NotBlank(message = "生成包路径不能为空") + private String packageName; + + /** 生成模块名 */ + @NotBlank(message = "生成模块名不能为空") + private String moduleName; + + /** 生成业务名 */ + @NotBlank(message = "生成业务名不能为空") + private String businessName; + + /** 生成功能名 */ + @NotBlank(message = "生成功能名不能为空") + private String functionName; + + /** 生成作者 */ + @NotBlank(message = "作者不能为空") + private String functionAuthor; + + /** 生成代码方式(0zip压缩包 1自定义路径) */ + private String genType; + + /** 生成路径(不填默认项目路径) */ + private String genPath; + + /** 主键信息 */ + private GenColumn pkColumn; + + /** 子表信息 */ + private GenTable subTable; + + /** 表列信息 */ + @Valid + private List columns; + + /** 其它生成选项 */ + private String options; + + /** 树编码字段 */ + private String treeCode; + + /** 树父编码字段 */ + private String treeParentCode; + + /** 树名称字段 */ + private String treeName; + + /** 上级菜单ID字段 */ + private String parentMenuId; + + /** 上级菜单名称字段 */ + private String parentMenuName; + + /** 是否含有关联字段 */ + @Deprecated + private String haveSubColumn; + + public boolean isSub() { + return isSub(this.tplCategory); + } + + public static boolean isSub(String tplCategory) { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory); + } + + public boolean isTree() { + return isTree(this.tplCategory); + } + + public static boolean isTree(String tplCategory) { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory); + } + + public boolean isCrud() { + return isCrud(this.tplCategory); + } + + public static boolean isCrud(String tplCategory) { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory); + } + + public boolean isSuperColumn(String javaField) { + return isSuperColumn(this.tplCategory, javaField); + } + + public static boolean isSuperColumn(String tplCategory, String javaField) { + if (isTree(tplCategory)) { + return StringUtils.equalsAnyIgnoreCase(javaField, + ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY)); + } + return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY); + } +} \ No newline at end of file diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/vo/GenTableVo.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/vo/GenTableVo.java new file mode 100644 index 0000000..5473577 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/vo/GenTableVo.java @@ -0,0 +1,113 @@ +package com.ruoyi.generator.domain.vo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.generator.domain.GenColumn; +import com.ruoyi.generator.domain.GenJoin; +import com.ruoyi.generator.domain.GenTable; + +import jakarta.validation.Valid; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Setter; + +/** + * 业务表 gen_table + * + * @author ruoyi + */ +@Data +@Setter +@EqualsAndHashCode(callSuper = true) +public class GenTableVo extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 业务表 */ + @Valid + private GenTable table; + + /** 业务表的列 */ + @Valid + private List columns; + + /** 关联信息 */ + @Valid + private List joinTablesMate; + + /** 参与关联的表 */ + private Collection joinTables; + + /** 参与关联的列 */ + private List joinColumns; + + /** 获取所有与本业务相关的表 */ + public List getAllGenTables() { + List allGenTables = new ArrayList<>(); + allGenTables.add(table); + allGenTables.addAll(joinTables); + return allGenTables; + } + + /** 获取所有与本业务相关的列 */ + public List getAllGenTableColumns() { + List allGenTableColumns = new ArrayList<>(); + if (columns != null) { + allGenTableColumns.addAll(columns); + } + if (joinColumns != null) { + allGenTableColumns.addAll(joinColumns); + } + return allGenTableColumns; + } + + /** 获取所有与本业务相关表的表ID与表对象映射 */ + public Map getTableMap() { + Map tableMap = new HashMap<>(); + if (table != null) { + tableMap.put(table.getTableId(), table); + } + if (joinTables != null) { + for (GenTable genTable : joinTables) { + if (genTable != null) { + tableMap.put(genTable.getTableId(), genTable); + } + } + } + return tableMap; + } + + /** 获取所有与本业务相关表的表ID与表别名映射 */ + public Map getTableAliasMap() { + Map tableMap = new HashMap<>(); + if (table != null) { + tableMap.put(table.getTableId(), table.getTableAlias()); + } + if (joinTablesMate != null) { + for (GenJoin genTable : joinTablesMate) { + if (genTable != null) { + tableMap.put(genTable.getLeftTableId(), genTable.getLeftTableAlias()); + tableMap.put(genTable.getRightTableId(), genTable.getRightTableAlias()); + } + } + } + return tableMap; + } + + /** 获取所有与本业务相关列的列ID与列对象映射 */ + public Map getColumnMap() { + Map columnMap = new HashMap<>(); + List genTables = getAllGenTables(); + for (GenTable genTable : genTables) { + for (GenColumn genTableColumn : genTable.getColumns()) { + columnMap.put(genTableColumn.getColumnId(), genTableColumn); + } + } + return columnMap; + } + +} \ No newline at end of file diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenColumnMapper.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenColumnMapper.java new file mode 100644 index 0000000..31fa609 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenColumnMapper.java @@ -0,0 +1,61 @@ +package com.ruoyi.generator.mapper; + +import java.util.List; + +import com.ruoyi.generator.domain.GenColumn; + +/** + * 业务字段 数据层 + * + * @author ruoyi + */ +public interface GenColumnMapper +{ + /** + * 根据表名称查询列信息 + * + * @param tableName 表名称 + * @return 列信息 + */ + public List selectDbTableColumnsByName(String tableName); + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenColumn genTableColumn); + + /** + * 删除业务字段 + * + * @param genTableColumns 列数据 + * @return 结果 + */ + public int deleteGenTableColumns(List genTableColumns); + + /** + * 批量删除业务字段 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(Long[] ids); +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenJoinMapper.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenJoinMapper.java new file mode 100644 index 0000000..495faf3 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenJoinMapper.java @@ -0,0 +1,40 @@ +package com.ruoyi.generator.mapper; + +import java.util.List; + +import com.ruoyi.generator.domain.GenJoin; + +/** + * 代码生成关联字段Mapper接口 + * + * @author ruoyi + * @date 2025-02-19 + */ +public interface GenJoinMapper { + /** + * 查询代码生成关联字段列表 + * + * @param genJoinTable 代码生成关联字段 + * @return 代码生成关联字段集合 + */ + public List selectGenJoinTableList(GenJoin genJoinTable); + + /** + * 新增代码生成关联字段 + * + * @param genJoinTable 代码生成关联字段 + * @return 结果 + */ + public int insertGenJoinTable(GenJoin genJoinTable); + + /** + * 修改代码生成关联字段 + * + * @param genJoinTable 代码生成关联字段 + * @return 结果 + */ + public int updateGenJoinTable(GenJoin genJoinTable); + + public int deleteGenJoinTableByTableId(Long tableId); + +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java new file mode 100644 index 0000000..9bc3ab5 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java @@ -0,0 +1,90 @@ +package com.ruoyi.generator.mapper; + +import java.util.List; +import com.ruoyi.generator.domain.GenTable; + +/** + * 业务 数据层 + * + * @author ruoyi + */ +public interface GenTableMapper { + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询表ID业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 查询表名称业务信息 + * + * @param tableName 表名称 + * @return 业务信息 + */ + public GenTable selectGenTableByName(String tableName); + + /** + * 新增业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int insertGenTable(GenTable genTable); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int updateGenTable(GenTable genTable); + + /** + * 批量删除业务 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableByIds(Long[] ids); + + /** + * 创建表 + * + * @param sql 表结构 + * @return 结果 + */ + public int createTable(String sql); +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenColumnService.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenColumnService.java new file mode 100644 index 0000000..ab61158 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenColumnService.java @@ -0,0 +1,45 @@ +package com.ruoyi.generator.service; + +import java.util.List; + +import com.ruoyi.generator.domain.GenColumn; + +/** + * 业务字段 服务层 + * + * @author ruoyi + */ +public interface IGenColumnService +{ + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenColumn genTableColumn); + + /** + * 删除业务字段信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(String ids); +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenJoinService.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenJoinService.java new file mode 100644 index 0000000..e193719 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenJoinService.java @@ -0,0 +1,46 @@ +package com.ruoyi.generator.service; + +import java.util.List; + +import com.ruoyi.generator.domain.GenJoin; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.generator.domain.vo.GenTableVo; + +/** + * 代码生成关联字段Service接口 + * + * @author ruoyi + * @date 2025-02-19 + */ +public interface IGenJoinService { + /** + * 查询代码生成关联字段列表 + * + * @param genJoinTable 代码生成关联字段 + * @return 代码生成关联字段集合 + */ + public List selectGenJoinTableList(GenJoin genJoinTable); + + /** + * 新增代码生成关联字段 + * + * @param genJoinTable 代码生成关联字段 + * @return 结果 + */ + public int insertGenJoinTable(GenJoin genJoinTable); + + /** + * 修改代码生成关联字段 + * + * @param genJoinTable 代码生成关联字段 + * @return 结果 + */ + public int updateGenJoinTable(GenJoin genJoinTable); + + /** + * 根据tableId删除字段关联 + */ + public int deleteGenJoinTableByTableId(Long tableId); + + public GenTableVo selectGenJoinTableVoListByGenTable(GenTable table); +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java new file mode 100644 index 0000000..388309f --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java @@ -0,0 +1,129 @@ +package com.ruoyi.generator.service; + +import java.util.List; +import java.util.Map; + +import com.ruoyi.generator.domain.GenTable; + +/** + * 业务 服务层 + * + * @author ruoyi + */ +public interface IGenTableService { + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public void updateGenTable(GenTable genTable); + + /** + * 删除业务信息 + * + * @param tableIds 需要删除的表数据ID + * @return 结果 + */ + public void deleteGenTableByIds(Long[] tableIds); + + /** + * 创建表 + * + * @param sql 创建表语句 + * @return 结果 + */ + public boolean createTable(String sql); + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + */ + public void importGenTable(List tableList, String operName); + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + public Map previewCode(Long tableId); + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + public byte[] downloadCode(String tableName); + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + * @return 数据 + */ + public void generatorCode(String tableName); + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + public void synchDb(String tableName); + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + public byte[] downloadCode(String[] tableNames); + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + public void validateEdit(GenTable genTable); +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenColumnServiceImpl.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenColumnServiceImpl.java new file mode 100644 index 0000000..dc8195f --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenColumnServiceImpl.java @@ -0,0 +1,71 @@ +package com.ruoyi.generator.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.generator.domain.GenColumn; +import com.ruoyi.generator.mapper.GenColumnMapper; +import com.ruoyi.generator.service.IGenColumnService; + +/** + * 业务字段 服务层实现 + * + * @author ruoyi + */ +@Service +public class GenColumnServiceImpl implements IGenColumnService +{ + @Autowired + private GenColumnMapper genTableColumnMapper; + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + @Override + public List selectGenTableColumnListByTableId(Long tableId) + { + return genTableColumnMapper.selectGenTableColumnListByTableId(tableId); + } + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + @Override + public int insertGenTableColumn(GenColumn genTableColumn) + { + return genTableColumnMapper.insertGenTableColumn(genTableColumn); + } + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + @Override + public int updateGenTableColumn(GenColumn genTableColumn) + { + return genTableColumnMapper.updateGenTableColumn(genTableColumn); + } + + /** + * 删除业务字段对象 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteGenTableColumnByIds(String ids) + { + return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids)); + } +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenJoinServiceImpl.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenJoinServiceImpl.java new file mode 100644 index 0000000..0cb6843 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenJoinServiceImpl.java @@ -0,0 +1,146 @@ +package com.ruoyi.generator.service.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.generator.constant.GenConstants; +import com.ruoyi.generator.domain.GenColumn; +import com.ruoyi.generator.domain.GenJoin; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.generator.domain.vo.GenTableVo; +import com.ruoyi.generator.mapper.GenJoinMapper; +import com.ruoyi.generator.mapper.GenTableMapper; +import com.ruoyi.generator.service.IGenJoinService; + +/** + * 代码生成关联字段Service业务层处理 + * + * @author ruoyi + * @date 2025-02-19 + */ +@Service +public class GenJoinServiceImpl implements IGenJoinService { + @Autowired + private GenJoinMapper genJoinTableMapper; + + @Autowired + private GenTableMapper genTableMapper; + + /** + * 查询代码生成关联字段列表 + * + * @param genJoinTable 代码生成关联字段 + * @return 代码生成关联字段 + */ + @Override + public List selectGenJoinTableList(GenJoin genJoinTable) { + return genJoinTableMapper.selectGenJoinTableList(genJoinTable); + } + + public GenTable selectGenTableById(Long id) { + GenTable genTable = genTableMapper.selectGenTableById(id); + setTableFromOptions(genTable); + return genTable; + } + + /** + * 设置代码生成其他选项值 + * + * @param genTable 设置后的生成对象 + */ + public void setTableFromOptions(GenTable genTable) { + JSONObject paramsObj = JSON.parseObject(genTable.getOptions()); + if (StringUtils.isNotNull(paramsObj)) { + String treeCode = paramsObj.getString(GenConstants.TREE_CODE); + String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID); + String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); + + genTable.setTreeCode(treeCode); + genTable.setTreeParentCode(treeParentCode); + genTable.setTreeName(treeName); + genTable.setParentMenuId(parentMenuId); + genTable.setParentMenuName(parentMenuName); + } + } + + @Override + public GenTableVo selectGenJoinTableVoListByGenTable(GenTable table) { + GenTableVo genTableVo = new GenTableVo(); + genTableVo.setTable(table); + genTableVo.setColumns(table.getColumns()); + + GenJoin genJoinTable = new GenJoin(); + genJoinTable.setTableId(table.getTableId()); + List selectGenJoinTableList = this.selectGenJoinTableList(genJoinTable); + genTableVo.setJoinTablesMate(selectGenJoinTableList); + + List joinColumns = new ArrayList(); + Map joinTableMap = new HashMap(); + joinTableMap.put(table.getTableId(), table); + selectGenJoinTableList.forEach(i -> { + if (Objects.isNull(joinTableMap.get(i.getLeftTableId()))) { + joinTableMap.put(i.getLeftTableId(), this.selectGenTableById(i.getLeftTableId())); + } + if (Objects.isNull(joinTableMap.get(i.getRightTableId()))) { + joinTableMap.put(i.getRightTableId(), this.selectGenTableById(i.getRightTableId())); + } + GenTable newTable = joinTableMap.get(i.getNewTableId()); + if(Objects.isNull(newTable)) throw new ServiceException("关联表不存在"); + List joinColumnNames = i.getJoinColumns(); + if(Objects.isNull(joinColumnNames)) return; + newTable.getColumns().forEach(j -> { + if (joinColumnNames.contains(j.getColumnName())) { + joinColumns.add(j); + } + }); + }); + genTableVo.setJoinColumns(joinColumns); + genTableVo.setJoinTables(joinTableMap.values()); + return genTableVo; + } + + /** + * 新增代码生成关联字段 + * + * @param genJoinTable 代码生成关联字段 + * @return 结果 + */ + @Override + public int insertGenJoinTable(GenJoin genJoinTable) { + genJoinTable.setCreateTime(DateUtils.getNowDate()); + return genJoinTableMapper.insertGenJoinTable(genJoinTable); + } + + /** + * 修改代码生成关联字段 + * + * @param genJoinTable 代码生成关联字段 + * @return 结果 + */ + @Override + public int updateGenJoinTable(GenJoin genJoinTable) { + genJoinTable.setUpdateTime(DateUtils.getNowDate()); + return genJoinTableMapper.updateGenJoinTable(genJoinTable); + } + + /** + * 根据tableId删除字段关联 + */ + public int deleteGenJoinTableByTableId(Long tableId) { + return genJoinTableMapper.deleteGenJoinTableByTableId(tableId); + } + +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableServiceImpl.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableServiceImpl.java new file mode 100644 index 0000000..1203cb6 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableServiceImpl.java @@ -0,0 +1,499 @@ +package com.ruoyi.generator.service.impl; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.text.CharsetKit; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.generator.constant.GenConstants; +import com.ruoyi.generator.domain.GenColumn; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.generator.domain.vo.GenTableVo; +import com.ruoyi.generator.mapper.GenColumnMapper; +import com.ruoyi.generator.mapper.GenTableMapper; +import com.ruoyi.generator.service.IGenJoinService; +import com.ruoyi.generator.service.IGenTableService; +import com.ruoyi.generator.util.GenUtils; +import com.ruoyi.generator.util.VelocityInitializer; +import com.ruoyi.generator.util.VelocityUtils; + +/** + * 业务 服务层实现 + * + * @author ruoyi + */ +@Service +public class GenTableServiceImpl implements IGenTableService { + private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class); + + @Autowired + private GenTableMapper genTableMapper; + + @Autowired + private GenColumnMapper genTableColumnMapper; + + @Autowired + private IGenJoinService genJoinTableService; + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + @Override + public GenTable selectGenTableById(Long id) { + GenTable genTable = genTableMapper.selectGenTableById(id); + setTableFromOptions(genTable); + return genTable; + } + + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + @Override + public List selectGenTableList(GenTable genTable) { + return genTableMapper.selectGenTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + @Override + public List selectDbTableList(GenTable genTable) { + return genTableMapper.selectDbTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + @Override + public List selectDbTableListByNames(String[] tableNames) { + List genTables = genTableMapper.selectDbTableListByNames(tableNames); + genTables.forEach(i -> i.setTableAlias(generateTableAlias(i.getTableName()))); + return genTables; + } + + public static String generateTableAlias(String tableName) { + if (StringUtils.isEmpty(tableName)) { + return "t"; + } + + // 改进的正则表达式,更准确地处理所有三种情况 + Pattern pattern = Pattern.compile("([A-Z][a-z0-9]*)|([a-z0-9]+)(?=[A-Z])|([a-z0-9]+)(?=_)|([a-z0-9]+)$"); + Matcher matcher = pattern.matcher(tableName); + StringBuilder alias = new StringBuilder(); + + while (matcher.find()) { + String word = matcher.group(); + if (!word.isEmpty()) { + alias.append(word.charAt(0)); + } + } + + return alias.toString().toLowerCase(); + } + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + @Override + public List selectGenTableAll() { + return genTableMapper.selectGenTableAll(); + } + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + @Override + @Transactional + public void updateGenTable(GenTable genTable) { + String options = JSON.toJSONString(genTable.getParams()); + genTable.setOptions(options); + int row = genTableMapper.updateGenTable(genTable); + if (row > 0) { + for (GenColumn cenTableColumn : genTable.getColumns()) { + genTableColumnMapper.updateGenTableColumn(cenTableColumn); + } + } + } + + /** + * 删除业务对象 + * + * @param tableIds 需要删除的数据ID + * @return 结果 + */ + @Override + @Transactional + public void deleteGenTableByIds(Long[] tableIds) { + genTableMapper.deleteGenTableByIds(tableIds); + genTableColumnMapper.deleteGenTableColumnByIds(tableIds); + } + + /** + * 创建表 + * + * @param sql 创建表语句 + * @return 结果 + */ + @Override + public boolean createTable(String sql) { + return genTableMapper.createTable(sql) == 0; + } + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + */ + @Override + @Transactional + public void importGenTable(List tableList, String operName) { + try { + for (GenTable table : tableList) { + String tableName = table.getTableName(); + GenUtils.initTable(table, operName); + int row = genTableMapper.insertGenTable(table); + if (row > 0) { + // 保存列信息 + List genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + for (GenColumn column : genTableColumns) { + GenUtils.initColumnField(column, table); + genTableColumnMapper.insertGenTableColumn(column); + } + } + } + } catch (Exception e) { + throw new ServiceException("导入失败:" + e.getMessage()); + } + } + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + @Override + public Map previewCode(Long tableId) { + Map dataMap = new LinkedHashMap<>(); + // 查询表信息 + GenTable table = genTableMapper.selectGenTableById(tableId); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + VelocityInitializer.initVelocity(); + GenTableVo genTableVo = genJoinTableService.selectGenJoinTableVoListByGenTable(table); + + VelocityContext context = VelocityUtils.prepareContext(genTableVo); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); + for (String template : templates) { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + dataMap.put(template, sw.toString()); + } + return dataMap; + } + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + @Override + public byte[] downloadCode(String tableName) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + generatorCode(tableName, zip); + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + */ + @Override + public void generatorCode(String tableName) { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + VelocityInitializer.initVelocity(); + GenTableVo genTableVo = genJoinTableService.selectGenJoinTableVoListByGenTable(table); + + VelocityContext context = VelocityUtils.prepareContext(genTableVo); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); + for (String template : templates) { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try { + String path = getGenPath(table, template); + FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8); + } catch (IOException e) { + throw new ServiceException("渲染模板失败,表名:" + table.getTableName()); + } + } + } + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + @Override + @Transactional + public void synchDb(String tableName) { + GenTable table = genTableMapper.selectGenTableByName(tableName); + List tableColumns = table.getColumns(); + Map tableColumnMap = tableColumns.stream() + .collect(Collectors.toMap(GenColumn::getColumnName, Function.identity())); + + List dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + if (StringUtils.isEmpty(dbTableColumns)) { + throw new ServiceException("同步数据失败,原表结构不存在"); + } + List dbTableColumnNames = dbTableColumns.stream().map(GenColumn::getColumnName) + .collect(Collectors.toList()); + + dbTableColumns.forEach(column -> { + GenUtils.initColumnField(column, table); + if (tableColumnMap.containsKey(column.getColumnName())) { + GenColumn prevColumn = tableColumnMap.get(column.getColumnName()); + column.setColumnId(prevColumn.getColumnId()); + if (column.isList()) { + // 如果是列表,继续保留查询方式/字典类型选项 + column.setDictType(prevColumn.getDictType()); + column.setQueryType(prevColumn.getQueryType()); + } + if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk() + && (column.isInsert() || column.isEdit()) + && ((column.isUsableColumn()) || (!column.isSuperColumn()))) { + // 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项 + column.setIsRequired(prevColumn.getIsRequired()); + column.setHtmlType(prevColumn.getHtmlType()); + } + genTableColumnMapper.updateGenTableColumn(column); + } else { + genTableColumnMapper.insertGenTableColumn(column); + } + }); + + List delColumns = tableColumns.stream() + .filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList()); + if (StringUtils.isNotEmpty(delColumns)) { + genTableColumnMapper.deleteGenTableColumns(delColumns); + } + } + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + @Override + public byte[] downloadCode(String[] tableNames) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + for (String tableName : tableNames) { + generatorCode(tableName, zip); + } + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 查询表信息并生成代码 + */ + private void generatorCode(String tableName, ZipOutputStream zip) { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + VelocityInitializer.initVelocity(); + GenTableVo genTableVo = genJoinTableService.selectGenJoinTableVoListByGenTable(table); + VelocityContext context = VelocityUtils.prepareContext(genTableVo); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); + for (String template : templates) { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try { + // 添加到zip + zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table))); + IOUtils.write(sw.toString(), zip, Constants.UTF8); + IOUtils.closeQuietly(sw); + zip.flush(); + zip.closeEntry(); + } catch (IOException e) { + log.error("渲染模板失败,表名:" + table.getTableName(), e); + } + } + } + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + @Override + public void validateEdit(GenTable genTable) { + if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) { + String options = JSON.toJSONString(genTable.getParams()); + JSONObject paramsObj = JSON.parseObject(options); + if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) { + throw new ServiceException("树编码字段不能为空"); + } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) { + throw new ServiceException("树父编码字段不能为空"); + } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) { + throw new ServiceException("树名称字段不能为空"); + } else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) { + if (StringUtils.isEmpty(genTable.getSubTableName())) { + throw new ServiceException("关联子表的表名不能为空"); + } else if (StringUtils.isEmpty(genTable.getSubTableFkName())) { + throw new ServiceException("子表关联的外键名不能为空"); + } + } + } + } + + /** + * 设置主键列信息 + * + * @param table 业务表信息 + */ + public void setPkColumn(GenTable table) { + for (GenColumn column : table.getColumns()) { + if (column.isPk()) { + table.setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getPkColumn())) { + table.setPkColumn(table.getColumns().get(0)); + } + if (GenConstants.TPL_SUB.equals(table.getTplCategory())) { + for (GenColumn column : table.getSubTable().getColumns()) { + if (column.isPk()) { + table.getSubTable().setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getSubTable().getPkColumn())) { + table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0)); + } + } + } + + /** + * 设置主子表信息 + * + * @param table 业务表信息 + */ + public void setSubTable(GenTable table) { + String subTableName = table.getSubTableName(); + if (StringUtils.isNotEmpty(subTableName)) { + table.setSubTable(genTableMapper.selectGenTableByName(subTableName)); + } + } + + /** + * 设置代码生成其他选项值 + * + * @param genTable 设置后的生成对象 + */ + public void setTableFromOptions(GenTable genTable) { + JSONObject paramsObj = JSON.parseObject(genTable.getOptions()); + if (StringUtils.isNotNull(paramsObj)) { + String treeCode = paramsObj.getString(GenConstants.TREE_CODE); + String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID); + String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); + + genTable.setTreeCode(treeCode); + genTable.setTreeParentCode(treeParentCode); + genTable.setTreeName(treeName); + genTable.setParentMenuId(parentMenuId); + genTable.setParentMenuName(parentMenuName); + } + } + + /** + * 获取代码生成地址 + * + * @param table 业务表信息 + * @param template 模板文件路径 + * @return 生成地址 + */ + public static String getGenPath(GenTable table, String template) { + String genPath = table.getGenPath(); + if (StringUtils.equals(genPath, "/")) { + return System.getProperty("user.dir") + File.separator + "src" + File.separator + + VelocityUtils.getFileName(template, table); + } + return genPath + File.separator + VelocityUtils.getFileName(template, table); + } +} \ No newline at end of file diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java new file mode 100644 index 0000000..6514bdb --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java @@ -0,0 +1,259 @@ +package com.ruoyi.generator.util; + +import java.util.Arrays; + +import org.apache.commons.lang3.RegExUtils; + +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.generator.config.GenConfig; +import com.ruoyi.generator.constant.GenConstants; +import com.ruoyi.generator.domain.GenColumn; +import com.ruoyi.generator.domain.GenTable; + +/** + * 代码生成器 工具类 + * + * @author ruoyi + */ +public class GenUtils +{ + /** + * 初始化表信息 + */ + public static void initTable(GenTable genTable, String operName) + { + genTable.setClassName(convertClassName(genTable.getTableName())); + genTable.setPackageName(GenConfig.getPackageName()); + genTable.setModuleName(getModuleName(GenConfig.getPackageName())); + genTable.setBusinessName(getBusinessName(genTable.getTableName())); + genTable.setFunctionName(replaceText(genTable.getTableComment())); + genTable.setFunctionAuthor(GenConfig.getAuthor()); + genTable.setCreateBy(operName); + } + + /** + * 初始化列属性字段 + */ + public static void initColumnField(GenColumn column, GenTable table) + { + String dataType = getDbType(column.getColumnType()); + String columnName = column.getColumnName(); + column.setTableId(table.getTableId()); + column.setCreateBy(table.getCreateBy()); + // 设置java字段名 + column.setJavaField(StringUtils.toCamelCase(columnName)); + // 设置默认类型 + column.setJavaType(GenConstants.TYPE_STRING); + column.setQueryType(GenConstants.QUERY_EQ); + + if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) + { + // 字符串长度超过500设置为文本域 + Integer columnLength = getColumnLength(column.getColumnType()); + String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT; + column.setHtmlType(htmlType); + } + else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) + { + column.setJavaType(GenConstants.TYPE_DATE); + column.setHtmlType(GenConstants.HTML_DATETIME); + } + else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) + { + column.setHtmlType(GenConstants.HTML_INPUT); + + // 如果是浮点型 统一用BigDecimal + String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ","); + if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) + { + column.setJavaType(GenConstants.TYPE_BIGDECIMAL); + } + // 如果是整形 + else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) + { + column.setJavaType(GenConstants.TYPE_INTEGER); + } + // 长整形 + else + { + column.setJavaType(GenConstants.TYPE_LONG); + } + } + + // 插入字段(默认所有字段都需要插入) + column.setIsInsert(GenConstants.REQUIRE); + + // 编辑字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk()) + { + column.setIsEdit(GenConstants.REQUIRE); + } + // 列表字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk()) + { + column.setIsList(GenConstants.REQUIRE); + } + // 查询字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) + { + column.setIsQuery(GenConstants.REQUIRE); + } + + // 查询字段类型 + if (StringUtils.endsWithIgnoreCase(columnName, "name")) + { + column.setQueryType(GenConstants.QUERY_LIKE); + } + // 状态字段设置单选框 + if (StringUtils.endsWithIgnoreCase(columnName, "status")) + { + column.setHtmlType(GenConstants.HTML_RADIO); + } + // 类型&性别字段设置下拉框 + else if (StringUtils.endsWithIgnoreCase(columnName, "type") + || StringUtils.endsWithIgnoreCase(columnName, "sex")) + { + column.setHtmlType(GenConstants.HTML_SELECT); + } + // 图片字段设置图片上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "image")) + { + column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD); + } + // 文件字段设置文件上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "file")) + { + column.setHtmlType(GenConstants.HTML_FILE_UPLOAD); + } + // 内容字段设置富文本控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "content")) + { + column.setHtmlType(GenConstants.HTML_EDITOR); + } + } + + /** + * 校验数组是否包含指定值 + * + * @param arr 数组 + * @param targetValue 值 + * @return 是否包含 + */ + public static boolean arraysContains(String[] arr, String targetValue) + { + return Arrays.asList(arr).contains(targetValue); + } + + /** + * 获取模块名 + * + * @param packageName 包名 + * @return 模块名 + */ + public static String getModuleName(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + int nameLength = packageName.length(); + return StringUtils.substring(packageName, lastIndex + 1, nameLength); + } + + /** + * 获取业务名 + * + * @param tableName 表名 + * @return 业务名 + */ + public static String getBusinessName(String tableName) + { + int lastIndex = tableName.lastIndexOf("_"); + int nameLength = tableName.length(); + return StringUtils.substring(tableName, lastIndex + 1, nameLength); + } + + /** + * 表名转换成Java类名 + * + * @param tableName 表名称 + * @return 类名 + */ + public static String convertClassName(String tableName) + { + boolean autoRemovePre = GenConfig.getAutoRemovePre(); + String tablePrefix = GenConfig.getTablePrefix(); + if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) + { + String[] searchList = StringUtils.split(tablePrefix, ","); + tableName = replaceFirst(tableName, searchList); + } + return StringUtils.convertToCamelCase(tableName); + } + + /** + * 批量替换前缀 + * + * @param replacementm 替换值 + * @param searchList 替换列表 + * @return + */ + public static String replaceFirst(String replacementm, String[] searchList) + { + String text = replacementm; + for (String searchString : searchList) + { + if (replacementm.startsWith(searchString)) + { + text = replacementm.replaceFirst(searchString, ""); + break; + } + } + return text; + } + + /** + * 关键字替换 + * + * @param text 需要被替换的名字 + * @return 替换后的名字 + */ + public static String replaceText(String text) + { + return RegExUtils.replaceAll(text, "(?:表|若依)", ""); + } + + /** + * 获取数据库类型字段 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static String getDbType(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + return StringUtils.substringBefore(columnType, "("); + } + else + { + return columnType; + } + } + + /** + * 获取字段长度 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static Integer getColumnLength(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + String length = StringUtils.substringBetween(columnType, "(", ")"); + return Integer.valueOf(length); + } + else + { + return 0; + } + } +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java new file mode 100644 index 0000000..9f69403 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java @@ -0,0 +1,34 @@ +package com.ruoyi.generator.util; + +import java.util.Properties; +import org.apache.velocity.app.Velocity; +import com.ruoyi.common.constant.Constants; + +/** + * VelocityEngine工厂 + * + * @author ruoyi + */ +public class VelocityInitializer +{ + /** + * 初始化vm方法 + */ + public static void initVelocity() + { + Properties p = new Properties(); + try + { + // 加载classpath目录下的vm文件 + p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + // 定义字符集 + p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8); + // 初始化Velocity引擎,指定配置Properties + Velocity.init(p); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java new file mode 100644 index 0000000..4a4ed90 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java @@ -0,0 +1,369 @@ +package com.ruoyi.generator.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.velocity.VelocityContext; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.generator.constant.GenConstants; +import com.ruoyi.generator.domain.GenColumn; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.generator.domain.vo.GenTableVo; + +/** + * 模板处理工具类 + * + * @author ruoyi + */ +public class VelocityUtils { + /** 项目空间路径 */ + private static final String PROJECT_PATH = "main/java"; + + /** mybatis空间路径 */ + private static final String MYBATIS_PATH = "main/resources/mapper"; + + /** 默认上级菜单,系统工具 */ + private static final String DEFAULT_PARENT_MENU_ID = "3"; + + /** + * 设置模板变量信息 + * + * @return 模板列表 + */ + public static VelocityContext prepareContext(GenTableVo genTableVo) { + GenTable genTable = genTableVo.getTable(); + String moduleName = genTable.getModuleName(); + String businessName = genTable.getBusinessName(); + String packageName = genTable.getPackageName(); + String tplCategory = genTable.getTplCategory(); + String functionName = genTable.getFunctionName(); + + VelocityContext velocityContext = new VelocityContext(); + velocityContext.put("tplCategory", genTable.getTplCategory()); + velocityContext.put("tableName", genTable.getTableName()); + velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】"); + velocityContext.put("ClassName", genTable.getClassName()); + velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName())); + velocityContext.put("moduleName", genTable.getModuleName()); + velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName())); + velocityContext.put("businessName", genTable.getBusinessName()); + velocityContext.put("basePackage", getPackagePrefix(packageName)); + velocityContext.put("packageName", packageName); + velocityContext.put("author", genTable.getFunctionAuthor()); + velocityContext.put("datetime", DateUtils.getDate()); + velocityContext.put("pkColumn", genTable.getPkColumn()); + velocityContext.put("importList", getImportList(genTable)); + velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); + velocityContext.put("columns", genTable.getColumns()); + velocityContext.put("table", genTable); + velocityContext.put("tableMap", genTableVo.getTableMap()); + velocityContext.put("tableAliasMap", genTableVo.getTableAliasMap()); + velocityContext.put("columnMap", genTableVo.getColumnMap()); + velocityContext.put("allColumns", genTableVo.getAllGenTableColumns()); + velocityContext.put("joinColunms", genTableVo.getJoinColumns()); + velocityContext.put("joinTablesMate", genTableVo.getJoinTablesMate()); + velocityContext.put("dicts", getDicts(genTable)); + setMenuVelocityContext(velocityContext, genTable); + if (GenConstants.TPL_TREE.equals(tplCategory)) { + setTreeVelocityContext(velocityContext, genTable); + } + if (GenConstants.TPL_SUB.equals(tplCategory)) { + setSubVelocityContext(velocityContext, genTable); + } + return velocityContext; + } + + public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String parentMenuId = getParentMenuId(paramsObj); + context.put("parentMenuId", parentMenuId); + } + + public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeCode = getTreecode(paramsObj); + String treeParentCode = getTreeParentCode(paramsObj); + String treeName = getTreeName(paramsObj); + + context.put("treeCode", treeCode); + context.put("treeParentCode", treeParentCode); + context.put("treeName", treeName); + context.put("expandColumn", getExpandColumn(genTable)); + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) { + context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + if (paramsObj.containsKey(GenConstants.TREE_NAME)) { + context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME)); + } + } + + public static void setSubVelocityContext(VelocityContext context, GenTable genTable) { + GenTable subTable = genTable.getSubTable(); + String subTableName = genTable.getSubTableName(); + String subTableFkName = genTable.getSubTableFkName(); + String subClassName = genTable.getSubTable().getClassName(); + String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName); + + context.put("subTable", subTable); + context.put("subTableName", subTableName); + context.put("subTableFkName", subTableFkName); + context.put("subTableFkClassName", subTableFkClassName); + context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName)); + context.put("subClassName", subClassName); + context.put("subclassName", StringUtils.uncapitalize(subClassName)); + context.put("subImportList", getImportList(genTable.getSubTable())); + } + + /** + * 获取模板信息 + * + * @return 模板列表 + */ + public static List getTemplateList(String tplCategory, String tplWebType) { + List templates = new ArrayList(); + templates.add("vm/java/domain.java.vm"); + templates.add("vm/java/mapper.java.vm"); + templates.add("vm/java/service.java.vm"); + templates.add("vm/java/serviceImpl.java.vm"); + templates.add("vm/java/controller.java.vm"); + templates.add("vm/xml/mapper.xml.vm"); + templates.add("vm/sql/sql.vm"); + templates.add("vm/js/api.js.vm"); + if (GenConstants.TPL_CRUD.equals(tplCategory)) { + templates.add("vm/vue/index.vue.vm"); + } else if (GenConstants.TPL_TREE.equals(tplCategory)) { + templates.add("vm/vue/index-tree.vue.vm"); + } else if (GenConstants.TPL_SUB.equals(tplCategory)) { + templates.add("vm/vue/index.vue.vm"); + templates.add("vm/java/sub-domain.java.vm"); + } + templates.add("vm/uniapp/edit.vue.vm"); + templates.add("vm/uniapp/list.vue.vm"); + templates.add("vm/uniapp/show.vue.vm"); + return templates; + } + + /** + * 获取文件名 + */ + public static String getFileName(String template, GenTable genTable) { + // 文件名称 + String fileName = ""; + // 包路径 + String packageName = genTable.getPackageName(); + // 模块名 + String moduleName = genTable.getModuleName(); + // 大写类名 + String className = genTable.getClassName(); + // 业务名称 + String businessName = genTable.getBusinessName(); + + String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/"); + String mybatisPath = MYBATIS_PATH + "/" + moduleName; + String vuePath = "vue"; + String uniPath = "uniapp"; + + if (template.contains("domain.java.vm")) { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, className); + } else if (template.contains("sub-domain.java.vm") + && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory())) { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName()); + } else if (template.contains("mapper.java.vm")) { + fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className); + } else if (template.contains("service.java.vm")) { + fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className); + } else if (template.contains("serviceImpl.java.vm")) { + fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className); + } else if (template.contains("controller.java.vm")) { + fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className); + } else if (template.contains("mapper.xml.vm")) { + fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className); + } else if (template.contains("sql.vm")) { + fileName = businessName + "Menu.sql"; + } else if (template.contains("api.js.vm")) { + fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName); + } else if (template.contains("index.vue.vm")) { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } else if (template.contains("index-tree.vue.vm")) { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } else if (template.contains("entity.js.vm")) { + fileName = StringUtils.format("{}/entity/{}/{}.js", vuePath, moduleName, businessName); + } else if (template.contains("edit.vue.vm")) { + fileName = StringUtils.format("{}/pages/{}/{}/edit.vue", uniPath, moduleName, businessName); + } else if (template.contains("list.vue.vm")) { + fileName = StringUtils.format("{}/pages/{}/{}/list.vue", uniPath, moduleName, businessName); + } else if (template.contains("show.vue.vm")) { + fileName = StringUtils.format("{}/pages/{}/{}/show.vue", uniPath, moduleName, businessName); + } + return fileName; + } + + /** + * 获取包前缀 + * + * @param packageName 包名称 + * @return 包前缀名称 + */ + public static String getPackagePrefix(String packageName) { + int lastIndex = packageName.lastIndexOf("."); + return StringUtils.substring(packageName, 0, lastIndex); + } + + /** + * 根据列类型获取导入包 + * + * @param genTable 业务表对象 + * @return 返回需要导入的包列表 + */ + public static HashSet getImportList(GenTable genTable) { + List columns = genTable.getColumns(); + GenTable subGenTable = genTable.getSubTable(); + HashSet importList = new HashSet(); + if (StringUtils.isNotNull(subGenTable)) { + importList.add("java.util.List"); + } + for (GenColumn column : columns) { + if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) { + importList.add("java.time.LocalDate"); + importList.add("com.fasterxml.jackson.annotation.JsonFormat"); + } else if (!column.isSuperColumn() && GenConstants.TYPE_TIME.equals(column.getJavaType())) { + importList.add("java.time.LocalTime"); + importList.add("com.fasterxml.jackson.annotation.JsonFormat"); + } else if (!column.isSuperColumn() && GenConstants.TYPE_DATETIME.equals(column.getJavaType())) { + importList.add("java.time.LocalDateTime"); + importList.add("com.fasterxml.jackson.annotation.JsonFormat"); + } else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) { + importList.add("java.math.BigDecimal"); + } + } + return importList; + } + + /** + * 根据列类型获取字典组 + * + * @param genTable 业务表对象 + * @return 返回字典组 + */ + public static String getDicts(GenTable genTable) { + List columns = genTable.getColumns(); + Set dicts = new HashSet(); + addDicts(dicts, columns); + if (StringUtils.isNotNull(genTable.getSubTable())) { + List subColumns = genTable.getSubTable().getColumns(); + addDicts(dicts, subColumns); + } + return StringUtils.join(dicts, ", "); + } + + /** + * 添加字典列表 + * + * @param dicts 字典列表 + * @param columns 列集合 + */ + public static void addDicts(Set dicts, List columns) { + for (GenColumn column : columns) { + if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny( + column.getHtmlType(), + new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) { + dicts.add("'" + column.getDictType() + "'"); + } + } + } + + /** + * 获取权限前缀 + * + * @param moduleName 模块名称 + * @param businessName 业务名称 + * @return 返回权限前缀 + */ + public static String getPermissionPrefix(String moduleName, String businessName) { + return StringUtils.format("{}:{}", moduleName, businessName); + } + + /** + * 获取上级菜单ID字段 + * + * @param paramsObj 生成其他选项 + * @return 上级菜单ID字段 + */ + public static String getParentMenuId(JSONObject paramsObj) { + if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID) + && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) { + return paramsObj.getString(GenConstants.PARENT_MENU_ID); + } + return DEFAULT_PARENT_MENU_ID; + } + + /** + * 获取树编码 + * + * @param paramsObj 生成其他选项 + * @return 树编码 + */ + public static String getTreecode(JSONObject paramsObj) { + if (paramsObj.containsKey(GenConstants.TREE_CODE)) { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树父编码 + * + * @param paramsObj 生成其他选项 + * @return 树父编码 + */ + public static String getTreeParentCode(JSONObject paramsObj) { + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树名称 + * + * @param paramsObj 生成其他选项 + * @return 树名称 + */ + public static String getTreeName(JSONObject paramsObj) { + if (paramsObj.containsKey(GenConstants.TREE_NAME)) { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME)); + } + return StringUtils.EMPTY; + } + + /** + * 获取需要在哪一列上面显示展开按钮 + * + * @param genTable 业务表对象 + * @return 展开按钮列序号 + */ + public static int getExpandColumn(GenTable genTable) { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + int num = 0; + for (GenColumn column : genTable.getColumns()) { + if (column.isList()) { + num++; + String columnName = column.getColumnName(); + if (columnName.equals(treeName)) { + break; + } + } + } + return num; + } +} diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/mapper/generator/GenColumnMapper.xml b/ruoyi-models/ruoyi-generator/src/main/resources/mapper/generator/GenColumnMapper.xml new file mode 100644 index 0000000..b69b309 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/mapper/generator/GenColumnMapper.xml @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort,sub_column_table_name,sub_column_fk_name,sub_column_name,sub_column_java_type,sub_column_java_field, create_by, create_time, update_by, update_time from gen_table_column + + + + + + + + insert into gen_table_column ( + table_id, + column_name, + column_comment, + column_type, + java_type, + java_field, + is_pk, + is_increment, + is_required, + is_insert, + is_edit, + is_list, + is_query, + query_type, + html_type, + dict_type, + sort, + sub_column_table_name, + sub_column_fk_name, + sub_column_name, + sub_column_java_field, + sub_column_java_type, + create_by, + create_time + )values( + #{tableId}, + #{columnName}, + #{columnComment}, + #{columnType}, + #{javaType}, + #{javaField}, + #{isPk}, + #{isIncrement}, + #{isRequired}, + #{isInsert}, + #{isEdit}, + #{isList}, + #{isQuery}, + #{queryType}, + #{htmlType}, + #{dictType}, + #{sort}, + #{subColumnTableName}, + #{subColumnFkName}, + #{subColumnName}, + #{subColumnJavaField}, + #{subColumnJavaType}, + #{createBy}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + + update gen_table_column + + column_comment = #{columnComment}, + java_type = #{javaType}, + java_field = #{javaField}, + is_insert = #{isInsert}, + is_edit = #{isEdit}, + is_list = #{isList}, + is_query = #{isQuery}, + is_required = #{isRequired}, + query_type = #{queryType}, + html_type = #{htmlType}, + dict_type = #{dictType}, + sort = #{sort}, + sub_column_table_name = #{subColumnTableName}, + sub_column_java_type = #{subColumnJavaType}, + sub_column_fk_name = #{subColumnFkName}, + sub_column_name = #{subColumnName}, + sub_column_java_field = #{subColumnJavaField}, + update_by = #{updateBy}, + update_time = + + CURRENT_TIMESTAMP + + + sysdate() + + + + where column_id = #{columnId} + + + + delete from gen_table_column where table_id in + + #{tableId} + + + + + delete from gen_table_column where column_id in + + #{item.columnId} + + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/mapper/generator/GenJoinMapper.xml b/ruoyi-models/ruoyi-generator/src/main/resources/mapper/generator/GenJoinMapper.xml new file mode 100644 index 0000000..e9e9092 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/mapper/generator/GenJoinMapper.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + select table_id, left_table_id, right_table_id, left_table_alias, right_table_alias, left_table_fk, right_table_fk, join_type, join_columns, order_num, new_table_id from gen_join_table + + + + + + insert into gen_join_table + + table_id, + left_table_id, + right_table_id, + left_table_alias, + right_table_alias, + left_table_fk, + right_table_fk, + join_type, + join_columns, + order_num, + new_table_id, + + + #{tableId}, + #{leftTableId}, + #{rightTableId}, + #{leftTableAlias}, + #{rightTableAlias}, + #{leftTableFk}, + #{rightTableFk}, + #{joinType}, + #{joinColumns,typeHandler=com.ruoyi.common.handler.GenericListTypeHandler$StringList}, + #{orderNum}, + #{newTableId}, + + + + + update gen_join_table + + left_table_id = #{leftTableId}, + right_table_id = #{rightTableId}, + left_table_alias = #{leftTableAlias}, + right_table_alias = #{rightTableAlias}, + left_table_fk = #{leftTableFk}, + right_table_fk = #{rightTableFk}, + join_type = #{joinType}, + join_columns = #{joinColumns,typeHandler=com.ruoyi.common.handler.GenericListTypeHandler$StringList}, + order_num = #{orderNum}, + new_table_id = #{newTableId}, + + where gen_join_table.table_id = #{tableId} and gen_join_table.right_table_id = #{rightTableId} + + + + delete from gen_join_table where table_id = #{tableId} + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/ruoyi-models/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml new file mode 100644 index 0000000..ad4617e --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select table_id, table_name,table_alias, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, have_sub_column,create_by, create_time, update_by, update_time, remark from gen_table + + + + + + + + + + + + + + + + + + insert into gen_table ( + table_name, + table_alias, + table_comment, + class_name, + tpl_category, + tpl_web_type, + package_name, + module_name, + business_name, + function_name, + function_author, + gen_type, + gen_path, + remark, + create_by, + have_sub_column, + create_time + )values( + #{tableName}, + #{tableAlias}, + #{tableComment}, + #{className}, + #{tplCategory}, + #{tplWebType}, + #{packageName}, + #{moduleName}, + #{businessName}, + #{functionName}, + #{functionAuthor}, + #{genType}, + #{genPath}, + #{remark}, + #{haveSubColumn}, + #{createBy}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + + ${sql} + + + + update gen_table + + table_name = #{tableName}, + table_alias = #{tableAlias}, + table_comment = #{tableComment}, + sub_table_name = #{subTableName}, + sub_table_fk_name = #{subTableFkName}, + class_name = #{className}, + function_author = #{functionAuthor}, + gen_type = #{genType}, + gen_path = #{genPath}, + tpl_category = #{tplCategory}, + tpl_web_type = #{tplWebType}, + package_name = #{packageName}, + module_name = #{moduleName}, + business_name = #{businessName}, + function_name = #{functionName}, + options = #{options}, + update_by = #{updateBy}, + remark = #{remark}, + have_sub_column = #{haveSubColumn}, + update_time = + + CURRENT_TIMESTAMP + + + sysdate() + + + + where table_id = #{tableId} + + + + delete from gen_table where table_id in + + #{tableId} + + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/controller.java.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/controller.java.vm new file mode 100644 index 0000000..3716c6f --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/controller.java.vm @@ -0,0 +1,124 @@ +package ${packageName}.controller; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.ruoyi.common.utils.poi.ExcelUtil; +#if($table.crud || $table.sub) +import com.ruoyi.common.core.page.TableDataInfo; +#elseif($table.tree) +#end +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; + +/** + * ${functionName}Controller + * + * @author ${author} + * @date ${datetime} + */ +@RestController +@RequestMapping("/${moduleName}/${businessName}") +@Tag(name = "【${functionName}】管理") +public class ${ClassName}Controller extends BaseController +{ + @Autowired + private I${ClassName}Service ${className}Service; + + /** + * 查询${functionName}列表 + */ + @Operation(summary = "查询${functionName}列表") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')") + @GetMapping("/list") +#if($table.crud || $table.sub) + public TableDataInfo list(${ClassName} ${className}) + { + startPage(); + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return getDataTable(list); + } +#elseif($table.tree) + public AjaxResult list(${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return success(list); + } +#end + + /** + * 导出${functionName}列表 + */ + @Operation(summary = "导出${functionName}列表") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')") + @Log(title = "${functionName}", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, ${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + util.exportExcel(response, list, "${functionName}数据"); + } + + /** + * 获取${functionName}详细信息 + */ + @Operation(summary = "获取${functionName}详细信息") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')") + @GetMapping(value = "/{${pkColumn.javaField}}") + public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) + { + return success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } + + /** + * 新增${functionName} + */ + @Operation(summary = "新增${functionName}") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')") + @Log(title = "${functionName}", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.insert${ClassName}(${className})); + } + + /** + * 修改${functionName} + */ + @Operation(summary = "修改${functionName}") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')") + @Log(title = "${functionName}", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.update${ClassName}(${className})); + } + + /** + * 删除${functionName} + */ + @Operation(summary = "删除${functionName}") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')") + @Log(title = "${functionName}", businessType = BusinessType.DELETE) + @DeleteMapping("/{${pkColumn.javaField}s}") + public AjaxResult remove(@PathVariable( name = "${pkColumn.javaField}s" ) ${pkColumn.javaType}[] ${pkColumn.javaField}s) + { + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); + } +} diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/domain.java.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/domain.java.vm new file mode 100644 index 0000000..68a852a --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/domain.java.vm @@ -0,0 +1,145 @@ +package ${packageName}.domain; + +#foreach ($import in $importList) +import ${import}; +#end +import io.swagger.v3.oas.annotations.media.Schema; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +#if($table.crud || $table.sub) +import com.ruoyi.common.core.domain.BaseEntity; +#elseif($table.tree) +import com.ruoyi.common.core.domain.TreeEntity; +#end + +/** + * ${functionName}对象 ${tableName} + * + * @author ${author} + * @date ${datetime} + */ +#if($table.crud || $table.sub) +#set($Entity="BaseEntity") +#elseif($table.tree) +#set($Entity="TreeEntity") +#end +@Schema(description = "${functionName}对象") +public class ${ClassName} extends ${Entity} +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $allColumns) +#if(!$table.isSuperColumn($column.javaField)) + + /** $column.columnComment */ + @Schema(title = "$column.columnComment") +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#elseif($column.javaType == 'Time') + @JsonFormat(pattern = "HH:mm:ss") + @Excel(name = "${comment}", width = 30, dateFormat = "HH:mm:ss") +#elseif($column.javaType == 'DateTime') + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; +#if($column.subColumnTableName && $table.haveSubColumn == '1') + + /** $column.javaField 到 $column.subColumnTableName 映射 */ + private $column.subColumnJavaType $column.subColumnJavaField; +#end +#end +#end +#if($table.sub) + /** $table.subTable.functionName信息 */ + private List<${subClassName}> ${subclassName}List; + +#end +#foreach ($column in $allColumns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } + +#if($column.subColumnTableName && $table.haveSubColumn == '1') +#if($column.subColumnJavaField.length() > 2 && $column.subColumnJavaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.subColumnJavaField) +#else +#set($AttrName=$column.subColumnJavaField.substring(0,1).toUpperCase() + ${column.subColumnJavaField.substring(1)}) +#end + public void set${AttrName}($column.subColumnJavaType $column.subColumnJavaField) + { + this.$column.subColumnJavaField = $column.subColumnJavaField; + } + + public $column.subColumnJavaType get${AttrName}() + { + return $column.subColumnJavaField; + } +#end + +#end +#end + +#if($table.sub) + public List<${subClassName}> get${subClassName}List() + { + return ${subclassName}List; + } + + public void set${subClassName}List(List<${subClassName}> ${subclassName}List) + { + this.${subclassName}List = ${subclassName}List; + } + +#end + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $allColumns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#if($column.subColumnTableName && $table.haveSubColumn == '1') +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.subColumnJavaField) +#else +#set($AttrName=$column.subColumnJavaField.substring(0,1).toUpperCase() + ${column.subColumnJavaField.substring(1)}) +#end + .append("${column.subColumnJavaField}", get${AttrName}()) +#end +#end +#if($table.sub) + .append("${subclassName}List", get${subClassName}List()) +#end + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm new file mode 100644 index 0000000..7e7d7c2 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm @@ -0,0 +1,91 @@ +package ${packageName}.mapper; + +import java.util.List; +import ${packageName}.domain.${ClassName}; +#if($table.sub) +import ${packageName}.domain.${subClassName}; +#end + +/** + * ${functionName}Mapper接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface ${ClassName}Mapper +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 删除${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); +#if($table.sub) + + /** + * 批量删除${subTable.functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 批量新增${subTable.functionName} + * + * @param ${subclassName}List ${subTable.functionName}列表 + * @return 结果 + */ + public int batch${subClassName}(List<${subClassName}> ${subclassName}List); + + + /** + * 通过${functionName}主键删除${subTable.functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}ID + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); +#end +} diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/service.java.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/service.java.vm new file mode 100644 index 0000000..264882b --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/service.java.vm @@ -0,0 +1,61 @@ +package ${packageName}.service; + +import java.util.List; +import ${packageName}.domain.${ClassName}; + +/** + * ${functionName}Service接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface I${ClassName}Service +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); +} diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm new file mode 100644 index 0000000..14746e1 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -0,0 +1,169 @@ +package ${packageName}.service.impl; + +import java.util.List; +#foreach ($column in $columns) +#if($column.javaField == 'createTime' || $column.javaField == 'updateTime') +import com.ruoyi.common.utils.DateUtils; +#break +#end +#end +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +#if($table.sub) +import java.util.ArrayList; +import com.ruoyi.common.utils.StringUtils; +import org.springframework.transaction.annotation.Transactional; +import ${packageName}.domain.${subClassName}; +#end +import ${packageName}.mapper.${ClassName}Mapper; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; + +/** + * ${functionName}Service业务层处理 + * + * @author ${author} + * @date ${datetime} + */ +@Service +public class ${ClassName}ServiceImpl implements I${ClassName}Service +{ + @Autowired + private ${ClassName}Mapper ${className}Mapper; + + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + @Override + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { + return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName} + */ + @Override + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) + { + return ${className}Mapper.select${ClassName}List(${className}); + } + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int insert${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'createTime') + ${className}.setCreateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + int rows = ${className}Mapper.insert${ClassName}(${className}); + insert${subClassName}(${className}); + return rows; +#else + return ${className}Mapper.insert${ClassName}(${className}); +#end + } + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int update${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'updateTime') + ${className}.setUpdateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()); + insert${subClassName}(${className}); +#end + return ${className}Mapper.update${ClassName}(${className}); + } + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s); + } + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } +#if($table.sub) + + /** + * 新增${subTable.functionName}信息 + * + * @param ${className} ${functionName}对象 + */ + public void insert${subClassName}(${ClassName} ${className}) + { + List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); + ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); + if (StringUtils.isNotNull(${subclassName}List)) + { + List<${subClassName}> list = new ArrayList<${subClassName}>(); + for (${subClassName} ${subclassName} : ${subclassName}List) + { + ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); + list.add(${subclassName}); + } + if (list.size() > 0) + { + ${className}Mapper.batch${subClassName}(list); + } + } + } +#end +} diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm new file mode 100644 index 0000000..a3f53eb --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm @@ -0,0 +1,76 @@ +package ${packageName}.domain; + +#foreach ($import in $subImportList) +import ${import}; +#end +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * ${subTable.functionName}对象 ${subTableName} + * + * @author ${author} + * @date ${datetime} + */ +public class ${subClassName} extends BaseEntity +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } +#end +#end + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $subTable.columns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#end + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/js/api.js.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/js/api.js.vm new file mode 100644 index 0000000..9295524 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/js/api.js.vm @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询${functionName}列表 +export function list${BusinessName}(query) { + return request({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }) +} + +// 查询${functionName}详细 +export function get${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }) +} + +// 新增${functionName} +export function add${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'post', + data: data + }) +} + +// 修改${functionName} +export function update${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'put', + data: data + }) +} + +// 删除${functionName} +export function del${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }) +} diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/sql/sql.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/sql/sql.vm new file mode 100644 index 0000000..4354d99 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/sql/sql.vm @@ -0,0 +1,22 @@ +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', CURRENT_TIMESTAMP, '', null, '${functionName}菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); \ No newline at end of file diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/uniapp/edit.vue.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/uniapp/edit.vue.vm new file mode 100644 index 0000000..c0b02a8 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/uniapp/edit.vue.vm @@ -0,0 +1,155 @@ + + + diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/uniapp/list.vue.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/uniapp/list.vue.vm new file mode 100644 index 0000000..98acb4e --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/uniapp/list.vue.vm @@ -0,0 +1,140 @@ + + + diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/uniapp/show.vue.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/uniapp/show.vue.vm new file mode 100644 index 0000000..1b4a86e --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/uniapp/show.vue.vm @@ -0,0 +1,85 @@ + + + diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm new file mode 100644 index 0000000..7bbd2fc --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm @@ -0,0 +1,474 @@ + + + diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm new file mode 100644 index 0000000..36c212b --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm @@ -0,0 +1,645 @@ + + + diff --git a/ruoyi-models/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm b/ruoyi-models/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm new file mode 100644 index 0000000..f7866f4 --- /dev/null +++ b/ruoyi-models/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -0,0 +1,167 @@ + + + + + +#foreach ($column in $columns) + +#end +#if($joinColunms) +#foreach ($column in $joinColunms) + +#end +#end + +#if($table.sub) + + + + + + +#foreach ($column in $subTable.columns) + +#end + +#end + + + select +#foreach ($column in $allColumns) +#set($columnName=$columnMap[$column.columnId].columnName) +#set($tableAlias=$tableAliasMap[$column.tableId]) + $tableAlias.$columnName#if($foreach.hasNext),#end +#end + from $table.tableName $table.tableAlias +#if($joinTablesMate) +#foreach($joinTable in $joinTablesMate) +#set($leftColumnName=$columnMap[$joinTable.leftTableFk].columnName) +#set($leftTableAlias=$joinTable.leftTableAlias) +#set($rightColumnName=$columnMap[$joinTable.rightTableFk].columnName) +#set($rightTableAlias=$joinTable.rightTableAlias) +#set($newTableName=$tableMap[$joinTable.newTableId].tableName) +#set($newTableAlias=$tableAliasMap[$joinTable.newTableId]) + ${joinTable.joinType} join $newTableName $newTableAlias on $rightTableAlias.$rightColumnName = $leftTableAlias.$leftColumnName +#end +#end + + + + + + + + insert into ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + $column.columnName, +#end +#end + + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + #{$column.javaField}, +#end +#end + + + + + update ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName) + $column.columnName = #{$column.javaField}, +#end +#end + +#if($table.haveSubColumn == '1') + where ${pkColumn.columnName} = #{${pkColumn.javaField}} +#else + where ${tableName}.${pkColumn.columnName} = #{${pkColumn.javaField}} +#end + + + + delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} in + + #{${pkColumn.javaField}} + + +#if($table.sub) + + + delete from ${subTableName} where ${subTableFkName} in + + #{${subTableFkclassName}} + + + + + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} + + + + insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values + + (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end) + + +#end + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-labMeter/pom.xml b/ruoyi-models/ruoyi-labMeter/pom.xml new file mode 100644 index 0000000..3a160bc --- /dev/null +++ b/ruoyi-models/ruoyi-labMeter/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + com.ruoyi.geekxd + ruoyi + 3.9.0-G + ../../pom.xml + + + ruoyi-labMeter + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-labMeter/src/main/java/com/ruoyi/labMeter/Main.java b/ruoyi-models/ruoyi-labMeter/src/main/java/com/ruoyi/labMeter/Main.java new file mode 100644 index 0000000..f072015 --- /dev/null +++ b/ruoyi-models/ruoyi-labMeter/src/main/java/com/ruoyi/labMeter/Main.java @@ -0,0 +1,17 @@ +package com.ruoyi.labMeter; + +//TIP 要运行代码,请按 或 +// 点击装订区域中的 图标。 +public class Main { + public static void main(String[] args) { + //TIP 当文本光标位于高亮显示的文本处时按 + // 查看 IntelliJ IDEA 建议如何修正。 + System.out.printf("Hello and welcome!"); + + for (int i = 1; i <= 5; i++) { + //TIP 按 开始调试代码。我们已经设置了一个 断点 + // 但您始终可以通过按 添加更多断点。 + System.out.println("i = " + i); + } + } +} \ No newline at end of file diff --git a/ruoyi-models/ruoyi-message/pom.xml b/ruoyi-models/ruoyi-message/pom.xml new file mode 100644 index 0000000..f9334d4 --- /dev/null +++ b/ruoyi-models/ruoyi-message/pom.xml @@ -0,0 +1,39 @@ + + + + ruoyi-models + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-message + + + message消息模块 + + + + + + + com.ruoyi.geekxd + ruoyi-framework + + + + + com.ruoyi.geekxd + ruoyi-common + + + + com.ruoyi.geekxd + ruoyi-auth-starter + + + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/annotation/MessageLog.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/annotation/MessageLog.java new file mode 100644 index 0000000..71620f1 --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/annotation/MessageLog.java @@ -0,0 +1,36 @@ +package com.ruoyi.modelMessage.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.ruoyi.modelMessage.enums.MessageType; + + +//自定义消息系统注解 +@Target({ ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MessageLog { + /** + * 消息操作的描述 + */ + String description() default ""; + + /** + * 消息标题 + */ + String title() default ""; + + /** + * 是否立即记录消息,默认为 true + */ + boolean immediateLog() default true; + + /** + * 操作类型 + */ + MessageType messageType() default MessageType.INFO; +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/controller/MessageSystemController.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/controller/MessageSystemController.java new file mode 100644 index 0000000..f76cf28 --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/controller/MessageSystemController.java @@ -0,0 +1,158 @@ +package com.ruoyi.modelMessage.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.modelMessage.domain.MessageSystem; +import com.ruoyi.modelMessage.service.IMessageSystemService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 消息管理Controller + * + * @author ruoyi + * @date 2024-12-21 + */ +@RestController +@RequestMapping("/modelMessage/messageSystem") +@Tag(name = "【消息管理】管理") +public class MessageSystemController extends BaseController +{ + @Autowired + private IMessageSystemService messageSystemService; + + /** + * 查询消息管理列表 + */ + @Operation(summary = "查询消息管理列表") + //@PreAuthorize("@ss.hasPermi('modelMessage:messageSystem:list')") + @GetMapping("/list") + public TableDataInfo list(MessageSystem messageSystem) + { + startPage(); + messageSystem.setCreateBy(getUsername()); + messageSystem.setMessageRecipient(getUsername()); + List list = messageSystemService.selectMessageSystemList(messageSystem); + return getDataTable(list); + } + + /** + * 导出消息管理列表 + */ + @Operation(summary = "导出消息管理列表") + @PreAuthorize("@ss.hasPermi('modelMessage:messageSystem:export')") + @Log(title = "消息管理", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, MessageSystem messageSystem) + { + List list = messageSystemService.selectMessageSystemList(messageSystem); + ExcelUtil util = new ExcelUtil(MessageSystem.class); + util.exportExcel(response, list, "消息管理数据"); + } + + /** + * 获取消息管理详细信息 + */ + @Operation(summary = "获取消息管理详细信息") + //@PreAuthorize("@ss.hasPermi('modelMessage:messageSystem:query')") + @GetMapping(value = "/{messageId}") + public AjaxResult getInfo(@PathVariable("messageId") Long messageId) + { + return success(messageSystemService.selectMessageSystemByMessageId(messageId)); + } + + /** + * 修改消息管理 + */ + @Operation(summary = "修改消息管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:messageSystem:edit')") + @Log(title = "消息管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody MessageSystem messageSystem) + { + messageSystem.setUpdateBy(getUsername()); + messageSystem.setUpdateTime(DateUtils.getNowDate()); + return toAjax(messageSystemService.updateMessageSystem(messageSystem)); + } + + /** + * 删除消息管理 + */ + @Operation(summary = "删除消息管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:messageSystem:remove')") + @Log(title = "消息管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{messageIds}") + public AjaxResult remove(@PathVariable( name = "messageIds" ) Long[] messageIds) + { + return toAjax(messageSystemService.deleteMessageSystemByMessageIds(messageIds)); + } + + /** + * 批量发送消息 + */ + @Operation(summary = "批量发送消息") + @Log(title = "批量发送消息", businessType = BusinessType.INSERT) + @PostMapping + @Transactional + public AjaxResult batchAdd(@RequestBody List messageSystemList) { + try { + messageSystemList.forEach(messageSystemService::processMessageSystem); + messageSystemService.batchInsertMessageSystem(messageSystemList); + return AjaxResult.success("消息发送成功!"); + } catch (Exception e) { + return AjaxResult.error("消息发送失败", e); + } + } + + /** + * 点击信息详情状态调整为已读 + */ + @Operation(summary = "点击信息详情状态调整为已读") + @PostMapping("/{messageId}") + public AjaxResult update(@PathVariable Long messageId){ + return success(messageSystemService.updateState(messageId)); + } + + /** + * 统一查询系统资源信息(角色、部门、用户) + * + * @param type 查询类型:role-角色信息, dept-部门信息, user-用户信息, usersbyrole-根据角色查用户, usersbydept-根据部门查用户, usersbysendmode-根据发送方式查用户 + * @param id 可选参数,当查询特定角色或部门下的用户时使用 + * @param sendMode 可选参数,当查询特定发送方式的用户时使用(phone/email) + * @return 查询结果 + */ + @Operation(summary = "统一查询系统资源信息") + @GetMapping("/systemResource") + public AjaxResult getSystemResource(@RequestParam String type, @RequestParam(required = false) Long id, + @RequestParam(required = false) String sendMode) { + try { + Object result = messageSystemService.querySystemResource(type, id, sendMode); + return AjaxResult.success(result); + } catch (ServiceException e) { + return AjaxResult.error(e.getMessage()); + } + } +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/controller/MessageTemplateController.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/controller/MessageTemplateController.java new file mode 100644 index 0000000..6d3af0b --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/controller/MessageTemplateController.java @@ -0,0 +1,126 @@ +package com.ruoyi.modelMessage.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.modelMessage.domain.MessageTemplate; +import com.ruoyi.modelMessage.service.IMessageTemplateService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 模版管理Controller + * + * @author ruoyi + * @date 2024-12-31 + */ +@RestController +@RequestMapping("/modelMessage/template") +@Tag(name = "【模版管理】管理") +public class MessageTemplateController extends BaseController +{ + @Autowired + private IMessageTemplateService messageTemplateService; + + /** + * 查询模版管理列表 + */ + @Operation(summary = "查询模版管理列表") + //@PreAuthorize("@ss.hasPermi('modelMessage:template:list')") + @GetMapping("/list") + public TableDataInfo list(MessageTemplate messageTemplate) + { + startPage(); + List list = messageTemplateService.selectMessageTemplateList(messageTemplate); + return getDataTable(list); + } + + /** + * 导出模版管理列表 + */ + @Operation(summary = "导出模版管理列表") + @PreAuthorize("@ss.hasPermi('modelMessage:template:export')") + @Log(title = "模版管理", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, MessageTemplate messageTemplate) + { + List list = messageTemplateService.selectMessageTemplateList(messageTemplate); + ExcelUtil util = new ExcelUtil(MessageTemplate.class); + util.exportExcel(response, list, "模版管理数据"); + } + + /** + * 获取模版管理详细信息 + */ + @Operation(summary = "获取模版管理详细信息") + //@PreAuthorize("@ss.hasPermi('modelMessage:template:query')") + @GetMapping(value = "/{templateId}") + public AjaxResult getInfo(@PathVariable("templateId") Long templateId) + { + return success(messageTemplateService.selectMessageTemplateByTemplateId(templateId)); + } + + /** + * 新增模版管理 + */ + @Operation(summary = "新增模版管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:template:add')") + @Log(title = "模版管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody MessageTemplate messageTemplate) + { + return toAjax(messageTemplateService.insertMessageTemplate(messageTemplate)); + } + + /** + * 修改模版管理 + */ + @Operation(summary = "修改模版管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:template:edit')") + @Log(title = "模版管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody MessageTemplate messageTemplate) + { + return toAjax(messageTemplateService.updateMessageTemplate(messageTemplate)); + } + + /** + * 删除模版管理 + */ + @Operation(summary = "删除模版管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:template:remove')") + @Log(title = "模版管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{templateIds}") + public AjaxResult remove(@PathVariable( name = "templateIds" ) Long[] templateIds) + { + return toAjax(messageTemplateService.deleteMessageTemplateByTemplateIds(templateIds)); + } + + /** + * 查询模版签名 + * @return 模版信息列表 + */ + @Operation(summary = "查询模版下拉框") + @GetMapping("/selecTemplates") + public AjaxResult selecTemplates() { + return success(messageTemplateService.selecTemplates()); + } +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/controller/MessageVariableController.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/controller/MessageVariableController.java new file mode 100644 index 0000000..6fba84a --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/controller/MessageVariableController.java @@ -0,0 +1,125 @@ +package com.ruoyi.modelMessage.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.modelMessage.domain.MessageVariable; +import com.ruoyi.modelMessage.service.IMessageVariableService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 变量管理Controller + * + * @author ruoyi + * @date 2024-12-31 + */ +@RestController +@RequestMapping("/modelMessage/variable") +@Tag(name = "【变量管理】管理") +public class MessageVariableController extends BaseController +{ + @Autowired + private IMessageVariableService messageVariableService; + + /** + * 查询变量管理列表 + */ + @Operation(summary = "查询变量管理列表") + //@PreAuthorize("@ss.hasPermi('modelMessage:variable:list')") + @GetMapping("/list") + public TableDataInfo list(MessageVariable messageVariable) + { + startPage(); + List list = messageVariableService.selectMessageVariableList(messageVariable); + return getDataTable(list); + } + + /** + * 导出变量管理列表 + */ + @Operation(summary = "导出变量管理列表") + @PreAuthorize("@ss.hasPermi('modelMessage:variable:export')") + @Log(title = "变量管理", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, MessageVariable messageVariable) + { + List list = messageVariableService.selectMessageVariableList(messageVariable); + ExcelUtil util = new ExcelUtil(MessageVariable.class); + util.exportExcel(response, list, "变量管理数据"); + } + + /** + * 获取变量管理详细信息 + */ + @Operation(summary = "获取变量管理详细信息") + //@PreAuthorize("@ss.hasPermi('modelMessage:variable:query')") + @GetMapping(value = "/{variableId}") + public AjaxResult getInfo(@PathVariable("variableId") Long variableId) + { + return success(messageVariableService.selectMessageVariableByVariableId(variableId)); + } + + /** + * 新增变量管理 + */ + @Operation(summary = "新增变量管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:variable:add')") + @Log(title = "变量管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody MessageVariable messageVariable) + { + return toAjax(messageVariableService.insertMessageVariable(messageVariable)); + } + + /** + * 修改变量管理 + */ + @Operation(summary = "修改变量管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:variable:edit')") + @Log(title = "变量管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody MessageVariable messageVariable) + { + return toAjax(messageVariableService.updateMessageVariable(messageVariable)); + } + + /** + * 删除变量管理 + */ + @Operation(summary = "删除变量管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:variable:remove')") + @Log(title = "变量管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{variableIds}") + public AjaxResult remove(@PathVariable( name = "variableIds" ) Long[] variableIds) + { + return toAjax(messageVariableService.deleteMessageVariableByVariableIds(variableIds)); + } + + /** + * 查询变量 + */ + @Operation(summary = "查询变量下拉框") + @GetMapping("/selectMessageVariable") + public AjaxResult selectMessageVariable() { + return success(messageVariableService.selectMessageVariable()); + } +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/domain/MessageSystem.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/domain/MessageSystem.java new file mode 100644 index 0000000..7dc177a --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/domain/MessageSystem.java @@ -0,0 +1,159 @@ +package com.ruoyi.modelMessage.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 消息管理对象 message_system + * + * @author ruoyi + * @date 2024-12-21 + */ +@Schema(description = "消息管理对象") +public class MessageSystem extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键 */ + @Schema(title = "主键") + private Long messageId; + + /** 标题 */ + @Schema(title = "标题") + @Excel(name = "标题") + private String messageTitle; + + /** 消息内容 */ + @Schema(title = "消息内容") + @Excel(name = "消息内容") + private String messageContent; + + /** 消息状态(0未读 1已读) */ + @Schema(title = "消息状态(0未读 1已读)") + @Excel(name = "消息状态(0未读 1已读)") + private String messageStatus; + + /** 消息类型 */ + @Schema(title = "消息类型") + @Excel(name = "消息类型") + private String messageType; + + /** 消息类型 */ + @Schema(title = "接收人") + @Excel(name = "接收人") + private String messageRecipient; + + /** 发送方式(0平台 1手机号 2 邮箱) */ + @Schema(title = "发送方式(0平台 1手机号 2 邮箱)") + @Excel(name = "发送方式(0平台 1手机号 2 邮箱)") + private String sendMode; + + /** 号码 */ + @Schema(title = "号码") + @Excel(name = "号码") + private String code; + + public String getSendMode() { + return sendMode; + } + + public void setSendMode(String sendMode) { + this.sendMode = sendMode; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public void setMessageId(Long messageId) + { + this.messageId = messageId; + } + + public Long getMessageId() + { + return messageId; + } + + + public void setMessageTitle(String messageTitle) + { + this.messageTitle = messageTitle; + } + + public String getMessageTitle() + { + return messageTitle; + } + + + public void setMessageContent(String messageContent) + { + this.messageContent = messageContent; + } + + public String getMessageContent() + { + return messageContent; + } + + + public void setMessageStatus(String messageStatus) + { + this.messageStatus = messageStatus; + } + + public String getMessageStatus() + { + return messageStatus; + } + + + public void setMessageType(String messageType) + { + this.messageType = messageType; + } + + public String getMessageType() + { + return messageType; + } + + public String getMessageRecipient() { + return messageRecipient; + } + + public void setMessageRecipient(String messageRecipient) { + this.messageRecipient = messageRecipient; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("messageId", getMessageId()) + .append("messageTitle", getMessageTitle()) + .append("messageContent", getMessageContent()) + .append("messageStatus", getMessageStatus()) + .append("messageType", getMessageType()) + .append("messageRecipient", getMessageRecipient()) + .append("sendMode", getSendMode()) + .append("code", getCode()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } + + +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/domain/MessageTemplate.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/domain/MessageTemplate.java new file mode 100644 index 0000000..f8067cf --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/domain/MessageTemplate.java @@ -0,0 +1,134 @@ +package com.ruoyi.modelMessage.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 模版管理对象 message_template + * + * @author ruoyi + * @date 2024-12-31 + */ +@Schema(description = "模版管理对象") +public class MessageTemplate extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + + /** 主键 */ + @Schema(title = "主键") + private Long templateId; + + /** 模版名称 */ + @Schema(title = "模版名称") + @Excel(name = "模版名称") + private String templateName; + + /** 模版CODE */ + @Schema(title = "模版CODE") + @Excel(name = "模版CODE") + private String templateCode; + + /** 模版类型 */ + @Schema(title = "模版类型") + @Excel(name = "模版类型") + private String templateType; + + /** 模版内容 */ + @Schema(title = "模版内容") + @Excel(name = "模版内容") + private String templateContent; + + /** 变量属性 */ + @Schema(title = "变量属性") + @Excel(name = "变量属性") + private String templateVariable; + public void setTemplateId(Long templateId) + { + this.templateId = templateId; + } + + public Long getTemplateId() + { + return templateId; + } + + + public void setTemplateName(String templateName) + { + this.templateName = templateName; + } + + public String getTemplateName() + { + return templateName; + } + + + public void setTemplateCode(String templateCode) + { + this.templateCode = templateCode; + } + + public String getTemplateCode() + { + return templateCode; + } + + + public void setTemplateType(String templateType) + { + this.templateType = templateType; + } + + public String getTemplateType() + { + return templateType; + } + + + public void setTemplateContent(String templateContent) + { + this.templateContent = templateContent; + } + + public String getTemplateContent() + { + return templateContent; + } + + + public void setTemplateVariable(String templateVariable) + { + this.templateVariable = templateVariable; + } + + public String getTemplateVariable() + { + return templateVariable; + } + + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("templateId", getTemplateId()) + .append("templateName", getTemplateName()) + .append("templateCode", getTemplateCode()) + .append("templateType", getTemplateType()) + .append("templateContent", getTemplateContent()) + .append("templateVariable", getTemplateVariable()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/domain/MessageVariable.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/domain/MessageVariable.java new file mode 100644 index 0000000..155cd38 --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/domain/MessageVariable.java @@ -0,0 +1,107 @@ +package com.ruoyi.modelMessage.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 变量管理对象 message_variable + * + * @author ruoyi + * @date 2024-12-31 + */ +@Schema(description = "变量管理对象") +public class MessageVariable extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + + /** 主键 */ + @Schema(title = "主键") + private Long variableId; + + /** 变量名称 */ + @Schema(title = "变量名称") + @Excel(name = "变量名称") + private String variableName; + + /** 变量类型 */ + @Schema(title = "变量类型") + @Excel(name = "变量类型") + private String variableType; + + /** 变量内容 */ + @Schema(title = "变量内容") + @Excel(name = "变量内容") + private String variableContent; + public void setVariableId(Long variableId) + { + this.variableId = variableId; + } + + public MessageVariable(Long variableId, String variableName, String variableType, String variableContent) { + this.variableId = variableId; + this.variableName = variableName; + this.variableType = variableType; + this.variableContent = variableContent; + } + + public Long getVariableId() + { + return variableId; + } + + + public void setVariableName(String variableName) + { + this.variableName = variableName; + } + + public String getVariableName() + { + return variableName; + } + + + public void setVariableType(String variableLength) + { + this.variableType = variableLength; + } + + public String getVariableType() + { + return variableType; + } + + + public void setVariableContent(String variableContent) + { + this.variableContent = variableContent; + } + + public String getVariableContent() + { + return variableContent; + } + + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("variableId", getVariableId()) + .append("variableName", getVariableName()) + .append("variableType", getVariableType()) + .append("variableContent", getVariableContent()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/enums/BuiltInVariableType.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/enums/BuiltInVariableType.java new file mode 100644 index 0000000..b0c7063 --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/enums/BuiltInVariableType.java @@ -0,0 +1,100 @@ +package com.ruoyi.modelMessage.enums; + +import com.ruoyi.auth.common.utils.RandomCodeUtil; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.modelMessage.domain.MessageSystem; + +public enum BuiltInVariableType { + TIME("time", "HH:mm:ss") { + @Override + public String generateValue(MessageSystem message) { + return DateUtils.dateTimeNow(format); + } + }, + DATE("date", "yyyy-MM-dd") { + @Override + public String generateValue(MessageSystem message) { + return DateUtils.dateTimeNow(format); + } + }, + DATETIME("datetime", "yyyy-MM-dd HH:mm:ss") { + @Override + public String generateValue(MessageSystem message) { + return DateUtils.dateTimeNow(format); + } + }, + ADDRESSER("addresser", null) { + @Override + public String generateValue(MessageSystem message) { + return SecurityUtils.getUsername(); + } + }, + CODE("code", null) { + @Override + public String generateValue(MessageSystem message) { + return RandomCodeUtil.numberCode(CODE_LENGTH); + } + }, + RANDOMN_DIGITS("RandomnDigits", null) { + @Override + public String generateValue(MessageSystem message) { + return RandomCodeUtil.numberCode(CODE_LENGTH); + } + }, + RANDOMN_CHARACTERS("RandomnCharacters", null) { + @Override + public String generateValue(MessageSystem message) { + return RandomCodeUtil.code(CODE_LENGTH); + } + }, + RANDOMN_DIGIT_LETTERS("RandomN-digitLetters", null) { + @Override + public String generateValue(MessageSystem message) { + return RandomCodeUtil.code(CODE_LENGTH); + } + }, + RANDOMN_DIGIT_LETTERS_ALT("RandomNDigitLetters", null) { + @Override + public String generateValue(MessageSystem message) { + return RandomCodeUtil.code(CODE_LENGTH); + } + }, + RECIPIENTS("recipients", null) { + @Override + public String generateValue(MessageSystem message) { + return message.getMessageRecipient(); + } + }; + + private final String identifier; + protected final String format; + private static final int CODE_LENGTH = 6; + + BuiltInVariableType(String identifier, String format) { + this.identifier = identifier; + this.format = format; + } + + public String getIdentifier() { + return identifier; + } + + public abstract String generateValue(MessageSystem message); + + public static BuiltInVariableType fromIdentifier(String identifier) { + if (identifier == null) return null; + for (BuiltInVariableType type : values()) { + if (type.identifier.equals(identifier)) { + return type; + } + } + return null; + } + + public static boolean isBuiltInVariable(String identifier) { + return fromIdentifier(identifier) != null; + } +} + + diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/enums/MessageType.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/enums/MessageType.java new file mode 100644 index 0000000..36a1dbb --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/enums/MessageType.java @@ -0,0 +1,25 @@ +package com.ruoyi.modelMessage.enums; + +public enum MessageType { + INFO("信息", "普通信息记录"), + WARN("警告", "非致命问题警告"), + ERROR("错误", "严重错误信息"), + DEBUG("调试", "调试信息,仅用于开发环境"), + SUCCESS("成功", "操作成功的提示信息"); + + private final String code; + private final String description; + + MessageType(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/enums/SendMode.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/enums/SendMode.java new file mode 100644 index 0000000..6693572 --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/enums/SendMode.java @@ -0,0 +1,32 @@ +package com.ruoyi.modelMessage.enums; + +public enum SendMode { + PLATFORM("0", "平台"), + PHONE("1", "手机号"), + EMAIL("2", "邮箱"); + + private final String code; + private final String description; + + SendMode(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public static SendMode fromCode(String code) { + for (SendMode mode : SendMode.values()) { + if (mode.getCode().equals(code)) { + return mode; + } + } + throw new IllegalArgumentException("未知的发送方式: " + code); + } +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/mapper/MessageSystemMapper.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/mapper/MessageSystemMapper.java new file mode 100644 index 0000000..ec2fb4e --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/mapper/MessageSystemMapper.java @@ -0,0 +1,109 @@ +package com.ruoyi.modelMessage.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.modelMessage.domain.MessageSystem; + +/** + * 消息管理Mapper接口 + * + * @author ruoyi + * @date 2024-12-21 + */ +public interface MessageSystemMapper +{ + /** + * 查询消息管理 + * + * @param messageId 消息管理主键 + * @return 消息管理 + */ + public MessageSystem selectMessageSystemByMessageId(Long messageId); + + /** + * 查询消息管理列表 + * + * @param messageSystem 消息管理 + * @return 消息管理集合 + */ + public List selectMessageSystemList(MessageSystem messageSystem); + + /** + * 新增消息管理 + * + * @param messageSystem 消息管理 + * @return 结果 + */ + public int insertMessageSystem(MessageSystem messageSystem); + + /** + * 修改消息管理 + * + * @param messageSystem 消息管理 + * @return 结果 + */ + public int updateMessageSystem(MessageSystem messageSystem); + + /** + * 删除消息管理 + * + * @param messageId 消息管理主键 + * @return 结果 + */ + public int deleteMessageSystemByMessageId(Long messageId); + + /** + * 批量删除消息管理 + * + * @param messageIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteMessageSystemByMessageIds(Long[] messageIds); + + //查询平台系统资源收件人用户信息 + @Select("SELECT user_id, dept_id, user_name, phonenumber, email FROM `sys_user`") + public List selectUser(); + + //查询角色信息 然后根据角色把消息发给某角色 + @Select("SELECT role_id, role_name FROM `sys_role`") + public List selectRole(); + + //查询部门信息 然后根据部门把消息发给某部门 + @Select("SELECT dept_id, parent_id, dept_name FROM `sys_dept`") + public List selectDept(); + + // 根据发送方式过滤用户 (短信或邮箱) + public List selectUserBySendMode(String filterType); + + //将信息状态未读信息变为已读 + @Update("update message_system set message_status = 1 where message_id = #{messageId} and message_recipient = #{userName}") + public int updateState(Long messageId, String userName); + + + /** + * 根据部门ID获取所有符合条件的用户信息。 + * + * @param deptId 部门ID + * @return 用户信息列表 + */ + @Select("SELECT u.user_name FROM sys_user u JOIN sys_dept d ON u.dept_id = d.dept_id WHERE d.dept_id = #{deptId}") + public List getUserNameByDeptId(Long deptId); + + /** + * 根据角色ID查询用户列表。 + * + * @param roleId 角色ID + * @return 用户列表 + */ + @Select("SELECT u.user_name FROM sys_user u JOIN sys_user_role ur ON u.user_id = ur.user_id WHERE ur.role_id = #{roleId}") + public List selectUsersByRoleId(Long roleId); + + //批量发送信息 + public int batchInsertMessageSystem(List messageSystemList); +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/mapper/MessageTemplateMapper.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/mapper/MessageTemplateMapper.java new file mode 100644 index 0000000..ed3f94a --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/mapper/MessageTemplateMapper.java @@ -0,0 +1,77 @@ +package com.ruoyi.modelMessage.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Select; + +import com.ruoyi.modelMessage.domain.MessageTemplate; + +/** + * 模版管理Mapper接口 + * + * @author ruoyi + * @date 2024-12-31 + */ +public interface MessageTemplateMapper +{ + /** + * 查询模版管理 + * + * @param templateId 模版管理主键 + * @return 模版管理 + */ + public MessageTemplate selectMessageTemplateByTemplateId(Long templateId); + + /** + * 查询模版管理列表 + * + * @param messageTemplate 模版管理 + * @return 模版管理集合 + */ + public List selectMessageTemplateList(MessageTemplate messageTemplate); + + /** + * 新增模版管理 + * + * @param messageTemplate 模版管理 + * @return 结果 + */ + public int insertMessageTemplate(MessageTemplate messageTemplate); + + /** + * 修改模版管理 + * + * @param messageTemplate 模版管理 + * @return 结果 + */ + public int updateMessageTemplate(MessageTemplate messageTemplate); + + /** + * 删除模版管理 + * + * @param templateId 模版管理主键 + * @return 结果 + */ + public int deleteMessageTemplateByTemplateId(Long templateId); + + /** + * 批量删除模版管理 + * + * @param templateIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteMessageTemplateByTemplateIds(Long[] templateIds); + + /** + * 根据模板templateCode查询模板信息 + * + * @param templateCode 模版编码 + * @return 模版对象 + */ + @Select("SELECT template_code,template_variable,template_content FROM `message_template` WHERE template_code = #{templateCode}") + public MessageTemplate selectMessageTemplateByTemplateCode(String templateCode); + + //查询模版签名 + @Select("SELECT template_id,template_code FROM `message_template`") + public List selecTemplates(); +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/mapper/MessageVariableMapper.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/mapper/MessageVariableMapper.java new file mode 100644 index 0000000..8d3cf24 --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/mapper/MessageVariableMapper.java @@ -0,0 +1,75 @@ +package com.ruoyi.modelMessage.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Select; + +import com.ruoyi.modelMessage.domain.MessageVariable; + +/** + * 变量管理Mapper接口 + * + * @author ruoyi + * @date 2024-12-31 + */ +public interface MessageVariableMapper +{ + /** + * 查询变量管理 + * + * @param variableId 变量管理主键 + * @return 变量管理 + */ + public MessageVariable selectMessageVariableByVariableId(Long variableId); + + /** + * 查询变量管理列表 + * + * @param messageVariable 变量管理 + * @return 变量管理集合 + */ + public List selectMessageVariableList(MessageVariable messageVariable); + + /** + * 新增变量管理 + * + * @param messageVariable 变量管理 + * @return 结果 + */ + public int insertMessageVariable(MessageVariable messageVariable); + + /** + * 修改变量管理 + * + * @param messageVariable 变量管理 + * @return 结果 + */ + public int updateMessageVariable(MessageVariable messageVariable); + + /** + * 删除变量管理 + * + * @param variableId 变量管理主键 + * @return 结果 + */ + public int deleteMessageVariableByVariableId(Long variableId); + + /** + * 批量删除变量管理 + * + * @param variableIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteMessageVariableByVariableIds(Long[] variableIds); + + //查询变量 + @Select("SELECT variable_id, variable_name, variable_type, variable_content FROM message_variable") + public List selectMessageVariable(); + + //查询在使用模版签名时用到了那些变量一一赋值 + public List selectMessageVariables(List variableNames); + + //查询模版使用的变量 + @Select("SELECT template_variable FROM message_template") + public List selectAllTemplateVariables(); +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/IMessageSystemService.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/IMessageSystemService.java new file mode 100644 index 0000000..47b142f --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/IMessageSystemService.java @@ -0,0 +1,81 @@ +package com.ruoyi.modelMessage.service; + +import java.util.List; + +import com.ruoyi.modelMessage.domain.MessageSystem; + +/** + * 消息管理Service接口 + * + * @author ruoyi + * @date 2024-12-21 + */ +public interface IMessageSystemService +{ + /** + * 查询消息管理 + * + * @param messageId 消息管理主键 + * @return 消息管理 + */ + public MessageSystem selectMessageSystemByMessageId(Long messageId); + + /** + * 查询消息管理列表 + * + * @param messageSystem 消息管理 + * @return 消息管理集合 + */ + public List selectMessageSystemList(MessageSystem messageSystem); + + /** + * 新增消息管理 + * + * @param messageSystem 消息管理 + * @return 结果 + */ + public int insertMessageSystem(MessageSystem messageSystem); + + /** + * 修改消息管理 + * + * @param messageSystem 消息管理 + * @return 结果 + */ + public int updateMessageSystem(MessageSystem messageSystem); + + /** + * 批量删除消息管理 + * + * @param messageIds 需要删除的消息管理主键集合 + * @return 结果 + */ + public int deleteMessageSystemByMessageIds(Long[] messageIds); + + /** + * 删除消息管理信息 + * + * @param messageId 消息管理主键 + * @return 结果 + */ + public int deleteMessageSystemByMessageId(Long messageId); + + //点击信息详情状态调整为已读 + public int updateState(Long messageId); + + //根据发送方式 执行不同操作 + public void processMessageSystem(MessageSystem messageSystem); + + // 批量发送信息 + public int batchInsertMessageSystem(List messageSystemList); + + /** + * 统一查询系统资源信息(角色、部门、用户) + * + * @param type 查询类型:role-角色信息, dept-部门信息, user-用户信息, usersbyrole-根据角色查用户, usersbydept-根据部门查用户, usersbysendmode-根据发送方式查用户 + * @param id 可选参数,当查询特定角色或部门下的用户时使用 + * @param sendMode 可选参数,当查询特定发送方式的用户时使用(phone/email) + * @return 查询结果 + */ + public Object querySystemResource(String type, Long id, String sendMode); +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/IMessageTemplateService.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/IMessageTemplateService.java new file mode 100644 index 0000000..6c73856 --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/IMessageTemplateService.java @@ -0,0 +1,65 @@ +package com.ruoyi.modelMessage.service; + +import java.util.List; + +import com.ruoyi.modelMessage.domain.MessageTemplate; + +/** + * 模版管理Service接口 + * + * @author ruoyi + * @date 2024-12-31 + */ +public interface IMessageTemplateService +{ + /** + * 查询模版管理 + * + * @param templateId 模版管理主键 + * @return 模版管理 + */ + public MessageTemplate selectMessageTemplateByTemplateId(Long templateId); + + /** + * 查询模版管理列表 + * + * @param messageTemplate 模版管理 + * @return 模版管理集合 + */ + public List selectMessageTemplateList(MessageTemplate messageTemplate); + + /** + * 新增模版管理 + * + * @param messageTemplate 模版管理 + * @return 结果 + */ + public int insertMessageTemplate(MessageTemplate messageTemplate); + + /** + * 修改模版管理 + * + * @param messageTemplate 模版管理 + * @return 结果 + */ + public int updateMessageTemplate(MessageTemplate messageTemplate); + + /** + * 批量删除模版管理 + * + * @param templateIds 需要删除的模版管理主键集合 + * @return 结果 + */ + public int deleteMessageTemplateByTemplateIds(Long[] templateIds); + + /** + * 删除模版管理信息 + * + * @param templateId 模版管理主键 + * @return 结果 + */ + public int deleteMessageTemplateByTemplateId(Long templateId); + + // 查询模版签名 + public List selecTemplates(); +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/IMessageVariableService.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/IMessageVariableService.java new file mode 100644 index 0000000..1075bca --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/IMessageVariableService.java @@ -0,0 +1,67 @@ +package com.ruoyi.modelMessage.service; + +import java.util.List; + +import com.ruoyi.modelMessage.domain.MessageVariable; + +/** + * 变量管理Service接口 + * + * @author ruoyi + * @date 2024-12-31 + */ +public interface IMessageVariableService +{ + /** + * 查询变量管理 + * + * @param variableId 变量管理主键 + * @return 变量管理 + */ + public MessageVariable selectMessageVariableByVariableId(Long variableId); + + /** + * 查询变量管理列表 + * + * @param messageVariable 变量管理 + * @return 变量管理集合 + */ + public List selectMessageVariableList(MessageVariable messageVariable); + + /** + * 新增变量管理 + * + * @param messageVariable 变量管理 + * @return 结果 + */ + public int insertMessageVariable(MessageVariable messageVariable); + + /** + * 修改变量管理 + * + * @param messageVariable 变量管理 + * @return 结果 + */ + public int updateMessageVariable(MessageVariable messageVariable); + + /** + * 批量删除变量管理 + * + * @param variableIds 需要删除的变量管理主键集合 + * @return 结果 + */ + public int deleteMessageVariableByVariableIds(Long[] variableIds); + + /** + * 删除变量管理信息 + * + * @param variableId 变量管理主键 + * @return 结果 + */ + public int deleteMessageVariableByVariableId(Long variableId); + + /** + * 查询全部变量 + */ + public List selectMessageVariable(); +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/impl/MessageSystemServiceImpl.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/impl/MessageSystemServiceImpl.java new file mode 100644 index 0000000..16c8f19 --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/impl/MessageSystemServiceImpl.java @@ -0,0 +1,496 @@ +package com.ruoyi.modelMessage.service.impl; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.modelMessage.domain.MessageSystem; +import com.ruoyi.modelMessage.domain.MessageTemplate; +import com.ruoyi.modelMessage.domain.MessageVariable; +import com.ruoyi.modelMessage.enums.BuiltInVariableType; +import com.ruoyi.modelMessage.enums.SendMode; +import com.ruoyi.modelMessage.mapper.MessageSystemMapper; +import com.ruoyi.modelMessage.mapper.MessageTemplateMapper; +import com.ruoyi.modelMessage.mapper.MessageVariableMapper; +import com.ruoyi.modelMessage.service.IMessageSystemService; +import com.ruoyi.tfa.email.utils.EmailUtil; +import com.ruoyi.tfa.phone.config.DySmsConfig; +import com.ruoyi.tfa.phone.domain.DySmsTemplate; +import com.ruoyi.tfa.phone.utils.DySmsUtil; + +/** + * 消息管理Service业务层处理 + * + */ +@Service +public class MessageSystemServiceImpl implements IMessageSystemService { + + private static final Logger log = LoggerFactory.getLogger(MessageSystemServiceImpl.class); + + @Autowired + private DySmsConfig dySmsConfig; + + @Autowired + private MessageSystemMapper messageSystemMapper; + + @Autowired + private MessageTemplateMapper messageTemplateMapper; + + @Autowired + private MessageVariableMapper messageVariableMapper; + + /** + * 查询消息管理 + * + * @param messageId 消息管理主键 + * @return 消息管理 + */ + @Override + public MessageSystem selectMessageSystemByMessageId(Long messageId) { + return messageSystemMapper.selectMessageSystemByMessageId(messageId); + } + + /** + * 查询消息管理列表 + * + * @param messageSystem 消息管理 + * @return 消息管理列表 + */ + @Override + public List selectMessageSystemList(MessageSystem messageSystem) { + return messageSystemMapper.selectMessageSystemList(messageSystem); + } + + /** + * 新增消息管理 + * + * @param messageSystem 消息管理 + * @return 结果 + */ + @Override + public int insertMessageSystem(MessageSystem messageSystem) { + return messageSystemMapper.insertMessageSystem(messageSystem); + } + + /** + * 修改消息管理 + * + * @param messageSystem 消息管理 + * @return 结果 + */ + @Override + public int updateMessageSystem(MessageSystem messageSystem) { + messageSystem.setUpdateTime(DateUtils.getNowDate()); + return messageSystemMapper.updateMessageSystem(messageSystem); + } + + /** + * 批量删除消息管理 + * + * @param messageIds 需要删除的消息管理主键 + * @return 结果 + */ + @Override + public int deleteMessageSystemByMessageIds(Long[] messageIds) { + return messageSystemMapper.deleteMessageSystemByMessageIds(messageIds); + } + + /** + * 删除消息管理信息 + * + * @param messageId 消息管理主键 + * @return 结果 + */ + @Override + public int deleteMessageSystemByMessageId(Long messageId) { + return messageSystemMapper.deleteMessageSystemByMessageId(messageId); + } + + // 收件人为本人的话点击信息详情的时候然后把状态未读信息修改为已读 + @Override + public int updateState(Long messageId) { + int result = messageSystemMapper.updateState(messageId, SecurityUtils.getUsername()); + return result; + } + + /** + * 批量发送信息 + */ + @Override + public int batchInsertMessageSystem(List messageSystemList) { + for (MessageSystem messageSystem : messageSystemList) { + messageSystem.setMessageStatus("0"); // 默认发送信息为未读状态 + messageSystem.setCreateBy(SecurityUtils.getUsername()); + messageSystem.setUpdateBy(SecurityUtils.getUsername()); + messageSystem.setCreateTime(DateUtils.getNowDate()); + messageSystem.setUpdateTime(DateUtils.getNowDate()); + } + int result = messageSystemMapper.batchInsertMessageSystem(messageSystemList); + if (result <= 0) { + throw new ServiceException("消息发送失败!"); + } + return result; + } + + /** + * 统一查询系统资源信息(角色、部门、用户) + * + * @param type 查询类型:role-角色信息, dept-部门信息, user-用户信息, usersbyrole-根据角色查用户, usersbydept-根据部门查用户, usersbysendmode-根据发送方式查用户 + * @param id 可选参数,当查询特定角色或部门下的用户时使用 + * @param sendMode 可选参数,当查询特定发送方式的用户时使用(phone/email) + * @return 查询结果 + */ + @Override + public Object querySystemResource(String type, Long id, String sendMode) { + switch (type.toLowerCase()) { + case "role": + return messageSystemMapper.selectRole(); + case "dept": + return messageSystemMapper.selectDept(); + case "user": + return messageSystemMapper.selectUser(); + case "usersbyrole": + if (id == null) { + throw new ServiceException("查询角色,角色Id不能为空"); + } + return messageSystemMapper.selectUsersByRoleId(id); + case "usersbydept": + if (id == null) { + throw new ServiceException("查询部门,部门Id不能为空"); + } + return messageSystemMapper.getUserNameByDeptId(id); + case "usersbysendmode": + return getUsersBySendMode(sendMode); + default: + throw new ServiceException("不支持的查询类型: " + type); + } + } + + // 根据传递的值进行过滤筛选不符合的用户 + private List getUsersBySendMode(String sendMode) { + if (sendMode == null || sendMode.trim().isEmpty()) { + sendMode = "default"; + } + switch (sendMode) { + case "1": return messageSystemMapper.selectUserBySendMode("phone"); + case "2": return messageSystemMapper.selectUserBySendMode("email"); + default: return messageSystemMapper.selectUser(); + } + } + + /** + * 根据 MessageSystem 对象的 sendMode 属性处理消息的发送方式 + */ + @Override + public void processMessageSystem(MessageSystem messageSystem) { + if (messageSystem == null || messageSystem.getSendMode() == null) { + throw new ServiceException("无效的消息系统对象或发送方式!"); + } + + String sendModeStr = messageSystem.getSendMode(); + SendMode sendMode; + try { + sendMode = SendMode.fromCode(sendModeStr); + } catch (IllegalArgumentException e) { + throw new ServiceException("不支持的消息发送方式: " + sendModeStr); + } + switch (sendMode) { + case PHONE: + sendNotificationMessage(messageSystem); + break; + case EMAIL: + handleEmailNotification(messageSystem); + break; + case PLATFORM: + sendPlatformMessage(messageSystem); + break; + default: + throw new ServiceException("不支持的发送方式: " + sendMode); + } + } + + /** + * 发送平台消息 + */ + public void sendPlatformMessage(MessageSystem messageSystem) { + String notificationContent = messageSystem.getMessageContent(); + try { + String filledMessage = notificationContent.startsWith("SMS_") + ? processTemplateMessage(messageSystem, notificationContent) + : notificationContent; // 是自定义输入,使用用户输入的内容 + log.info("平台发送成功: {}", filledMessage); + messageSystem.setMessageContent(filledMessage); + } catch (Exception e) { + log.error("发送平台消息时发生异常: ", e); + throw new ServiceException("发送平台消息异常:" + e.getMessage()); + } + } + + /** + * 发送邮件通知 + */ + public void handleEmailNotification(MessageSystem messageSystem) { + String email = messageSystem.getCode(); + if (StringUtils.isEmpty(email)) { + throw new ServiceException("邮箱不能为空!"); + } + try { + String filledMessage = messageSystem.getMessageContent().startsWith("SMS_") + ? processTemplateMessage(messageSystem, messageSystem.getMessageContent()) + : messageSystem.getMessageContent(); // 是自定义输入,则直接使用用户提供的内容 + log.info("邮件发送成功: {}", filledMessage); + messageSystem.setMessageContent(filledMessage); + EmailUtil.sendMessage(email, "通知", filledMessage); + } catch (Exception e) { + log.error("发送邮件时发生异常: ", e); + throw new ServiceException("发送通知信息异常:" + e.getMessage()); + } + } + + /** + * 发送短信通知 + */ + @SuppressWarnings("unchecked") + public void sendNotificationMessage(MessageSystem messageSystem) { + String phone = messageSystem.getCode(); + if (StringUtils.isEmpty(phone)) { + throw new ServiceException("手机号码不能为空!"); + } + // 检查短信配置 + if (dySmsConfig == null || dySmsConfig.getTemplate() == null || dySmsConfig.getTemplate().isEmpty()) { + throw new ServiceException("短信配置或模板未正确加载"); + } + try { + // 解析消息内容获取模板信息和参数 + Map parsedParams= parseInput(messageSystem.getMessageContent()); + String templateCode = (String) parsedParams.get("templateCode"); + if (templateCode == null) { + throw new ServiceException("消息内容中未包含有效的模板代码"); + } + // 查找匹配的模板 + DySmsTemplate dySmsTemplate = dySmsConfig.getTemplate().get(templateCode); + // 如果直接匹配失败,尝试通过模板代码匹配 + if (dySmsTemplate == null) { + for (DySmsTemplate template : dySmsConfig.getTemplate().values()) { + if (templateCode.equals(template.getTemplateCode())) { + dySmsTemplate = template; + break; + } + } + } + // 如果还是没找到且只有一个模板,直接使用 + if (dySmsTemplate == null && dySmsConfig.getTemplate().size() == 1) { + dySmsTemplate = dySmsConfig.getTemplate().values().iterator().next(); + } + if (dySmsTemplate == null) { + throw new ServiceException("未找到短信模板: " + templateCode + ",请检查配置"); + } + // 获取并填充参数 + Map params = (Map) parsedParams.get("params"); + List variableNames = (List) parsedParams.get("variableNames"); + fillBuiltInVariables(params, messageSystem, new HashSet<>(variableNames)); + // 获取模板内容并填充变量,生成最终的消息内容 + String templateContent = (String) parsedParams.get("templateContent"); + if (templateContent != null) { + String filledContent = fillTemplate(templateContent, params); + messageSystem.setMessageContent(filledContent); // 更新为填充后的内容 + } + // 构造 JSON 参数并发送短信 + JSONObject templateParamJson = new JSONObject(params); + DySmsUtil.sendSms(phone, dySmsTemplate, templateParamJson); + log.info("短信发送成功 - 手机号: {}, 模板: {}, 参数: {}", phone, templateCode, templateParamJson); + } catch (Exception e) { + log.error("发送短信时发生异常: ", e); + throw new ServiceException("发送短信异常:" + e.getMessage()); + } + } + + /** + * 解析输入字符串,提取模板代码和参数 + */ + public Map parseInput(String input) { + if (input == null || input.trim().isEmpty()) { + throw new ServiceException("输入内容不能为空!"); + } + String templateCode = null; + String queryParams = ""; + // 支持 SMS_ 开头的模板代码或者配置中的模板名 + if (input.startsWith("SMS_")) { + int templateCodeEndIndex = input.indexOf('?'); + if (templateCodeEndIndex != -1) { + templateCode = input.substring(0, templateCodeEndIndex); + queryParams = input.substring(templateCodeEndIndex + 1); + } else { + templateCode = input; + } + } + MessageTemplate templateContent = null; + List variableNames = new ArrayList<>(); + if (templateCode != null) { + templateContent = messageTemplateMapper.selectMessageTemplateByTemplateCode(templateCode); + if (templateContent == null) { + throw new ServiceException("未找到该模版签名! " + templateCode); + } + variableNames = extractVariableNamesFromTemplate(templateContent); + } + Map params = new HashMap<>(); + if (!queryParams.isEmpty()) { + for (String param : queryParams.split("&")) { + String[] keyValue = param.split("=", 2); + if (keyValue.length != 2) { + throw new ServiceException("无效的参数格式: " + param + ",请使用 key=value 格式"); + } + String value = URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8); + params.put(keyValue[0], value); + } + } + if (templateContent != null) { + for (String varName : variableNames) { + params.putIfAbsent(varName, null); + } + } + return Map.of( "templateCode", templateCode, "params", params, + "templateContent", templateContent != null ? templateContent.getTemplateContent() : input, + "variableNames", variableNames ); + } + + /** + * 提取模板中的变量名 + */ + public List extractVariableNamesFromTemplate(MessageTemplate templateContent) { + List variableNames = new ArrayList<>(); + Pattern pattern = Pattern.compile("\\$\\{(.*?)\\}"); + Matcher matcher = pattern.matcher(templateContent.getTemplateContent()); + while (matcher.find()) { + variableNames.add(matcher.group(1)); + } + return variableNames; + } + + /** + * 填充模板内容 + */ + public String fillTemplate(String template, Map params) { + StringBuilder sb = new StringBuilder(template); + for (Map.Entry entry : params.entrySet()) { + String key = "${" + entry.getKey() + "}"; + String value = Optional.ofNullable(entry.getValue()).orElse(""); + + int index; + while ((index = sb.indexOf(key)) != -1) { + sb.replace(index, index + key.length(), value); + } + } + return sb.toString(); + } + + /** + * 清除内置变量的随机数字 + */ + public void clearBuiltInVariables(Map params) { + List builtInVariables = messageVariableMapper.selectMessageVariable(); + for (MessageVariable variable : builtInVariables) { + String variableContent = variable.getVariableContent(); + params.remove(variableContent); + } + } + + /** + * 处理模板消息并填充所有类型的变量 + */ + @SuppressWarnings("unchecked") + public String processTemplateMessage(MessageSystem messageSystem, String notificationContent) throws Exception { + Map parsedParams = parseInput(notificationContent); + String templateCode = (String) parsedParams.get("templateCode"); + MessageTemplate templateContent = null; + if (templateCode != null) { + templateContent = messageTemplateMapper.selectMessageTemplateByTemplateCode(templateCode); + } + Map params = (Map) parsedParams.get("params"); + List variableNames = new ArrayList<>(); + if (templateContent != null) { + variableNames = extractVariableNamesFromTemplate(templateContent); + } + Set variableNameSet = new HashSet<>(variableNames); + clearBuiltInVariables(params); + fillBuiltInVariables(params, messageSystem, variableNameSet); + for (String varName : variableNameSet) { + if (!params.containsKey(varName) || params.get(varName) == null) { + params.put(varName, "[变量未设置: " + varName + "]"); + } + } + String templateContentStr = templateContent != null ? + templateContent.getTemplateContent() : notificationContent; + return fillTemplate(templateContentStr, params); + } + + /** + * 填充所有类型的变量 + */ + public void fillBuiltInVariables(Map params, MessageSystem message, Set variableNameSet) { + List allVariables = messageVariableMapper.selectMessageVariables(new ArrayList<>(variableNameSet)); + for (MessageVariable variable : allVariables) { + String variableType = variable.getVariableType(); + String variableContent = variable.getVariableContent(); + String variableName = variable.getVariableName(); + if ("内置变量".equals(variableType)) { + boolean matchByContent = variableNameSet.contains(variableContent); + boolean matchByName = variableNameSet.contains(variableName); + if (matchByContent || matchByName) { + String keyToUse = matchByContent ? variableContent : variableName; + // 处理内置变量 + if (BuiltInVariableType.isBuiltInVariable(variableContent)) { + // 只有当参数不存在或为空时才生成内置变量的值 + if (!params.containsKey(keyToUse) || params.get(keyToUse) == null || params.get(keyToUse).isEmpty()) { + params.put(keyToUse, BuiltInVariableType.fromIdentifier(variableContent).generateValue(message)); + } + } else if ("recipients".equals(variableContent) || "收件人".equals(variableName)) { + // 收件人变量总是使用实际的收件人信息 + params.put(keyToUse, message.getMessageRecipient()); + } + } + } else if ("指定文本".equals(variableType)) { + if (BuiltInVariableType.isBuiltInVariable(variableContent) && variableNameSet.contains(variableContent)) { + // 只有当参数不存在或为空时才生成内置变量的值 + if (!params.containsKey(variableContent) || params.get(variableContent) == null || params.get(variableContent).isEmpty()) { + params.put(variableContent, BuiltInVariableType.fromIdentifier(variableContent).generateValue(message)); + } + } else if ("recipients".equals(variableContent) && variableNameSet.contains(variableName)) { + params.put(variableName, message.getMessageRecipient()); + } else if (variableNameSet.contains(variableName)) { + if (!params.containsKey(variableName) || params.get(variableName) == null) { + params.put(variableName, variableContent); + } + } + } else { + if (BuiltInVariableType.isBuiltInVariable(variableContent) && variableNameSet.contains(variableContent)) { + // 只有当参数不存在或为空时才生成内置变量的值 + if (!params.containsKey(variableContent) || params.get(variableContent) == null || params.get(variableContent).isEmpty()) { + params.put(variableContent, BuiltInVariableType.fromIdentifier(variableContent).generateValue(message)); + } + } else if ("recipients".equals(variableContent) && variableNameSet.contains(variableName)) { + params.put(variableName, message.getMessageRecipient()); + } + } + } + } +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/impl/MessageTemplateServiceImpl.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/impl/MessageTemplateServiceImpl.java new file mode 100644 index 0000000..27d59aa --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/impl/MessageTemplateServiceImpl.java @@ -0,0 +1,111 @@ +package com.ruoyi.modelMessage.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.modelMessage.domain.MessageTemplate; +import com.ruoyi.modelMessage.mapper.MessageTemplateMapper; +import com.ruoyi.modelMessage.service.IMessageTemplateService; + + +/** + * 模版管理Service业务层处理 + * + * @author ruoyi + * @date 2024-12-31 + */ +@Service +public class MessageTemplateServiceImpl implements IMessageTemplateService +{ + @Autowired + private MessageTemplateMapper messageTemplateMapper; + + /** + * 查询模版管理 + * + * @param templateId 模版管理主键 + * @return 模版管理 + */ + @Override + public MessageTemplate selectMessageTemplateByTemplateId(Long templateId) + { + return messageTemplateMapper.selectMessageTemplateByTemplateId(templateId); + } + + /** + * 查询模版管理列表 + * + * @param messageTemplate 模版管理 + * @return 模版管理 + */ + @Override + public List selectMessageTemplateList(MessageTemplate messageTemplate) + { + return messageTemplateMapper.selectMessageTemplateList(messageTemplate); + } + + /** + * 新增模版管理 + * + * @param messageTemplate 模版管理 + * @return 结果 + */ + @Override + public int insertMessageTemplate(MessageTemplate messageTemplate) + { + messageTemplate.setCreateBy(SecurityUtils.getUsername()); + messageTemplate.setCreateTime(DateUtils.getNowDate()); + messageTemplate.setUpdateBy(SecurityUtils.getUsername()); + messageTemplate.setUpdateTime(DateUtils.getNowDate()); + return messageTemplateMapper.insertMessageTemplate(messageTemplate); + } + + /** + * 修改模版管理 + * + * @param messageTemplate 模版管理 + * @return 结果 + */ + @Override + public int updateMessageTemplate(MessageTemplate messageTemplate) + { + messageTemplate.setUpdateBy(SecurityUtils.getUsername()); + messageTemplate.setUpdateTime(DateUtils.getNowDate()); + return messageTemplateMapper.updateMessageTemplate(messageTemplate); + } + + /** + * 批量删除模版管理 + * + * @param templateIds 需要删除的模版管理主键 + * @return 结果 + */ + @Override + public int deleteMessageTemplateByTemplateIds(Long[] templateIds) + { + return messageTemplateMapper.deleteMessageTemplateByTemplateIds(templateIds); + } + + /** + * 删除模版管理信息 + * + * @param templateId 模版管理主键 + * @return 结果 + */ + @Override + public int deleteMessageTemplateByTemplateId(Long templateId) + { + return messageTemplateMapper.deleteMessageTemplateByTemplateId(templateId); + } + + // 查询模版签名 + @Override + public List selecTemplates() { + List templates = messageTemplateMapper.selecTemplates(); + return templates; + } +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/impl/MessageVariableServiceImpl.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/impl/MessageVariableServiceImpl.java new file mode 100644 index 0000000..0cb87ba --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/service/impl/MessageVariableServiceImpl.java @@ -0,0 +1,129 @@ +package com.ruoyi.modelMessage.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.modelMessage.domain.MessageVariable; +import com.ruoyi.modelMessage.mapper.MessageVariableMapper; +import com.ruoyi.modelMessage.service.IMessageVariableService; + +/** + * 变量管理Service业务层处理 + * + * @author ruoyi + * @date 2024-12-31 + */ +@Service +public class MessageVariableServiceImpl implements IMessageVariableService +{ + + @Autowired + private MessageVariableMapper messageVariableMapper; + + /** + * 查询变量管理 + * + * @param variableId 变量管理主键 + * @return 变量管理 + */ + @Override + public MessageVariable selectMessageVariableByVariableId(Long variableId) + { + return messageVariableMapper.selectMessageVariableByVariableId(variableId); + } + + /** + * 查询变量管理列表 + * + * @param messageVariable 变量管理 + * @return 变量管理 + */ + @Override + public List selectMessageVariableList(MessageVariable messageVariable) + { + return messageVariableMapper.selectMessageVariableList(messageVariable); + } + + /** + * 新增变量管理 + * + * @param messageVariable 变量管理 + * @return 结果 + */ + @Override + public int insertMessageVariable(MessageVariable messageVariable) + { + messageVariable.setCreateBy(SecurityUtils.getUsername()); + messageVariable.setCreateTime(DateUtils.getNowDate()); + messageVariable.setUpdateBy(SecurityUtils.getUsername()); + messageVariable.setUpdateTime(DateUtils.getNowDate()); + return messageVariableMapper.insertMessageVariable(messageVariable); + } + + /** + * 修改变量管理 + * + * @param messageVariable 变量管理 + * @return 结果 + */ + @Override + public int updateMessageVariable(MessageVariable messageVariable) + { + messageVariable.setUpdateBy(SecurityUtils.getUsername()); + messageVariable.setUpdateTime(DateUtils.getNowDate()); + return messageVariableMapper.updateMessageVariable(messageVariable); + } + + /** + * 批量删除变量管理 + * + * @param variableIds 需要删除的变量管理主键 + * @return 结果 + */ + @Override + public int deleteMessageVariableByVariableIds(Long[] variableIds) + { + for (Long variableId : variableIds) { + // 获取变量信息 + MessageVariable variable = messageVariableMapper.selectMessageVariableByVariableId(variableId); + if (variable == null) { + throw new ServiceException("未找到该变量信息!"); + } + // 检查变量是否被模板使用 + String variableName = variable.getVariableName(); + List variables = messageVariableMapper.selectAllTemplateVariables(); + for (String templateVariable : variables) { + String[] templateParts = templateVariable.split("/"); + for (String part : templateParts) { + if (part.equals(variableName)) { + throw new ServiceException("变量为 '" + variableName + "'' 已被模版使用,不能删除!"); + } + } + } + } + return messageVariableMapper.deleteMessageVariableByVariableIds(variableIds); + } + + /** + * 删除变量管理信息 + * + * @param variableId 变量管理主键 + * @return 结果 + */ + @Override + public int deleteMessageVariableByVariableId(Long variableId) + { + return messageVariableMapper.deleteMessageVariableByVariableId(variableId); + } + + //查询变量 + @Override + public List selectMessageVariable() { + return messageVariableMapper.selectMessageVariable(); + } +} diff --git a/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/utils/MessageLogAspect.java b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/utils/MessageLogAspect.java new file mode 100644 index 0000000..7a5bfcf --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/java/com/ruoyi/modelMessage/utils/MessageLogAspect.java @@ -0,0 +1,68 @@ +package com.ruoyi.modelMessage.utils; + +import java.lang.reflect.Method; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.modelMessage.annotation.MessageLog; +import com.ruoyi.modelMessage.domain.MessageSystem; +import com.ruoyi.modelMessage.enums.MessageType; +import com.ruoyi.modelMessage.service.IMessageSystemService; + +@Aspect +@Component +@Order(1) //Aspect 的优先级 +public class MessageLogAspect { + + private static final Logger logger = LoggerFactory.getLogger(MessageLogAspect.class); + + @Autowired + private IMessageSystemService messageSystemService; + + @Around("@annotation(messageLog)") + public Object handleMessageLog(ProceedingJoinPoint joinPoint, MessageLog messageLog) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + // 获取注解信息 + String description = messageLog.description(); + String title = messageLog.title(); + MessageType messageType = messageLog.messageType(); + boolean immediateLog = messageLog.immediateLog(); + try { + Object result = joinPoint.proceed(); + // 如果设置了true立即执行 + if (immediateLog) { + logMessage(title, description, messageType); + } + return result; + } catch (Exception e) { + logger.error("消息记录失败,方法: {}, 描述: {}", method.getName(), description, e); + throw e; + } + } + + private void logMessage(String title, String description, MessageType messageType) { + MessageSystem messageSystem = new MessageSystem(); + messageSystem.setMessageTitle(title); // 标题 + messageSystem.setCreateBy(SecurityUtils.getUsername()); // 发送人 + messageSystem.setCreateTime(DateUtils.getNowDate()); // 发送时间 + messageSystem.setMessageContent(description); // 信息内容 + messageSystem.setMessageStatus("0"); // 默认为未读 0未读 1 已读 + messageSystem.setMessageType(messageType.getCode()); + messageSystem.setUpdateBy(SecurityUtils.getUsername()); // 修改人 + messageSystem.setUpdateTime(DateUtils.getNowDate()); // 修改时间 + messageSystem.setSendMode("0"); // 默认发送方式为平台 + messageSystemService.insertMessageSystem(messageSystem); + logger.info("消息记录成功,标题: {}, 描述: {}, 类型: {}", title, description, messageType); + } +} diff --git a/ruoyi-models/ruoyi-message/src/main/resources/mapper/modelMessage/MessageSystemMapper.xml b/ruoyi-models/ruoyi-message/src/main/resources/mapper/modelMessage/MessageSystemMapper.xml new file mode 100644 index 0000000..7f13324 --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/resources/mapper/modelMessage/MessageSystemMapper.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + select message_id, message_title, message_content, message_status, message_type, create_by, create_time, update_by, update_time, remark, message_recipient, send_mode, `code` from message_system + + + + + + + + insert into message_system + + message_title, + message_content, + message_status, + message_type, + create_by, + create_time, + update_by, + update_time, + remark, + message_recipient, + send_mode, + code, + + + #{messageTitle}, + #{messageContent}, + #{messageStatus}, + #{messageType}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{messageRecipient}, + #{sendMode}, + #{code}, + + + + + update message_system + + message_title = #{messageTitle}, + message_content = #{messageContent}, + message_status = #{messageStatus}, + message_type = #{messageType}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + message_recipient = #{messageRecipient}, + send_mode = #{sendMode}, + code = #{code}, + + where message_system.message_id = #{messageId} + + + + delete from message_system where message_id = #{messageId} + + + + delete from message_system where message_id in + + #{messageId} + + + + + + INSERT INTO message_system (message_title, message_content, message_status, message_type, message_recipient, + send_mode, `code`, create_by, create_time, update_by, update_time, remark) VALUES + + ( #{item.messageTitle}, #{item.messageContent}, #{item.messageStatus}, #{item.messageType}, + #{item.messageRecipient}, #{item.sendMode}, #{item.code}, #{item.createBy}, NOW(), #{item.updateBy}, + NOW(), #{item.remark} ) + + + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-message/src/main/resources/mapper/modelMessage/MessageTemplateMapper.xml b/ruoyi-models/ruoyi-message/src/main/resources/mapper/modelMessage/MessageTemplateMapper.xml new file mode 100644 index 0000000..343c613 --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/resources/mapper/modelMessage/MessageTemplateMapper.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + select template_id, template_name, template_code, template_type, template_content, template_variable, create_by, create_time, update_by, update_time, remark from message_template + + + + + + + + insert into message_template + + template_name, + template_code, + template_type, + template_content, + template_variable, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{templateName}, + #{templateCode}, + #{templateType}, + #{templateContent}, + #{templateVariable}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update message_template + + template_name = #{templateName}, + template_code = #{templateCode}, + template_type = #{templateType}, + template_content = #{templateContent}, + template_variable = #{templateVariable}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where message_template.template_id = #{templateId} + + + + delete from message_template where template_id = #{templateId} + + + + delete from message_template where template_id in + + #{templateId} + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-message/src/main/resources/mapper/modelMessage/MessageVariableMapper.xml b/ruoyi-models/ruoyi-message/src/main/resources/mapper/modelMessage/MessageVariableMapper.xml new file mode 100644 index 0000000..e89f028 --- /dev/null +++ b/ruoyi-models/ruoyi-message/src/main/resources/mapper/modelMessage/MessageVariableMapper.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + select variable_id, variable_name, variable_type, variable_content, create_by, create_time, update_by, update_time, remark from message_variable + + + + + + + + insert into message_variable + + variable_name, + variable_type, + variable_content, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{variableName}, + #{variableType}, + #{variableContent}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update message_variable + + variable_name = #{variableName}, + variable_type = #{variableType}, + variable_content = #{variableContent}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where message_variable.variable_id = #{variableId} + + + + delete from message_variable where variable_id = #{variableId} + + + + delete from message_variable where variable_id in + + #{variableId} + + + + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-models-starter/pom.xml b/ruoyi-models/ruoyi-models-starter/pom.xml new file mode 100644 index 0000000..d327904 --- /dev/null +++ b/ruoyi-models/ruoyi-models-starter/pom.xml @@ -0,0 +1,63 @@ + + + + ruoyi-models + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-models-starter + + + 中间件 + + + + + + + com.ruoyi.geekxd + ruoyi-quartz + + + + + com.ruoyi.geekxd + ruoyi-generator + + + + + com.ruoyi.geekxd + ruoyi-online + + + + + com.ruoyi.geekxd + ruoyi-message + + + + + com.ruoyi.geekxd + ruoyi-form + + + + + com.ruoyi.geekxd + ruoyi-flowable + + + + + com.ruoyi.geekxd + ruoyi-ngtools + + + + + diff --git a/ruoyi-models/ruoyi-ngtools/pom.xml b/ruoyi-models/ruoyi-ngtools/pom.xml new file mode 100644 index 0000000..80c1731 --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + com.ruoyi.geekxd + ruoyi + 3.9.0-G + ../../pom.xml + + + ruoyi-ngtools + + + 21 + 21 + UTF-8 + + + + + com.ruoyi.geekxd + ruoyi-framework + + + + + com.ruoyi.geekxd + ruoyi-common + + + + com.ruoyi.geekxd + ruoyi-auth-starter + + + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/Main.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/Main.java new file mode 100644 index 0000000..3260d7b --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/Main.java @@ -0,0 +1,17 @@ +package com.ruoyi.caltools; + +//TIP 要运行代码,请按 或 +// 点击装订区域中的 图标。 +public class Main { + public static void main(String[] args) { + //TIP 当文本光标位于高亮显示的文本处时按 + // 查看 IntelliJ IDEA 建议如何修正。 + System.out.printf("Hello and welcome!"); + + for (int i = 1; i <= 5; i++) { + //TIP 按 开始调试代码。我们已经设置了一个 断点 + // 但您始终可以通过按 添加更多断点。 + System.out.println("i = " + i); + } + } +} \ No newline at end of file diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/DpFlowCalc.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/DpFlowCalc.java new file mode 100644 index 0000000..9707471 --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/DpFlowCalc.java @@ -0,0 +1,1136 @@ +package com.ruoyi.caltools.controller; + + +import com.ruoyi.caltools.model.FlowProps; +import com.ruoyi.caltools.model.GasProps; + +import javax.swing.*; +import java.util.Optional; + +import static com.ruoyi.caltools.controller.FlowController.FlowConvert_BaseToWork; +import static com.ruoyi.caltools.controller.FlowController.YaLiSunShi; + + +public class DpFlowCalc { + /** + 标准孔板流量计算 + */ + public void OFlowCal(GasProps gasProps, FlowProps flowProps){ + + flowProps.setdOrificeD(flowProps.getdOrificeD() * (1 + 0.000001 * (flowProps.getdOrificeMaterial()) * (flowProps.getdTf() - 293.15))); + flowProps.setdPipeD(flowProps.getdPipeD() * (1 + 0.000001 * (flowProps.getdPipeMaterial()) * (flowProps.getdTf() - 293.15))); + flowProps.setdBeta(flowProps.getdOrificeD() / flowProps.getdPipeD()); +// flowProps.setdBeta(0.5972); + + //求渐近速度系数 E + flowProps.setdE(1 / Math.pow((1 - Math.pow(flowProps.getdBeta(), 4)), 0.5)); +// flowProps.setdE(1.0615); + + //求相对密度系数 FG + // LiuTiType = 1 + flowProps.setdFG(Math.pow((1 / gasProps.dRD_Real), 0.5)); +// flowProps.setdFG(1.2531); + //求流动温度系数 'FT + flowProps.setdFT(Math.pow((293.15 / flowProps.getdTf()), 0.5)); +// flowProps.setdFT(1.0086); + //求等熵指数????????????????????????????????? + + flowProps.setdKappa(gasProps.dKappa); +// flowProps.setdKappa(1.357); + + //求动力粘度 dlnd + flowProps.setdDViscosity(Dlndjs(flowProps.getdPf()/1e6, flowProps.getdTf())); +// flowProps.setdDViscosity(0.01096); + + //求可膨胀系数 + flowProps.setdDExpCoefficient(KePenZhang_JiSuan(flowProps.getdPf() , flowProps.getdDp(), flowProps.getdBeta(), gasProps.getdKappa(), flowProps.getdCoreType(), 0)); +// flowProps.setdDExpCoefficient(0.9977); + +// gasProps.dFpv=1.0195; +// gasProps.dHhvv=39.944; +// gasProps.dRD_Real=0.6368; + + //迭代计算流量和流出系数 + + double conQvA = 0.0000031795 * (1530000.0D * gasProps.dRD_Real / (flowProps.getdDViscosity() * flowProps.getdOrificeD())) * flowProps.getdE() * Math.pow(flowProps.getdOrificeD(), 2) * gasProps.dFpv * flowProps.getdFG() * flowProps.getdDExpCoefficient() * flowProps.getdFT() * Math.sqrt(flowProps.getdPf() * flowProps.getdDp() / 1e6); + double[] XQv = new double[4]; + double[] CQv = new double[4]; + double[] dQv = new double[4]; + double Qn = 0; + int n ; + XQv[0] = 1000000.0F; + boolean xhFlag=true; + _100000: + + while(xhFlag) { + for (n = 1; n <= 2; n++) { + CQv[n] = C_JiSuan(flowProps.getdOrificeD(), flowProps.getdBeta(), XQv[n - 1], conQvA, flowProps.getdPtmode(), flowProps.getdCoreType(), 0); + XQv[n] = conQvA * CQv[n]; + dQv[n] = XQv[n] - XQv[n - 1]; + } + if (XQv[2] == XQv[1] || dQv[2] == dQv[1]) { + Qn = conQvA * flowProps.getdDViscosity() * flowProps.getdOrificeD() * CQv[2] / (1530000.0D * gasProps.dRD_Real); + //管道雷诺数 + flowProps.setdRnPipe(XQv[2]); + //流出系数 + flowProps.setdCd(CQv[2]); + + } + if (Math.abs((conQvA - XQv[2] / CQv[1]) / conQvA) > 0.00000000000000005) { + XQv[0] = XQv[n - 1] - dQv[n - 1] * ((XQv[n - 1] - XQv[n - 2]) / (dQv[n - 1] - dQv[n - 2])); +//C# TO JAVA CONVERTER TODO TASK: There is no 'goto' in Java: + continue _100000; + } + else + { + xhFlag=false; + } + } + + //孔板锐利度系数Bk + flowProps.setdOrificeSharpness(1); + if (flowProps.getdCoreType() == 0) + { + flowProps.setdBk((flowProps.getdOrificeSharpness() == 0) ? (BkTable(flowProps.getdOrificeRk(), flowProps.getdOrificeD(), 1)) : (flowProps.getdOrificeSharpness())); + } + else + { + flowProps.setdBk(1); + } + + + //管道粗糙度系数 Gme + flowProps.setdRoughNessPipe(CcdXsjs(flowProps.getdPipeType(), flowProps.getdPipeD(), flowProps.getdBeta(), flowProps.getdRnPipe())); + //修正后的流出系数 + flowProps.setdCd(flowProps.getdCd() * flowProps.getdBk() * flowProps.getdRoughNessPipe()); +// flowProps.setdCd(0.6039); + //标况体积流量 m³、s + flowProps.setdVFlowb(Qn * flowProps.getdBk() * flowProps.getdRoughNessPipe()); + //工况体积流量 + flowProps.setdVFlowf(FlowConvert_BaseToWork(flowProps, gasProps)); + //标况质量流量 + flowProps.setdMFlowb(flowProps.getdVFlowb() * gasProps.dRhob); + //标况能量流量 + flowProps.setdEFlowb(flowProps.getdVFlowb() * gasProps.dHhvv); + //管道内天然气流速 + flowProps.setdVelocityFlow(flowProps.getdVFlowf() / (3.1415926 * Math.pow((flowProps.getdPipeD() / 2000), 2))); + //压力损失 + flowProps.setdPressLost(YaLiSunShi(flowProps.getdCd(), flowProps.getdBeta(), flowProps.getdDp(), flowProps.getdCoreType())); + } + + /** + * 计算管道绝对粗糙度 K (GB/T 21446-2008 附录C) + * @param flowProps + * @return 粗糙度修正系数 K (保留4位小数) + */ + public static double calculateK(FlowProps flowProps){ + double Jdccd; + switch (flowProps.getdPipeType()) { + + case 0: + Jdccd = 0.029F; + break; + case 1: + case 2: + case 3: + Jdccd = 0.075F; + break; + case 4: + Jdccd = 0.1F; + break; + case 5: + Jdccd = 0.15F; + break; + case 6: + Jdccd = 1; + break; + case 7: + Jdccd = 2.1F; + break; + case 8: + Jdccd = 0.04F; + break; + case 9: + Jdccd = 0.15F; + break; + case 10: + Jdccd = 0.13F; + break; + case 11: + Jdccd = 0.25F; + break; + default: + // 处理未知类型(可选) + throw new IllegalArgumentException("未知的管道类型: "); + } + return Jdccd; + } + + /** + * 计算管道粗糙度修正系数 G_me (GB/T 21446-2008 附录C) + * @param D_pipe 管道内径 (mm) + * @param K 绝对粗糙度 (mm) + * @param C 未修正的流出系数 + * @return 粗糙度修正系数 G_me (保留4位小数) + */ + public static double calculateRoughnessFactor(double D_pipe, double K, double C) { + // 计算相对粗糙度 K/D + double K_over_D = K / D_pipe; + + // 判断是否需要修正 + if (K_over_D <= 0.0004) { + return 1.0000; + } + + // 计算修正项 + double term = (K_over_D * 1e6) - 400; // 转换为无量纲项 + if (term < 0) { + throw new IllegalArgumentException("K/D 超出修正公式适用范围"); + } + + double G_me = 1 + (0.011 / C) * Math.sqrt(term); + return FormatUtil.format(G_me, Optional.of(4)); // 保留四位小数 + } + + /** + * 热膨胀修正(GB/T 21446-2008 第6.2.1节) + * @param flowProps 流量参数 + */ + public static void thermalExpansionCorrection(FlowProps flowProps) { + // 孔板孔径修正 + double dOrificeMaterial = flowProps.getdOrificeMaterial(); // 材料膨胀系数 (e.g. 16e-6/℃) + double dOrificeD = flowProps.getdOrificeD(); // 初始孔径(mm) + double dOrificeD_corrected = dOrificeD * (1 + 0.000001*dOrificeMaterial * (flowProps.getdTf() - 293.15)); // 20℃为参考温度 + flowProps.setdOrificeD(FormatUtil.format(dOrificeD_corrected, Optional.of(2))); + + // 管道内径修正 + double dPipeMaterial = flowProps.getdPipeMaterial(); + double dPipeD = flowProps.getdPipeD(); + double dPipeD_corrected = dPipeD * (1 + 0.000001*dPipeMaterial * (flowProps.getdTf() - 293.15)); + flowProps.setdPipeD(FormatUtil.format(dPipeD_corrected,Optional.of(2))); + + // 更新直径比β + flowProps.setdBeta( FormatUtil.format(flowProps.getdOrificeD() / flowProps.getdPipeD(),Optional.of(4))); + } + + /** + * 计算渐近速度系数E(GB/T 21446-2008 式(8)) + */ + public static void calculateE(FlowProps flowProps) { + double beta = flowProps.getdBeta(); + flowProps.setdE( FormatUtil.format(1 / Math.sqrt(1 - Math.pow(beta, 4)),Optional.of(4))); + } + + /** + * 计算相对密度系数FG(GB/T 21446-2008 式(9)) + */ + public static void calculateFG(FlowProps flowProps,GasProps gasProps) { + gasProps.setdRD_Real(gasProps.getdRD_Real()); + flowProps.setdFG( FormatUtil.format(1 / Math.sqrt(gasProps.dRD_Real),Optional.of(4))); + } + + /** + * 计算流动温度系数FT(GB/T 21446-2008 式(10)) + */ + public static void calculateFT(FlowProps flowProps) { + double Tb = flowProps.getdTb_M() ; // 参比温度转K + double Tf = flowProps.getdTf() ; // 工况温度转K + flowProps.setdFT( FormatUtil.format(Math.sqrt(Tb / Tf),Optional.of(4))); + } + + /** + * 计算可膨胀系数ε(GB/T 21446-2008 式(11)) + */ + public static void calculateEpsilon(FlowProps flowProps, GasProps gasProps) { + double P1 = flowProps.getdPf(); // 上游绝对压力(Pa) + double deltaP = flowProps.getdDp(); // 差压(Pa) + double beta = flowProps.getdBeta(); + double kappa = flowProps.getdKappa(); + + double tau = FormatUtil.format((P1 - deltaP) / P1, Optional.of(4)); // 压力比 + double epsilon=1 - (0.351 + 0.256 * Math.pow(beta, 4) + 0.93 * Math.pow(beta, 8)) * (1 - Math.pow(tau, 1/kappa)); + + flowProps.setdDExpCoefficient( FormatUtil.format(epsilon,Optional.of(4))); + } + /** + * 计算等熵指数κ(GB/T 21446-2008 推荐方法) + * @param gasProps 气体参数(需包含等熵指数或热容比) + * @return 等熵指数κ + */ + public static double calculateKappa(GasProps gasProps) { + // 若已通过AGA 8或其他方法计算,直接返回 +// if (gasProps.getdKappa() > 0) { +// return gasProps.getdKappa(); +// } + + // 近似公式:基于理想气体比热比和压缩因子修正 + double gamma = 1.3; // 天然气典型比热比(Cp/Cv≈1.3) + double Z = gasProps.getdZf(); // 工况压缩因子 + + // 修正公式(经验关系) + double kappa = gamma / (1 - (gamma - 1) * (1 / Z - 1)); + return FormatUtil.format(kappa,Optional.of(4)); + } + + + + /** + * 迭代计算流量(GB/T 21446-2008 第7章) + * @param flowProps 流量参数 + * @param gasProps 气体参数 + */ + public static void iterativeFlowCalculation(FlowProps flowProps, GasProps gasProps) { + // 0. 单位转换 + double D = flowProps.getdPipeD() / 1000.0; // 管道内径(m) + double d = flowProps.getdOrificeD() / 1000.0; // 孔板孔径(m) + double beta = flowProps.getdBeta(); + double P1 = flowProps.getdPf(); // 绝对压力(Pa) + double deltaP = flowProps.getdDp(); // 差压(Pa) + double Tf = flowProps.getdTf() ; // 工况温度(K) + + flowProps.setdPb_M(gasProps.getdPb()); + flowProps.setdTb_M(gasProps.getdTb()); + flowProps.setdFpv(FormatUtil.format(gasProps.dFpv,Optional.of(4))); + + // 1. 计算中间参数 + calculateE(flowProps); + calculateFG(flowProps,gasProps); + calculateFT(flowProps); + + flowProps.setdKappa( calculateKappa(gasProps)); +// flowProps.setdDViscosity( calculateDynamicViscosity(gasProps)); + flowProps.setdDViscosity(FormatUtil.format( Dlndjs(flowProps.getdPf()/1e6,flowProps.getdTf()),Optional.of(5))); + calculateEpsilon(flowProps, gasProps); +// +// flowProps.setdKappa(1.357); +// flowProps.setdBeta(0.5792); +// flowProps.setdFpv(1.0195); +// flowProps.setdFG(1.2531); +// flowProps.setdE(1.0615); +// flowProps.setdDExpCoefficient(0.9977); +// flowProps.setdFT(1.0086); + + + // 2. 初始雷诺数估算(假设初始C=0.6) + double C_initial = 0.6; + double Qf_initial = (C_initial * flowProps.getdE() * flowProps.getdDExpCoefficient() * Math.PI * Math.pow(d, 2) / 4) + * Math.sqrt(2 * deltaP / (gasProps.dRhof * (1 - Math.pow(beta, 4)))); + flowProps.setdVFlowf(Qf_initial); // 初始工况流量(m³/s) + + // 3. 迭代参数 + double tolerance = 1e-6; + int maxIter = 100; + double currentC = C_initial; + double currentReD = calculateReD(Qf_initial, D, gasProps.dRhof, flowProps.getdDViscosity()); + int iter = 0; + double prevC=0; + // 4. 迭代循环 + do { + prevC = currentC; + + // 4.1 计算流出系数C(GB/T 21446-2008 附录A) +// currentC =FormatUtil.format( calculateCd(beta, currentReD, flowProps.getdPipeD(), flowProps.getdPtmode()),Optional.of(4)); + currentC = calculateCd(beta, currentReD, flowProps.getdPipeD(), flowProps.getdPtmode()); + + // 4.2 更新流量 +// double Qf =FormatUtil.format ((currentC * flowProps.getdDExpCoefficient() * Math.PI * Math.pow(d, 2) / 4) +// * Math.sqrt(2 * deltaP / (gasProps.dRhof * (1 - Math.pow(beta, 4)))),Optional.of(4)); + double Qf =(currentC * flowProps.getdDExpCoefficient() * Math.PI * Math.pow(d, 2) / 4) + * Math.sqrt(2 * deltaP / (gasProps.dRhof * (1 - Math.pow(beta, 4)))); + flowProps.setdVFlowf(Qf); + + // 4.3 更新雷诺数 +// currentReD =FormatUtil.format( calculateReD(Qf, D, gasProps.dRhof, flowProps.getdDViscosity()), Optional.of(2)); + currentReD = calculateReD(Qf, D, gasProps.dRhof, flowProps.getdDViscosity()); + + iter++; + if (iter > maxIter) { + throw new RuntimeException("迭代未收敛,超过最大迭代次数!"); + } + } while (Math.abs(currentC - prevC) / currentC > tolerance); + // 5. 保存最终结果 + + // 在迭代计算流出系数后,添加粗糙度修正 + + double K = calculateK(flowProps); // 根据实际管道类型选择 + double G_me = calculateRoughnessFactor(flowProps.getdPipeD(), K, currentC); + double C_corrected = FormatUtil.format(currentC * G_me, Optional.of(4)); + + flowProps.setdCd(C_corrected); + flowProps.setdRoughNessPipe(G_me); + flowProps.setdRnPipe(currentReD); + + + + + // 6. 计算标况流量(GB/T 21446-2008 式(1)) + double Qn = flowProps.getdVFlowf() * (flowProps.getdFpv()*flowProps.getdFpv() * P1 / flowProps.getdPb_M()) + * (flowProps.getdTb_M()) / Tf; + flowProps.setdVFlowb(FormatUtil.format(Qn,Optional.of(4))); + + //标况质量流量 + flowProps.setdMFlowb(FormatUtil.format(flowProps.getdVFlowb() * gasProps.dRhob,Optional.of(4))); + //标况能量流量 + flowProps.setdEFlowb(FormatUtil.format(flowProps.getdVFlowb() * gasProps.dHhvv,Optional.of(4))); + //管道内天然气流速 + flowProps.setdVelocityFlow(FormatUtil.format(flowProps.getdVFlowf() / (Math.PI * Math.pow((flowProps.getdPipeD() / 2000), 2)),Optional.of(3))); + //压力损失 + flowProps.setdPressLost(FormatUtil.format(YaLiSunShi(flowProps.getdCd(), flowProps.getdBeta(), flowProps.getdDp(), flowProps.getdCoreType()),Optional.of(2))); + + } + + + + + + /** + * 计算天然气动力粘度(Sutherland公式简化版,适用于低压天然气) + * @param gasProps 气体参数(需包含温度、密度、组分) + * @return 动力粘度 (Pa·s) + */ + public static double calculateDynamicViscosity(GasProps gasProps) { + double T = gasProps.getdTf() ; // 工况温度(K) + double rho = gasProps.getdRhof(); // 工况密度(kg/m³) + double Rd = gasProps.getdRD_Real(); // 真实相对密度 + + // Sutherland常数(天然气典型值,C=120) + double C = 120.0; // Sutherland常数 + double mu0 = 1.0e-5; // 参考粘度 (Pa·s,@293.15K) + + // 计算粘度(简化公式,忽略压力修正) + double mu = mu0 * Math.pow(T / 293.15, 0.7) * (293.15 + C) / (T + C); + + // 密度修正(经验公式) + mu *= 1 + 0.001 * (rho - 1.2); // 密度对粘度的粗略修正 + + return FormatUtil.format( mu*1000,Optional.of(5)); + } + /** + * 计算雷诺数ReD(GB/T 21446-2008 式(5)) + */ + public static double calculateReD(double Qf, double D, double rho, double mu) { + return FormatUtil.format( (4 * Qf * rho*1000) / (Math.PI * D * mu),Optional.of(2)); // Qf: m³/s, D: m, rho: kg/m³, mu: Pa·s + } + + /** + * 计算流出系数C(GB/T 21446-2008 附录A) + */ + public static double calculateCd(double beta, double ReD, double D_mm, int ptMode) { + double L1, L2; + // 根据取压方式确定L1/L2(角接取压) + switch (ptMode) { + case 1: // 角接取压 + L1 = L2 = 0; // D单位为mm + break; + case 0: // 法兰取压 + L1 = L2 = 25.4 / D_mm; + break; + case 2: // D-D/2取压 + L1 = 1.0; + L2 = 0.47; + break; + default: + throw new IllegalArgumentException("不支持的取压方式"); + } + + double term1 = 0.5961 + 0.0261 * Math.pow(beta, 2) - 0.216 * Math.pow(beta, 8); + double term2 = 0.000521 * Math.pow(1e6 * beta / ReD, 0.7); + double A = Math.pow(19000 * beta / ReD, 0.8); + double term3 = (0.0188 + 0.0063 * A) * Math.pow(beta, 3.5) * Math.pow(1e6 / ReD, 0.3); + double term4 = (0.043 + 0.08 * Math.exp(-10 * L1) - 0.123 * Math.exp(-7 * L1)) + * (1 - 0.11 * A) * Math.pow(beta, 4) / (1 - Math.pow(beta, 4)); + double term5 = -0.031 * (2 * L2 / (1 - beta) - 0.8 * Math.pow(2 * L2 / (1 - beta), 1.1)) + * Math.pow(beta, 1.3); + + double Cd = term1 + term2 + term3 + term4 + term5; + + // 孔径<71.12mm修正 + if (D_mm < 71.12) { + Cd += 0.011 * (0.75 - beta) * (2.8 - D_mm / 25.4); + } + return FormatUtil.format(Cd,Optional.of(4)); + } + //查表计算粘度μ + public static double Dlndjs(double tempP_jy, double tempT) + { double[][] Dlndjs_Dlnd_Data = new double[8][11]; + double[] Dlndjs_Dlnd_T = new double[8]; + double[] Dlndjs_Dlnd_P = new double[11]; + double s1 ; + double s2 ; + double ky ; + double kx ; + int i ; + int m = 0; + int n = 0; + //On Error Resume Next VBConversions Warning: On Error Resume Next not supported in C# + Dlndjs_Dlnd_T[0] = -15 + 273.15; + Dlndjs_Dlnd_T[1] = 0 + 273.15; + Dlndjs_Dlnd_T[2] = 15 + 273.15; + Dlndjs_Dlnd_T[3] = 30 + 273.15; + Dlndjs_Dlnd_T[4] = 45 + 273.15; + Dlndjs_Dlnd_T[5] = 60 + 273.15; + Dlndjs_Dlnd_T[6] = 75 + 273.15; + Dlndjs_Dlnd_T[7] = 90 + 273.15; + + Dlndjs_Dlnd_P[0] = 0.1F; + Dlndjs_Dlnd_P[1] = 1; + Dlndjs_Dlnd_P[2] = 2; + Dlndjs_Dlnd_P[3] = 3; + Dlndjs_Dlnd_P[4] = 4; + Dlndjs_Dlnd_P[5] = 5; + Dlndjs_Dlnd_P[6] = 6; + Dlndjs_Dlnd_P[7] = 7; + Dlndjs_Dlnd_P[8] = 8; + Dlndjs_Dlnd_P[9] = 9; + Dlndjs_Dlnd_P[10] = 10; + Dlndjs_Dlnd_Data[0][0] = 976; + Dlndjs_Dlnd_Data[1][0] = 1027; + Dlndjs_Dlnd_Data[2][0] = 1071; + Dlndjs_Dlnd_Data[3][0] = 1123; + Dlndjs_Dlnd_Data[4][0] = 1167; + Dlndjs_Dlnd_Data[5][0] = 1213; + Dlndjs_Dlnd_Data[6][0] = 1260; + Dlndjs_Dlnd_Data[7][0] = 1303; + Dlndjs_Dlnd_Data[0][1] = 991; + Dlndjs_Dlnd_Data[1][1] = 1040; + Dlndjs_Dlnd_Data[2][1] = 1082; + Dlndjs_Dlnd_Data[3][1] = 1135; + Dlndjs_Dlnd_Data[4][1] = 1178; + Dlndjs_Dlnd_Data[5][1] = 1224; + Dlndjs_Dlnd_Data[6][1] = 1270; + Dlndjs_Dlnd_Data[7][1] = 1312; + Dlndjs_Dlnd_Data[0][2] = 1014; + Dlndjs_Dlnd_Data[1][2] = 1063; + Dlndjs_Dlnd_Data[2][2] = 1106; + Dlndjs_Dlnd_Data[3][2] = 1153; + Dlndjs_Dlnd_Data[4][2] = 1196; + Dlndjs_Dlnd_Data[5][2] = 1239; + Dlndjs_Dlnd_Data[6][2] = 1281; + Dlndjs_Dlnd_Data[7][2] = 1323; + Dlndjs_Dlnd_Data[0][3] = 1044; + Dlndjs_Dlnd_Data[1][3] = 1091; + Dlndjs_Dlnd_Data[2][3] = 1127; + Dlndjs_Dlnd_Data[3][3] = 1174; + Dlndjs_Dlnd_Data[4][3] = 1216; + Dlndjs_Dlnd_Data[5][3] = 1257; + Dlndjs_Dlnd_Data[6][3] = 1297; + Dlndjs_Dlnd_Data[7][3] = 1338; + Dlndjs_Dlnd_Data[0][4] = 1073; + Dlndjs_Dlnd_Data[1][4] = 1118; + Dlndjs_Dlnd_Data[2][4] = 1149; + Dlndjs_Dlnd_Data[3][4] = 1195; + Dlndjs_Dlnd_Data[4][4] = 1236; + Dlndjs_Dlnd_Data[5][4] = 1275; + Dlndjs_Dlnd_Data[6][4] = 1313; + Dlndjs_Dlnd_Data[7][4] = 1352; + Dlndjs_Dlnd_Data[0][5] = 1114; + Dlndjs_Dlnd_Data[1][5] = 1151; + Dlndjs_Dlnd_Data[2][5] = 1180; + Dlndjs_Dlnd_Data[3][5] = 1224; + Dlndjs_Dlnd_Data[4][5] = 1261; + Dlndjs_Dlnd_Data[5][5] = 1297; + Dlndjs_Dlnd_Data[6][5] = 1333; + Dlndjs_Dlnd_Data[7][5] = 1372; + Dlndjs_Dlnd_Data[0][6] = 1156; + Dlndjs_Dlnd_Data[1][6] = 1185; + Dlndjs_Dlnd_Data[2][6] = 1211; + Dlndjs_Dlnd_Data[3][6] = 1253; + Dlndjs_Dlnd_Data[4][6] = 1287; + Dlndjs_Dlnd_Data[5][6] = 1320; + Dlndjs_Dlnd_Data[6][6] = 1352; + Dlndjs_Dlnd_Data[7][6] = 1391; + Dlndjs_Dlnd_Data[0][7] = 1207; + Dlndjs_Dlnd_Data[1][7] = 1230; + Dlndjs_Dlnd_Data[2][7] = 1250; + Dlndjs_Dlnd_Data[3][7] = 1289; + Dlndjs_Dlnd_Data[4][7] = 1318; + Dlndjs_Dlnd_Data[5][7] = 1346; + Dlndjs_Dlnd_Data[6][7] = 1374; + Dlndjs_Dlnd_Data[7][7] = 1412; + Dlndjs_Dlnd_Data[0][8] = 1261; + Dlndjs_Dlnd_Data[1][8] = 1276; + Dlndjs_Dlnd_Data[2][8] = 1289; + Dlndjs_Dlnd_Data[3][8] = 1324; + Dlndjs_Dlnd_Data[4][8] = 1350; + Dlndjs_Dlnd_Data[5][8] = 1373; + Dlndjs_Dlnd_Data[6][8] = 1396; + Dlndjs_Dlnd_Data[7][8] = 1432; + Dlndjs_Dlnd_Data[0][9] = 1331; + Dlndjs_Dlnd_Data[1][9] = 1331; + Dlndjs_Dlnd_Data[2][9] = 1335; + Dlndjs_Dlnd_Data[3][9] = 1366; + Dlndjs_Dlnd_Data[4][9] = 1385; + Dlndjs_Dlnd_Data[5][9] = 1403; + Dlndjs_Dlnd_Data[6][9] = 1424; + Dlndjs_Dlnd_Data[7][9] = 1456; + Dlndjs_Dlnd_Data[0][10] = 1405; + Dlndjs_Dlnd_Data[1][10] = 1389; + Dlndjs_Dlnd_Data[2][10] = 1383; + Dlndjs_Dlnd_Data[3][10] = 1409; + Dlndjs_Dlnd_Data[4][10] = 1421; + Dlndjs_Dlnd_Data[5][10] = 1435; + Dlndjs_Dlnd_Data[6][10] = 1451; + Dlndjs_Dlnd_Data[7][10] = 1482; + + if (tempT < Dlndjs_Dlnd_T[0]) + { + tempT = Dlndjs_Dlnd_T[0]; + } + if (tempT > Dlndjs_Dlnd_T[7]) + { + tempT = Dlndjs_Dlnd_T[7]; + } + if (tempP_jy < Dlndjs_Dlnd_P[0]) + { + tempP_jy = Dlndjs_Dlnd_P[0]; + } + if (tempP_jy > Dlndjs_Dlnd_P[10]) + { + tempP_jy = Dlndjs_Dlnd_P[10]; + } + + for ( i = 0; i <= 6; i++) + { + if (tempT >= Dlndjs_Dlnd_T[i] && tempT <= Dlndjs_Dlnd_T[i + 1]) + { + m = i; + break; + } + } + + for (i = 0; i <= 9; i++) + { + if (tempP_jy >= Dlndjs_Dlnd_P[i] && tempP_jy <= Dlndjs_Dlnd_P[i + 1]) + { + n = i; + break; + } + } + + if (Dlndjs_Dlnd_P[n + 1] - Dlndjs_Dlnd_P[n] != 0) + { + ky = (tempP_jy - Dlndjs_Dlnd_P[n]) / (Dlndjs_Dlnd_P[n + 1] - Dlndjs_Dlnd_P[n]); + } + else + { + ky = 0; + } + if (Dlndjs_Dlnd_T[m + 1] - Dlndjs_Dlnd_T[m] != 0) + { + kx = (tempT - Dlndjs_Dlnd_T[m]) / (Dlndjs_Dlnd_T[m + 1] - Dlndjs_Dlnd_T[m]); + } + else + { + kx = 0; + } + s1 = Dlndjs_Dlnd_Data[m][n] + (Dlndjs_Dlnd_Data[m][n + 1] - Dlndjs_Dlnd_Data[m][n]) * ky; + s2 = Dlndjs_Dlnd_Data[m + 1][n] + (Dlndjs_Dlnd_Data[m + 1][n + 1] - Dlndjs_Dlnd_Data[m + 1][n]) * ky; + return (s1 + (s2 - s1) * kx) / 100000.0D; + } + //可膨胀系数计算 + private double KePenZhang_JiSuan(double tempP_jy, double tempDp_Pa, double tempZjb, double tempDszs, int JIeliuType, int JiSuanBiaoZhun) // 求可膨胀系数 + { + double returnValue = 0; + //0标准孔板 + //1ISA1932喷嘴 + //2长径喷嘴 + //3文丘里喷嘴 + //4粗铸收缩段经典文丘里管 + //5机械加工收缩段经典文丘里管 + //6粗焊铁板收缩段经典文丘里管 + //7 1/4圆孔板 + + double tuo ; + switch (JIeliuType) + { + case 0: //孔板流量计算 + switch (JiSuanBiaoZhun) + { + case 0: //6143-2004 + tuo = (tempP_jy - tempDp_Pa) / (tempP_jy ); + returnValue = 1 - (0.351 + 0.256 * Math.pow(tempZjb, 4) + 0.93 * Math.pow(tempZjb, 8)) * (1 - Math.pow(tuo, (1 / tempDszs))); + break; + case 1: //6143-1996 + returnValue = 1 - (0.41 + 0.35 * Math.pow(tempZjb, 4)) * tempDp_Pa / ( tempP_jy * tempDszs); + break; + } + break; + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + switch (JiSuanBiaoZhun) + { + case 0: + //标准喷嘴 iso5167-2002 + tuo = (tempP_jy - tempDp_Pa) / (tempP_jy ); + returnValue = Math.pow((((tempDszs * Math.pow(tuo, (2 / tempDszs))) / (tempDszs - 1)) * ((1 - Math.pow(tempZjb, 4)) / (1 - Math.pow(tempZjb, 4) * Math.pow(tuo, (2 / tempDszs)))) * ((1 - Math.pow(tuo, ((tempDszs - 1) / tempDszs))) / (1 - tuo))), 0.5); + break; + + case 1: //iso5167-93 + returnValue = 1 - (0.41 + 0.35 * Math.pow(tempZjb, 4)) * tempDp_Pa / ( tempP_jy * tempDszs); + break; + } + break; + case 7: //1/4圆孔板 + returnValue = 1 - (0.41 + 0.35 * Math.pow(tempZjb, 4)) * tempDp_Pa / ( tempP_jy * tempDszs); + break; + case 8: //锥形入口孔板 + tuo = (tempP_jy - tempDp_Pa) / (tempP_jy ); + returnValue = 1 - (0.351 + 0.256 * Math.pow(tempZjb, 4) + 0.93 * Math.pow(tempZjb, 8)) * (1 - Math.pow(tuo, (1 / tempDszs))); + + tuo = (tempP_jy - tempDp_Pa) / (tempP_jy ); + + returnValue = 0.5 * (returnValue + Math.pow((((tempDszs * Math.pow(tuo, (2 / tempDszs))) / (tempDszs - 1)) * ((1 - Math.pow(tempZjb, 4)) / (1 - Math.pow(tempZjb, 4) * Math.pow(tuo, (2 / tempDszs)))) * ((1 - Math.pow(tuo, ((tempDszs - 1) / tempDszs))) / (1 - tuo))), 0.5)); + break; + case 9: //偏心孔板 + returnValue = 1 - (0.41 + 0.35 * Math.pow(tempZjb, 4)) * tempDp_Pa / ( tempP_jy * tempDszs); + break; + + } + return returnValue; + } + //流出系数计算 + public final double C_JiSuan(double tempGj, double tempZjb, double tempReD, double tempconQvA, int tempQyfs, int JieLiuType, int JiSuanBiaoZhun) + { + double returnValue = 0; + //流出系数计算函数 + //输入:直径比,雷诺数,取压方式,节流装置类型,计算采用标准 + //输出:流出系数 + + //jieliutype + + //0标准孔板 + //1ISA1932喷嘴 + //2长径喷嘴 + //3文丘里喷嘴 + //4粗铸收缩段经典文丘里管 + //5机械加工收缩段经典文丘里管 + //6粗焊铁板收缩段经典文丘里管 + + double L1 = 0; + double L2 = 0; + switch (JieLiuType) + { + case 0: //孔板 + switch (tempQyfs) + { + case 0: + L1 = 25.4 / tempGj; + L2 = L1; + break; + case 1: + L1 = 0; + L2 = 0; + break; + case 2: + L1 = 1; + L2 = 0.47F; + break; + } + switch (JiSuanBiaoZhun) + { + case 0: //6143-2004 + if (tempGj >= 71.12) + { + returnValue = 0.5961 + 0.0261 * Math.pow(tempZjb, 2) - 0.216 * Math.pow(tempZjb, 8) + 0.000521 * Math.pow((1000000.0D * tempZjb / tempReD), 0.7) + (0.0188 + 0.0063 * Math.pow((19000 * tempZjb / tempReD), 0.8)) * Math.pow(tempZjb, 3.5) * Math.pow((1000000.0D / tempReD), 0.3) + (0.043 + 0.08 * Math.exp(-10 * L1) - 0.123 * Math.exp(-7 * L1)) * (1 - 0.11 * Math.pow((19000 * tempZjb / tempReD), 0.8)) * (Math.pow(tempZjb, 4) * Math.pow((1 - Math.pow(tempZjb, 4)), (-1))) - 0.031 * (2 * L2 / (1 - tempZjb) - 0.8 * Math.pow((2 * L2 / (1 - tempZjb)), 1.1)) * Math.pow(tempZjb, 1.3); + } + else if (tempGj < 71.12) + { + returnValue = (0.5961 + 0.0261 * Math.pow(tempZjb, 2) - 0.216 * Math.pow(tempZjb, 8) + 0.000521 * Math.pow((1000000.0D * tempZjb / tempReD), 0.7) + (0.0188 + 0.0063 * Math.pow((19000 * tempZjb / tempReD), 0.8)) * Math.pow(tempZjb, 3.5) * Math.pow((1000000.0D / tempReD), 0.3) + (0.043 + 0.08 * Math.exp(-10 * L1) - 0.123 * Math.exp(-7 * L1)) * (1 - 0.11 * Math.pow((19000 * tempZjb / tempReD), 0.8)) * Math.pow(tempZjb, 4) * Math.pow((1 - Math.pow(tempZjb, 4)), (-1)) - 0.031 * (2 * L2 / (1 - tempZjb) - 0.8 * Math.pow((2 * L2 * (1 - tempZjb)), 1.1)) * Math.pow(tempZjb, 1.3) + 0.011 * (0.75 - tempZjb) * (2.8 - tempGj / 25.4)); + } + break; + case 1: //6143-1996 + if (0.09 * L1 >= 0.039) + { + returnValue = 0.5959 + 0.0312 * Math.pow(tempZjb, (2.1)) - 0.184 * Math.pow(tempZjb, 8) + 0.0029 * Math.pow(tempZjb, 2.5) * Math.pow((1000000.0D / tempReD), 0.75) + 0.039 * Math.pow(tempZjb, 4) * Math.pow((1 - Math.pow(tempZjb, 4)), (-1)) - 0.0337 * L1 * Math.pow(tempZjb, 3); + } + else if (0.09 * L1 < 0.039) + { + returnValue = (0.5959 + 0.0312 * Math.pow(tempZjb, (2.1)) - 0.184 * Math.pow(tempZjb, 8) + 0.0029 * Math.pow(tempZjb, 2.5) * Math.pow((1000000.0D / tempReD), 0.75) + 0.09 * L1 * Math.pow(tempZjb, 4) * Math.pow((1 - Math.pow(tempZjb, 4)), (-1)) - 0.0337 * L1 * Math.pow(tempZjb, 3)); + } + break; + } + break; + case 1: //ISA1932喷嘴 + returnValue = (0.99 - 0.2262 * Math.pow(tempZjb, 4.1) - (0.00175 * Math.pow(tempZjb, 2) - 0.0033 * Math.pow(tempZjb, 4.15)) * (1000000.0D / Math.pow(tempReD, 1.15))); + break; + + case 2: //长径喷嘴 + returnValue = (0.9965 - 0.00653 * Math.pow(tempZjb, 0.5) * Math.pow((1000000.0D / tempReD), 0.5)); + break; + case 3: //文丘里喷嘴 + returnValue = (0.9858 - 0.196 * Math.pow(tempZjb, 4.5)); + break; + case 4: //粗铸收缩段经典文丘里管 + returnValue = (0.984F); + break; + case 5: //机械加工收缩段经典文丘里管 + returnValue = (0.995F); + break; + case 6: //粗焊铁板收缩段经典文丘里管 + returnValue = (0.985F); + break; + case 7: //1/4圆孔板 + returnValue = 0.73823 - 0.3309 * tempZjb - 1.1615 * Math.pow(tempZjb, 2) + 1.5084 * Math.pow(tempZjb, 3); + break; + case 8: //锥形入口孔板 + returnValue = (0.734F); + break; + case 9: //偏心孔板 + returnValue = 0.9355 - 1.6889 * tempZjb + 3.0428 * Math.pow(tempZjb, 2) - 1.7989 * Math.pow(tempZjb, 3); + break; + } + double tempRed1 ; + switch (JieLiuType) + { + case 0: //孔板流量计算 + tempRed1 = tempconQvA * returnValue; + switch (JieLiuType) + { + case 0: + if (tempRed1 < (170 * Math.pow(tempZjb, 2) * tempGj)) + { + JOptionPane.showMessageDialog(null, "雷诺数超过标准孔板的使用范围!停止计算!", "提示", JOptionPane.PLAIN_MESSAGE); + return returnValue; + } + break; + case 1: + if (tempZjb >= 0.1 & tempZjb <= 0.56) + { + if (tempRed1 < 5000) + { + JOptionPane.showMessageDialog(null, "雷诺数超过标准喷嘴的使用范围!停止计算!", "提示", JOptionPane.PLAIN_MESSAGE); + return returnValue; + } + } + if (tempZjb > 0.56) + { + if (tempRed1 < (16000 * Math.pow(tempZjb, 2) * tempGj)) + { + JOptionPane.showMessageDialog(null, "雷诺数超过标准喷嘴的使用范围!停止计算!", "提示", JOptionPane.PLAIN_MESSAGE); + return returnValue; + } + } + break; + } + break; + + case 1: //标准喷嘴 + tempRed1 = tempconQvA * returnValue; + if (tempZjb >= 0.3 & tempZjb < 0.44) + { + if (tempRed1 < 70000 | tempRed1 > 10000000.0D) + { + JOptionPane.showMessageDialog(null, "雷诺数超过标准喷嘴的使用范围!停止计算!", "提示", JOptionPane.PLAIN_MESSAGE); + } + } + if (tempZjb >= 0.44 & tempZjb < 0.8) + { + if (tempRed1 < 20000 | tempRed1 > 10000000.0D) + { + JOptionPane.showMessageDialog(null, "雷诺数超过标准喷嘴的使用范围!停止计算!", "提示", JOptionPane.PLAIN_MESSAGE); + } + } + break; + case 2: //长径喷嘴 + tempRed1 = tempconQvA * returnValue; + if (tempRed1 < 10000.0D | tempRed1 > 10000000.0D) + { + JOptionPane.showMessageDialog(null, "雷诺数超过长径喷嘴的使用范围!停止计算!", "提示", JOptionPane.PLAIN_MESSAGE); + } + break; + case 3: //文丘里喷嘴 + tempRed1 = tempconQvA * returnValue; + if (tempRed1 < 150000.0D | tempRed1 > 2000000.0D) + { + JOptionPane.showMessageDialog(null, "雷诺数超过文丘里喷嘴的使用范围!停止计算!", "提示", JOptionPane.PLAIN_MESSAGE); + } + break; + case 4: //粗铸收缩段经典文丘里管 + tempRed1 = tempconQvA * returnValue; + if (tempRed1 < 200000.0D | tempRed1 > 2000000.0D) + { + JOptionPane.showMessageDialog(null, "雷诺数超过粗铸收缩段经典文丘里管的使用范围!停止计算!", "提示", JOptionPane.PLAIN_MESSAGE); + return returnValue; + } + break; + case 5: //机械加工收缩段经典文丘里管 + tempRed1 = tempconQvA * returnValue; + if (tempRed1 < 200000.0D | tempRed1 > 1000000.0D) + { + JOptionPane.showMessageDialog(null, "雷诺数超过机械加工收缩段经典文丘里管的使用范围!停止计算!", "提示", JOptionPane.PLAIN_MESSAGE); + } + break; + case 6: //粗焊铁板收缩段经典文丘里管 + tempRed1 = tempconQvA * returnValue; + if (tempRed1 < 200000.0D | tempRed1 > 2000000.0D) + { + JOptionPane.showMessageDialog(null, "雷诺数超过粗焊铁板收缩段经典文丘里管的使用范围!停止计算!", "提示", JOptionPane.PLAIN_MESSAGE); + } + break; + + //标准孔板 + //ISA1932喷嘴 + //长径喷嘴 + //文丘里喷嘴 + //粗铸收缩段经典文丘里管 + //机械加工收缩段经典文丘里管 + //粗焊铁板收缩段经典文丘里管 + + + + + } + return returnValue; + } + + private double BkTable(double temPrk, double tempKj, int tempBkjsff) { + double[] BkTable_x = new double[10]; + double[] BkTable_Y = new double[10]; + //On Error Resume Next VBConversions Warning: On Error Resume Next not supported in C# + if (tempBkjsff == 1) { + double tempRkBiKj = temPrk / tempKj; + // static double[] x = new double[10]; //VBConversions Note: Static variable moved to class level and renamed BkTable_x. Local static variables are not supported in C#. + // static double[] Y = new double[10]; //VBConversions Note: Static variable moved to class level and renamed BkTable_Y. Local static variables are not supported in C#. + int i; + int xIndex = 0; + //If x(0) = 0 Then + BkTable_x[0] = 0.0004F; + BkTable_x[1] = 0.001F; + BkTable_x[2] = 0.002F; + BkTable_x[3] = 0.004F; + BkTable_x[4] = 0.006F; + BkTable_x[5] = 0.008F; + BkTable_x[6] = 0.01F; + BkTable_x[7] = 0.012F; + BkTable_x[8] = 0.014F; + BkTable_x[9] = 0.015F; + + BkTable_Y[0] = 1; + BkTable_Y[1] = 1.005F; + BkTable_Y[2] = 1.012F; + BkTable_Y[3] = 1.022F; + BkTable_Y[4] = 1.032F; + BkTable_Y[5] = 1.04F; + BkTable_Y[6] = 1.048F; + BkTable_Y[7] = 1.055F; + BkTable_Y[8] = 1.062F; + BkTable_Y[9] = 1.065F; + //End If + + if (tempRkBiKj <= 0.0004) { + return 1; + } + if (tempRkBiKj > 0.015) { + return 1.065F; + } + for (i = 0; i <= 8; i++) { + if (tempRkBiKj >= BkTable_x[i] && tempRkBiKj <= BkTable_x[i + 1]) { + xIndex = i; + break; + } + } + return BkTable_Y[xIndex] + (tempRkBiKj - BkTable_x[xIndex]) * (BkTable_Y[xIndex + 1] - BkTable_Y[xIndex]) / (BkTable_x[xIndex + 1] - BkTable_x[xIndex]); + } + return 0; + } + //管道粗糙度计算 + private double CcdXsjs(double tempPipeType, double tempGj, double tempZjb, double TempRed) + { + double returnValue ; + //粗糙度系数计算 + double Jdccd = 0; //绝对粗糙度 + double Xdccd ; //相对粗糙度 + //Dim CcdXs As single + double s1 ; + double s2 ; + double ky ; + double kx ; + int i ; + int m = 0; + int n = 0; + if (tempPipeType == 0) + { + Jdccd = 0.029F;} + else if (tempPipeType == 1) + { + Jdccd = 0.075F; + } + else if (tempPipeType == 2) + { + Jdccd = 0.075F; + } + else if (tempPipeType == 3) + { + Jdccd = 0.075F; + } + else if (tempPipeType == 4) + { + Jdccd = 0.1F; + } + else if (tempPipeType == 5) + { + Jdccd = 0.15F; + } + else if (tempPipeType == 6) + { + Jdccd = 1; + } + else if (tempPipeType == 7) + { + Jdccd = 2.1F; + } + else if (tempPipeType == 8) + { + Jdccd = 0.04F; + } + else if (tempPipeType == 9) + { + Jdccd = 0.15F; + } + else if (tempPipeType == 10) + { + Jdccd = 0.13F; + } + else if (tempPipeType == 11) + { + Jdccd = 0.25F; + } + Xdccd = tempGj / Jdccd; + if (Xdccd < 400) + { + JOptionPane.showMessageDialog(null, "粗糙度取得太高,粗略计算", "提示", JOptionPane.PLAIN_MESSAGE); + Xdccd = 400; + } + if (Xdccd >= 3400) + { + Xdccd = 3400; + } + + if (Xdccd < 3200 & Math.pow(tempZjb, 2) > 0.1 & Math.pow(tempZjb, 2) < 0.64) + { + + int[] Xdccdb = new int[10]; + double[] Btf = new double[8]; + double[][] CcdXsb = new double[10][8]; + + Xdccdb[0] = 400; + Xdccdb[1] = 800; + Xdccdb[2] = 1200; + Xdccdb[3] = 1600; + Xdccdb[4] = 2000; + Xdccdb[5] = 2400; + Xdccdb[6] = 2800; + Xdccdb[7] = 3200; + Xdccdb[8] = 3400; + + Btf[0] = 0.1F; + Btf[1] = 0.2F; + Btf[2] = 0.3F; + Btf[3] = 0.4F; + Btf[4] = 0.5F; + Btf[5] = 0.6F; + Btf[6] = 0.64F; + + CcdXsb[0][0] = 1.002; + CcdXsb[1][0] = 1; + CcdXsb[2][0] = 1; + CcdXsb[3][0] = 1; + CcdXsb[4][0] = 1; + CcdXsb[5][0] = 1; + CcdXsb[6][0] = 1; + CcdXsb[7][0] = 1; + CcdXsb[8][0] = 1; + CcdXsb[0][1] = 1.003; + CcdXsb[1][1] = 1.002; + CcdXsb[2][1] = 1.001; + CcdXsb[3][1] = 1; + CcdXsb[4][1] = 1; + CcdXsb[5][1] = 1; + CcdXsb[6][1] = 1; + CcdXsb[7][1] = 1; + + CcdXsb[0][2] = 1.006; + CcdXsb[1][2] = 1.004; + CcdXsb[2][2] = 1.002; + CcdXsb[3][2] = 1.001; + CcdXsb[4][2] = 1; + CcdXsb[5][2] = 1; + CcdXsb[6][2] = 1; + CcdXsb[7][2] = 1; + + CcdXsb[0][3] = 1.009; + CcdXsb[1][3] = 1.006; + CcdXsb[2][3] = 1.004; + CcdXsb[3][3] = 1.002; + CcdXsb[4][3] = 1.001; + CcdXsb[5][3] = 1; + CcdXsb[6][3] = 1; + CcdXsb[7][3] = 1; + + CcdXsb[0][4] = 1.014; + CcdXsb[1][4] = 1.009; + CcdXsb[2][4] = 1.006; + CcdXsb[3][4] = 1.004; + CcdXsb[4][4] = 1.002; + CcdXsb[5][4] = 1.001; + CcdXsb[6][4] = 1; + CcdXsb[7][4] = 1; + + CcdXsb[0][5] = 1.02; + CcdXsb[1][5] = 1.013; + CcdXsb[2][5] = 1.009; + CcdXsb[3][5] = 1.006; + CcdXsb[4][5] = 1.003; + CcdXsb[5][5] = 1.002; + CcdXsb[6][5] = 1; + CcdXsb[7][5] = 1; + + CcdXsb[0][6] = 1.024; + CcdXsb[1][6] = 1.016; + CcdXsb[2][6] = 1.011; + CcdXsb[3][6] = 1.007; + CcdXsb[4][6] = 1.004; + CcdXsb[5][6] = 1.002; + CcdXsb[6][6] = 1.002; + CcdXsb[7][6] = 1; + + + for (i = 0; i <= 8; i++) + { + if (Xdccd >= Xdccdb[i] && Xdccd <= Xdccdb[i + 1]) + { + m = i; + break; + } + } + for (i = 0; i <= 6; i++) + { + if (Math.pow(tempZjb, 2) >= Btf[i] && Math.pow(tempZjb, 2) <= Btf[i + 1]) + { + n = i; + break; + } + } + + ky = (Btf[n + 1] - Btf[n] != 0) ? ((Math.pow(tempZjb, 2) - Btf[n]) / (Btf[n + 1] - Btf[n])) : 0; + + kx = (Xdccdb[m + 1] - Xdccdb[m] != 0) ? ((Xdccd - Xdccdb[m]) / (Xdccdb[m + 1] - Xdccdb[m])) : 0; + + s1 = CcdXsb[m][n] + (CcdXsb[m][n + 1] - CcdXsb[m][n]) * ky; + s2 = CcdXsb[m + 1][n] + (CcdXsb[m + 1][n + 1] - CcdXsb[m + 1][n]) * ky; + returnValue = s1 + (s2 - s1) * kx; + + returnValue = TempRed > 1000000.0 ? returnValue : ((returnValue - 1) * Math.pow((Math.log10(TempRed) / 2), 2) + 1); + + } + else + { + returnValue = 1; + } + return returnValue; + } +} diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/FlowController.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/FlowController.java new file mode 100644 index 0000000..1df099b --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/FlowController.java @@ -0,0 +1,455 @@ +package com.ruoyi.caltools.controller; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.caltools.model.FlowProps; +import com.ruoyi.caltools.model.GasProps; +import com.ruoyi.caltools.service.DetailService; +import com.ruoyi.caltools.service.GBT11062Service; +import com.ruoyi.caltools.service.ThermService; +import com.ruoyi.system.controller.UnitConvert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static com.ruoyi.caltools.controller.DpFlowCalc.iterativeFlowCalculation; +import static com.ruoyi.caltools.controller.DpFlowCalc.thermalExpansionCorrection; + +@RestController +@RequestMapping("/flowCalTools") +public class FlowController { + @Autowired + private ThermService thermService; + @Autowired + private DetailService detailService; + @Autowired + private GBT11062Service gbt11062Service; + @Autowired + private GasController gasController; + @Autowired + private UnitConvert unitConvert; + @Autowired + private NozellFlowCalcISO9300 nozellFlowCalcISO9300; + @PostMapping("/flowCalc") + public AjaxResult flowCalc(@RequestBody FlowProps flowProps) { + GasProps gasProps = new GasProps(); + + + //大气压力转换成Pa + double tempPatm = unitConvert.ConvertUniter("pressure", flowProps.getdPatm(), flowProps.getdPatmUnit(), 0); + //压力转换成Pa + double tempPf = unitConvert.ConvertUniter("pressure", flowProps.getdPf(), flowProps.getdPfUnit(), 0); + + //压力转换成Pa + double tempDP = unitConvert.ConvertUniter("pressure", flowProps.getdDp(), flowProps.getdDpUnit(), 0); + //温度转换成K + double tempTf = unitConvert.ConvertUniter("temperature", flowProps.getdTf(), flowProps.getdTfUnit(), 2); + if (flowProps.getdPfType() == 0) //0是表压 + { + gasProps.dPf = tempPatm + tempPf; + flowProps.setdPf(tempPatm + tempPf); + } else { + gasProps.dPf = tempPf; + flowProps.setdPf(tempPf); + } + gasProps.dTf = tempTf; + flowProps.setdDp(tempDP); + flowProps.setdTf(tempTf); + + gasProps.dCbtj = flowProps.getdCbtj(); + switch (gasProps.dCbtj) + { + case 2: + gasProps.setdPb(101325); + gasProps.setdTb( 273.15); + flowProps.setdPb_M(101325); + flowProps.setdTb_M(273.15); + break; + case 1: + + gasProps.setdPb(101325); + gasProps.setdTb( 288.15); + flowProps.setdPb_M(101325); + flowProps.setdTb_M(288.15); + break; + case 0: + + gasProps.setdPb(101325); + gasProps.setdTb( 293.15); + flowProps.setdPb_M(101325); + flowProps.setdTb_M(293.15); + break; + } + + String[] stringArray = flowProps.getdngComponents().split("_"); + double[] doubleArray = new double[stringArray.length]; // 遍历字符串数组,将每个元素转换为 double 类型 + for (int i = 0; i < stringArray.length; i++) { + try { + doubleArray[i] = Double.parseDouble(stringArray[i]) / 100; + } catch (NumberFormatException e) { + // 处理转换异常 + System.err.println("无法将字符串 " + stringArray[i] + " 转换为 double 类型: " + e.getMessage()); + } + } + gasProps.adMixture = doubleArray; + GasController.ngCalcVoid(flowProps, gasProps); //计算临界流函数所有参数都计算了 + + //计算流量 + switch (flowProps.getdMeterType()) { + case 0: //差压式流量计 + // 执行计算 + thermalExpansionCorrection(flowProps); // 热膨胀修正 + iterativeFlowCalculation(flowProps, gasProps); + + //OFlowCal(gasProps,flowProps); + break; + case 1:// 速度式流量计 + SdFlowCal(gasProps, flowProps); + break; + case 2: //容积式流量计 + SdFlowCal(gasProps, flowProps); + break; + case 3:// 临界流函数流量计 + nozellFlowCalcISO9300. calculateFlowCoefficient(flowProps,gasProps); + break; + } + Object[] resultArray = {flowProps, gasProps}; + return AjaxResult.success(resultArray); + } + /** + 速度式流量计算 + */ + public static final void SdFlowCal(GasProps gasProps, FlowProps flowProps) + { + //工况体积流量 m³、s + flowProps.setdVFlowf(flowProps.getdPulseNum() / flowProps.getdMeterFactor()); + //标况体积流量 m³、s + flowProps.setdVFlowb(FlowConvert_WorkToBase(flowProps, gasProps)); + //标况质量流量 + flowProps.setdMFlowb(flowProps.getdVFlowb() * gasProps.dRhob); + //标况能量流量 + flowProps.setdEFlowb(flowProps.getdVFlowb() * gasProps.dHhvm); + + } + + public final void NozellFLowCal_New(GasProps gasProps, FlowProps flowProps) { + double nozzleThroatArea; // 喷嘴喉部面积 + double stagnationPressure; // 滞止压力 P0 + double stagnationTemp; // 滞止温度 T0 + double correctedPipeDiam; // 修正后管道直径 + double correctedThroatDiam;// 修正后喉部直径 + double beta; // 直径比 β = d/D + double massFlowRate; // 质量流量 Qm + + try { + // 1. 热膨胀修正 + correctedThroatDiam = flowProps.getdOrificeD() + * (1 + 1e-6 * flowProps.getdOrificeMaterial() * (flowProps.getdTf() - 293.15)); + correctedPipeDiam = flowProps.getdPipeD() + * (1 + 1e-6 * flowProps.getdPipeMaterial() * (flowProps.getdTf() - 293.15)); + beta = correctedThroatDiam / correctedPipeDiam; + + // 2. 计算喉部面积 + nozzleThroatArea = Math.PI * Math.pow(correctedThroatDiam / 2, 2); + + // 3. 计算马赫数 (假设流速来自flowProps) + double velocity = flowProps.getdVelocityFlow(); + double speedOfSound = Math.sqrt(gasProps.dKappa * gasProps.dZf + * 8314.51 / gasProps.dMrx * flowProps.getdTf()); + double machNumber = velocity / speedOfSound; + + // 4. 滞止压力 & 温度 (ISO 9300) + stagnationPressure = flowProps.getdPf() + * Math.pow(1 + (gasProps.dKappa - 1)/2 * machNumber*machNumber, + gasProps.dKappa/(gasProps.dKappa - 1)); + stagnationTemp = flowProps.getdTf() + * (1 + (gasProps.dKappa - 1)/2 * machNumber*machNumber); + + // 5. 计算质量流量 + double gasConstant = 8314.51 / gasProps.dMrx; + massFlowRate = nozzleThroatArea * flowProps.getdCd() + * Math.sqrt(gasProps.dKappa / (gasConstant * stagnationTemp)) + * stagnationPressure + * Math.pow(2/(gasProps.dKappa + 1), (gasProps.dKappa + 1)/(2*(gasProps.dKappa - 1))); + + // 6. 大直径比修正 + if (beta > 0.25) { + massFlowRate *= BetaG25(stagnationPressure, stagnationTemp, beta, gasProps); + } + + // 7. 设置结果 + flowProps.setdMFlowb(massFlowRate); + flowProps.setdVFlowb(massFlowRate / gasProps.dRhob); + flowProps.setdEFlowb(flowProps.getdVFlowb() * gasProps.dHhvm); + flowProps.setdVFlowf(FlowConvert_BaseToWork(flowProps, gasProps)); + + } catch (Exception ex) + { + } + } + + + + + /** + 天然气音速喷嘴流量计算 + */ + public final void NozellFLowCal( GasProps gasProps,FlowProps flowProps) + { + double dAnt; // 喷嘴喉部面积 + //double dCR;//临界流系数 + double dP1Z; //滞止压力 + double dT1Z; //滞止温度 + //double dPbeta;//上下游压力比 + double dDcorrect; // 考虑膨胀系数后的管道直径 + double ddcorrect; // 考虑膨胀系数后的喉部直径 + double dBeta; //直径比 + double dQm; + try + { + ddcorrect = flowProps.getdOrificeD() * (1 + 0.000001 * (flowProps.getdOrificeMaterial()) * (flowProps.getdTf() - 293.15)); + dDcorrect = flowProps.getdPipeD() * (1 + 0.000001 * (flowProps.getdOrificeMaterial()) * (flowProps.getdTf() - 293.15)); + dBeta = ddcorrect / dDcorrect; + dAnt = 3.1415926 * (ddcorrect / 2) * (ddcorrect / 2); + //ptNGcal.SOS(ref gasProps); + + //dCR = gasProps.dCstar * Math.Sqrt(gasProps.dZf); + dP1Z = flowProps.getdPf() * (1 + gasProps.dKappa / 2 * Math.pow((2 / (gasProps.dKappa + 1)), ((gasProps.dKappa + 1) / (gasProps.dKappa - 1))) * Math.pow(dBeta, 4)); + dT1Z = flowProps.getdTf() * (1 + (gasProps.dKappa - 1) / 2 * (2 / Math.pow((gasProps.dKappa + 1), ((gasProps.dKappa + 1) / (gasProps.dKappa - 1)))) * Math.pow(dBeta, 4)); + // dQm = dAnt * flowProps.dCd * gasProps.dCstar * dP1Z / Math.Sqrt(8314.51 * dT1Z / gasProps.dMrx); + dQm = dAnt * flowProps.getdCd() * gasProps.dCstar * Math.sqrt(gasProps.dZf * dP1Z * gasProps.dRhof); + + if (dBeta > 0.25) + { + dQm = dQm * BetaG25(dP1Z, dT1Z, dBeta, gasProps); + } + flowProps.setdMFlowb(dQm); + flowProps.setdVFlowb(flowProps.getdMFlowb() / gasProps.dRhob); + //标况能量流量 + flowProps.setdEFlowb(flowProps.getdVFlowb() * gasProps.dHhvm); + flowProps.setdVFlowf(FlowConvert_BaseToWork(flowProps, gasProps)); + } + catch (RuntimeException ex) + { + + } + } + + private double BetaG25(double P0, double T0, double BetaB, GasProps gasProps) + { + double[] nik0 = new double[9]; + double[] Sik0 = new double[9]; + double[] Tik0 = new double[9]; + double[] nik1 = new double[9]; + double[] Sik1 = new double[9]; + double[] Tik1 = new double[9]; + double C0 = 0; + double C1 = 0; + double Pai ; + double Tuo ; + double b ; + double F0 ; + double F1 ; + double Rf = 0.65; + // 直径比大于0.25的修正因子 + nik0[0] = 1.068826e-3; + nik0[1] = 1.199593e-2; + nik0[2] = -1.48292e-3; + nik0[3] = 2.764799e-4; + nik0[4] = 7.920711e-5; + nik0[5] = 1.11278e-3; + nik0[6] = -6.815626e-5; + nik0[7] = 3.86249e-8; + + nik1[0] = -3.46148e-3; + nik1[1] = 5.28029e-3; + nik1[2] = 1.195016e-2; + nik1[3] = 1.664232e-3; + nik1[4] = 1.159371e-3; + nik1[5] = 7.260461e-3; + nik1[6] = -7.541933e-4; + nik1[7] = 2.613967e-7; + + Sik0[0] = 0; + Sik0[1] = 0; + Sik0[2] = 0.5; + Sik0[3] = 1; + Sik0[4] = 2; + Sik0[5] = 3; + Sik0[6] = 5; + Sik0[7] = 10; + + Sik1[0] = 0; + Sik1[1] = 0; + Sik1[2] = 0; + Sik1[3] = 1; + Sik1[4] = 1.5; + Sik1[5] = 3; + Sik1[6] = 5; + Sik1[7] = 10; + + Tik0[0] = -1; + Tik0[1] = 0; + Tik0[2] = -6; + Tik0[3] = -1; + Tik0[4] = -2; + Tik0[5] = -8; + Tik0[6] = -10; + Tik0[7] = -18; + + Tik1[0] = -3; + Tik1[1] = -1; + Tik1[2] = 0; + Tik1[3] = -2; + Tik1[4] = -4; + Tik1[5] = -10; + Tik1[6] = -12; + Tik1[7] = -15; + + Pai = P0 / (gasProps.dPc * 1000000); + Tuo = T0 / gasProps.dTC; + for (int i = 0; i <= 7; i += 1) + { + C0 = C0 + nik0[i] * Math.pow(Pai, Sik0[i]) * Math.pow(Tuo, Tik0[i]); + C1 = C1 + nik1[i] * Math.pow(Pai, Sik1[i]) * Math.pow(Tuo, Tik1[i]); + } + b = 25.879 * Math.pow(BetaB, 6) - 32.693 * Math.pow(BetaB, 5) + 34.276 * Math.pow(BetaB, 4) - 6.0199 * Math.pow(BetaB, 3) - 1.1156 * Math.pow(BetaB, 2) - 0.1122 * BetaB + 0.0047; + F0 = 1 + b * C0; + F1 = 1 + b * C1; + return (1 - Rf) * F0 + Rf * F1; + } + + //压力损失计算 + public static final double YaLiSunShi(double tempLiuChuXiShu, double tempZjb, double tempDp, int JieLiuZhuangZhi) + { + double ylss = 0; + switch (JieLiuZhuangZhi) + { + case 0: + ylss = (tempDp * (Math.sqrt(1 - tempZjb) - tempLiuChuXiShu * Math.pow(tempZjb, 2)) / (Math.sqrt(1 - tempZjb) + tempLiuChuXiShu * Math.pow(tempZjb, 2))); + break; + case 1: + ylss = (tempDp * (Math.sqrt(1 - tempZjb) - tempLiuChuXiShu * Math.pow(tempZjb, 2)) / (Math.sqrt(1 - tempZjb) + tempLiuChuXiShu * Math.pow(tempZjb, 2))); + break; + case 2: + ylss = (tempDp * (Math.sqrt(1 - tempZjb) - tempLiuChuXiShu * Math.pow(tempZjb, 2)) / (Math.sqrt(1 - tempZjb) + tempLiuChuXiShu * Math.pow(tempZjb, 2))); + break; + } + return ylss; + } + + + // ######################################################################### + // ######################'流量转换 标况转工况############################## + // ######################################################################## + public static double FlowConvert_BaseToWork(FlowProps flowProps, GasProps gasProps) + { + double tempPn = 0; + double tempTn = 0; + + try + { + + if (gasProps.dZf == 0 || gasProps.dZb == 0) + { + return 0.0; + } + + + switch (gasProps.dCbtj) + { + case 2: + tempPn = 101325; + tempTn = 273.15; + break; + + case 1: + tempPn = 101325; + tempTn = 288.15; + break; + + case 0: + tempPn = 101325; + tempTn = 293.15; + break; + + case 3: + tempPn = 10155981; + tempTn = 288.7055555; + break; + } + + + flowProps.setdVFlowf(flowProps.getdVFlowb() * (tempPn * flowProps.getdTf() * gasProps.dZf) / (flowProps.getdPf() * tempTn * gasProps.dZb)); + return flowProps.getdVFlowf(); + // WARNING: ErrDo: is not supported + } + catch (RuntimeException exc) + { + return 0.0; + + } + } + + // ######################################################################### + // ######################流量转换工况转标况############################## + // ######################################################################## + public static double FlowConvert_WorkToBase(FlowProps flowProps, GasProps gasProps) + { + double tempPn = 0; + double tempTn = 0; + + // WARNING: On Error GOTO ErrDo is not supported + try + { + + switch (gasProps.dCbtj) + { + case 2: + tempPn = 101325; + tempTn = 273.15; + break; + + case 1: + tempPn = 101325; + tempTn = 288.15; + break; + + case 0: + tempPn = 101325; + tempTn = 293.15; + break; + + case 3: + tempPn = 0.10155981; + tempTn = 288.7055555; + break; + } + flowProps.setdVFlowb(flowProps.getdVFlowf() * (flowProps.getdPf() * tempTn * gasProps.dZb) / (tempPn * flowProps.getdTf() * gasProps.dZf)); + return flowProps.getdVFlowb(); + // WARNING: ErrDo: is not supported + } + catch (RuntimeException exc) + { + return 0.0; + } + + } + + public ThermService getThermService() { + return thermService; + } + + public DetailService getDetailService() { + return detailService; + } + + public GBT11062Service getGbt11062Service() { + return gbt11062Service; + } + + public GasController getGasController() { + return gasController; + } +} diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/FormatUtil.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/FormatUtil.java new file mode 100644 index 0000000..ba297ae --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/FormatUtil.java @@ -0,0 +1,40 @@ +package com.ruoyi.caltools.controller; + +import java.text.DecimalFormat; +import java.util.Optional; + +public class FormatUtil { + private static final DecimalFormat df1 = new DecimalFormat("#0.0"); + private static final DecimalFormat df2 = new DecimalFormat("#0.00"); + private static final DecimalFormat df3 = new DecimalFormat("#0.000"); + private static final DecimalFormat df4 = new DecimalFormat("#0.0000"); + private static final DecimalFormat df5 = new DecimalFormat("#0.00000"); + + public static double format(double value, Optional weishu ) { + double result; + Integer weishudefault=weishu.orElse(4); + + switch (weishudefault) { + case 1: + result= Double.parseDouble(df1.format(value)); + break; + case 2: + result= Double.parseDouble(df2.format(value)); + break; + case 3: + result= Double.parseDouble(df3.format(value)); + break; + case 4: + result= Double.parseDouble(df4.format(value)); + break; + case 5: + result= Double.parseDouble(df5.format(value)); + break; + + default: + throw new IllegalStateException("Unexpected value: " + weishu); + } + return result; + } + +} \ No newline at end of file diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/GBT22634WaterContentCalc.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/GBT22634WaterContentCalc.java new file mode 100644 index 0000000..7e8afa0 --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/GBT22634WaterContentCalc.java @@ -0,0 +1,143 @@ +package com.ruoyi.caltools.controller; + +import com.ruoyi.caltools.model.GasProps; +import org.apache.commons.math3.analysis.UnivariateFunction; +import org.apache.commons.math3.analysis.solvers.BracketingNthOrderBrentSolver; +import org.apache.commons.math3.exception.TooManyEvaluationsException; + +/** + * GB/T 22634-2008 天然气水含量与水露点工业级换算 + * 注:需配合气体性质计算模块GasController使用 + */ + +public class GBT22634WaterContentCalc { + + + + // 安托因方程常数(GB/T 22634规定) + private static final double ANTOINE_A = 8.07131; + private static final double ANTOINE_B = 1730.63; + private static final double ANTOINE_C = 233.426; + private static final double MMHG_TO_KPA = 0.1333223684; + + /** + * 工业级水含量计算(含真实气体修正) + * @param dewPointTemp 水露点温度(℃) + * @param pressure 绝对压力(kPa) + * @param gasProps 气体组分属性 + * @return 水含量(mg/Sm³) + */ + public double calculateWaterContent(double dewPointTemp, + double pressure, + GasProps gasProps) { + // 1. 计算饱和水蒸气压 + double pWater = calculateWaterVaporPressure(dewPointTemp); + // 2. 获取压缩因子 + GasController.Crit(gasProps,0); // 调用实际压缩因子计算模块 + // 3. 计算真实摩尔体积 + double molarVolume = calculateMolarVolume(dewPointTemp, pressure, gasProps.getdZf()); + // 4. 计算水含量 + return (pWater / (pressure - pWater)) * (18.01528 * 1e6) / molarVolume; + } + + /** + * 逆向计算水露点温度(工业级精度) + * @param waterContent 水含量(mg/Sm³) + * @param pressure 绝对压力(kPa) + * @param gasProps 气体组分属性 + * @return 水露点温度(℃),精度±0.01℃ + */ + public double calculateDewPoint(final double waterContent, + final double pressure, + final GasProps gasProps) { +// NewtonRaphsonSolver solver = new NewtonRaphsonSolver(1e-4); + + // 使用5阶布伦特法求解器(无需导数) + BracketingNthOrderBrentSolver solver = new BracketingNthOrderBrentSolver( + 1e-4, // 相对精度 + 1e-6, // 绝对精度 + 1e-10, // 函数值精度 + 5 // 多项式阶数 + ); + + UnivariateFunction function = new UnivariateFunction() { + @Override + public double value(double tempC) { + try { + // 创建临时气体属性副本 + GasProps tempProps = gasProps.clone(); + tempProps.setdTf(tempC + 273.15); // 更新温度 + tempProps.setdPf(pressure); + + double calc = calculateWaterContent(tempC, pressure, tempProps); + return calc - waterContent; + } catch (Exception e) { + return Double.NaN; + } + } + }; + + try { + return solver.solve(100, function, -50.0, 100.0, 20.0); // 初始猜测20℃ + } catch (TooManyEvaluationsException e) { + throw new ArithmeticException("露点计算未收敛,请检查输入参数"); + } + } + + // 饱和水蒸气压计算(GB/T 22634规定方法) + private double calculateWaterVaporPressure(double tempC) { + if(tempC < 0 || tempC > 100) { + throw new IllegalArgumentException("温度超出GB/T 22634适用范围"); + } + return Math.pow(10, ANTOINE_A - ANTOINE_B / (tempC + ANTOINE_C)) * MMHG_TO_KPA; + } + + // 真实气体摩尔体积计算 + private static double calculateMolarVolume(double tempC, double pressure, double Z) { + return Z * 8.3144621 * (tempC + 273.15) / pressure; + } + +// // 气体属性类(示例结构) +// public static class GasProps implements Cloneable { +// private double temperature; // K +// private double pressure; // kPa +// private double[] composition; // 组分摩尔分数 +// +// // 克隆方法用于迭代计算 +// @Override +// public GasProps clone() { +// GasProps clone = new GasProps(); +// clone.temperature = this.temperature; +// clone.pressure = this.pressure; +// clone.composition = this.composition.clone(); +// return clone; +// } +// +// // Getter/Setter +// public double getTemperature() { return temperature; } +// public void setTemperature(double temperature) { this.temperature = temperature; } +// public double getPressure() { return pressure; } +// public void setPressure(double pressure) { this.pressure = pressure; } +// public double[] getComposition() { return composition; } +// public void setComposition(double[] composition) { this.composition = composition; } +// } +// +// // 示例调用 +// public static void main(String[] args) { +// // 初始化气体属性 +// GasProps gas = new GasProps(); +// gas.setPressure(5000.0); // 5 MPa +// gas.setTemperature(293.15); // 20℃ +// gas.setComposition(new double[]{0.95, 0.03, 0.02}); // 示例组分 +// +// // 正向计算 +// double wc = calculateWaterContent(10.0, 5000.0, gas); +// System.out.printf("5MPa下10℃露点对应水含量:%.2f mg/Sm³\n", wc); +// +// // 逆向计算 +// double dewPoint = calculateDewPoint(1000.0, 5000.0, gas); +// System.out.printf("5MPa下1000mg/Sm³对应露点:%.2f℃\n", dewPoint); +// } + + +} diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/GasController.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/GasController.java new file mode 100644 index 0000000..8edfd11 --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/GasController.java @@ -0,0 +1,182 @@ +package com.ruoyi.caltools.controller; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.caltools.model.FlowProps; +import com.ruoyi.caltools.model.GasProps; +import com.ruoyi.caltools.service.DetailService; +import com.ruoyi.caltools.service.GBT11062Service; +import com.ruoyi.caltools.service.ThermService; +import com.ruoyi.caltools.utils.GasConstants; +import com.ruoyi.system.controller.UnitConvert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/NGCalcTools") +public class GasController { + + + public static ThermService thermService; + public static DetailService detailService; + public static GBT11062Service gbt11062Service; + + @Autowired + private UnitConvert unitConvert=new UnitConvert(); + + @PostMapping("/ngCalc") + public AjaxResult ngCalc(@RequestBody FlowProps flowProps) { + thermService = new ThermService(); + detailService = new DetailService(); + gbt11062Service = new GBT11062Service(); + GasProps gasProps = new GasProps(); + //大气压力转换成Pa + double tempPatm = unitConvert.ConvertUniter("pressure", flowProps.getdPatm(), flowProps.getdPatmUnit(), 0); + //压力转换成Pa + double tempPf = unitConvert.ConvertUniter("pressure", flowProps.getdPf(), flowProps.getdPfUnit(), 0); + //温度转换成K + double tempTf = unitConvert.ConvertUniter("temperature", flowProps.getdTf(), flowProps.getdTfUnit(), 2); + if (flowProps.getdPfType() == 0) //0是表压 + { + gasProps.dPf = tempPatm + tempPf; + } else { + gasProps.dPf = tempPf; + } + gasProps.dTf = tempTf; + gasProps.dCbtj = flowProps.getdCbtj(); + + switch (gasProps.dCbtj) + { + case 2: + gasProps.setdPb(101325); + gasProps.setdTb( 273.15); + flowProps.setdPb_M(101325); + flowProps.setdTb_M(273.15); + break; + case 1: + + gasProps.setdPb(101325); + gasProps.setdTb( 288.15); + flowProps.setdPb_M(101325); + flowProps.setdTb_M(288.15); + break; + case 0: + + gasProps.setdPb(101325); + gasProps.setdTb( 293.15); + flowProps.setdPb_M(101325); + flowProps.setdTb_M(293.15); + break; + } + + String[] stringArray = flowProps.getdngComponents().split("_"); + double[] doubleArray = new double[stringArray.length]; // 遍历字符串数组,将每个元素转换为 double 类型 + for (int i = 0; i < stringArray.length; i++) { + try { + doubleArray[i] = Double.parseDouble(stringArray[i]) / 100; + } catch (NumberFormatException e) { + // 处理转换异常 + System.err.println("无法将字符串 " + stringArray[i] + " 转换为 double 类型: " + e.getMessage()); + } + } + gasProps.adMixture = doubleArray; + Crit(gasProps, 0); //计算临界流函数所有参数都计算了 + return AjaxResult.success(gasProps); + } + + + + public static void ngCalcVoid(FlowProps flowProps, GasProps gasProps) { + thermService = new ThermService(); + detailService = new DetailService(); + gbt11062Service = new GBT11062Service(); + Crit(gasProps, 0); //计算临界流函数所有参数都计算了 + } + + public static int NG_Cal_Init() { + //create object for calculating density + if (null == (detailService=new DetailService())) { + return GasConstants.MEMORY_ALLOCATION_ERROR; + } + + //create object for calculating thermodynamic properties + if (null == (thermService=new ThermService())) { + return GasConstants.MEMORY_ALLOCATION_ERROR; + } + + return GasConstants.NG_Cal_INITIALIZED; + + }// NG_Cal_Init + public static int NG_Cal_UnInit() { + // delete the objects (if they exist) + detailService = null; + thermService = null; + return 0; + + } + + public static double Crit(GasProps gasProps, double dPlenumVelocity) + { + //variables local to function + double DH, DDH, S, H; + double tolerance = 1.0; + double R, P, T, Z; + + int i; + + //check objects for readiness; try to initialize if not + if (null == detailService || null == thermService) + { + NG_Cal_UnInit(); + + if (GasConstants.NG_Cal_INITIALIZED != NG_Cal_Init()) + { + + gasProps.lStatus =GasConstants. MEMORY_ALLOCATION_ERROR; return 0.0; + + } + } + + //begin by calculating densities and thermodynamic properties + thermService.Run(gasProps, detailService); + + //DH is enthalpy change from plenum to throat; this is our initial guess + + DH = (gasProps.dSOS * gasProps.dSOS - dPlenumVelocity * dPlenumVelocity) / 2.0; + + //trap plenum conditions before we alter the data stucture's contents + S = gasProps.dS; + + H = gasProps.dH; + + R = gasProps.dRhof; + P = gasProps.dPf; + Z = gasProps.dZf; + T = gasProps.dTf; + + DDH = 10.0; + + for (i = 1; i < GasConstants.MAX_NUM_OF_ITERATIONS; i++) + { + thermService.HS_Mode( gasProps, detailService, H - DH, S, true); + thermService.Run( gasProps, detailService); + DDH = DH; + DH = (gasProps.dSOS * gasProps.dSOS - dPlenumVelocity * dPlenumVelocity) / 2.0; + if (Math.abs(DDH - DH) < tolerance) break; + } + gasProps.dCstar = (gasProps.dRhof * gasProps.dSOS) / Math.sqrt(R * P * Z); + gasProps.dPf = P; + gasProps.dTf = T; + thermService.Run(gasProps, detailService); + gbt11062Service.Run(gasProps); + return gasProps.dCstar; + + } + +} + + + + + diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/NozellFlowCalc.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/NozellFlowCalc.java new file mode 100644 index 0000000..3c7f238 --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/NozellFlowCalc.java @@ -0,0 +1,134 @@ +package com.ruoyi.caltools.controller; + +import com.ruoyi.caltools.model.FlowProps; +import com.ruoyi.caltools.model.GasProps; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +public class NozellFlowCalc { + + + // 喷嘴类型枚举 + public enum NozzleType { + TOROIDAL, CYLINDRICAL + } + + // 计算流出系数 + public static double calculateDischargeCoefficient(NozzleType nozzleType, double reynoldsNumber) { + double a, b, c, d, e, f, n; + switch (nozzleType) { + case TOROIDAL: + a = 0.9990; + b = 3.415; + c = 0.0031; + d = 0.690; + e = 10; + f = 120000; + n = 0.5; + break; + case CYLINDRICAL: + a = 1.0000; + b = 6.341; + c = 0.008; // 假设天然气,按用户场景取c值 + d = 3.000; + e = 6; + f = 170000; + n = 0.5; + break; + default: + throw new IllegalArgumentException("不支持的喷嘴类型"); + } + + double numerator = c - d * Math.pow(reynoldsNumber, -n); + double denominator = 1 + Math.exp(e - reynoldsNumber / f); + return (a - b * Math.pow(reynoldsNumber, -n)) - numerator / denominator; + } + + // 计算雷诺数 + public static double calculateReynoldsNumber(double pipeDiameter, double velocity, double dynamicViscosity, double density) { + return (pipeDiameter * velocity * density) / dynamicViscosity; + } + + // 迭代计算流出系数和雷诺数 + public static void iterativeCalculation(FlowProps flowProps, GasProps gasProps, NozzleType nozzleType) { + double tolerance = 1e-6; + int maxIterations = 100; + double prevCd = 0; + double prevRe = 0; + + for (int i = 0; i < maxIterations; i++) { + double velocity = calculateVelocity(flowProps, gasProps); + double reynoldsNumber = calculateReynoldsNumber( + flowProps.getdPipeD(), + velocity, + gasProps.getdMu(), + gasProps.dRhof + ); + double cd = calculateDischargeCoefficient(nozzleType, reynoldsNumber); + + // 检查收敛 + if (Math.abs(cd - prevCd) < tolerance && Math.abs(reynoldsNumber - prevRe) < tolerance) { + flowProps.setdCdCorrect(cd); + flowProps.setdRnPipe(reynoldsNumber); + return; + } + + prevCd = cd; + prevRe = reynoldsNumber; + } + throw new RuntimeException("迭代未收敛"); + } + + // 计算流速 + private static double calculateVelocity(FlowProps flowProps, GasProps gasProps) { + // 假设工况体积流量计算逻辑,需根据实际流量公式补充 + // 这里简化示例,实际需根据ISO公式结合临界流函数等计算 + return flowProps.getdVFlowf() / (Math.PI * Math.pow(flowProps.getdPipeD() / 2, 2)); + } + + // 计算质量流量(示例) + public static double calculateMassFlow(FlowProps flowProps, GasProps gasProps, NozzleType nozzleType) { + iterativeCalculation(flowProps, gasProps, nozzleType); + double criticalFlowFactor = gasProps.dCstar; + double area = Math.PI * Math.pow(flowProps.getdOrificeD() / 2, 2); + double pressure = flowProps.getdPf(); + double temperature = flowProps.getdTf(); + double gasConstant = calculateGasConstant(gasProps); + + // 根据ISO公式计算理论质量流量,再结合流出系数 + double theoreticalMassFlow = area * criticalFlowFactor * pressure / Math.sqrt((gasConstant / gasProps.dMrx) * temperature); + return flowProps.getdCdCorrect() * theoreticalMassFlow; + } + + // 计算气体常数(示例) + private static double calculateGasConstant(GasProps gasProps) { + return 8.314 / gasProps.dMrx; // 通用气体常数处理,需根据实际精确计算 + } + + // 高精度计算辅助方法 + private static double highPrecisionCalculate(double value, int scale) { + return new BigDecimal(value).setScale(scale, RoundingMode.HALF_UP).doubleValue(); + } +// +// public static void main(String[] args) { +// FlowProps flowProps = new FlowProps(); +// GasProps gasProps = new GasProps(); +// NozzleType nozzleType = NozzleType.CYLINDRICAL; +// +// // 初始化输入参数 +// flowProps.dPipeD = 0.1; // 示例管道直径 +// flowProps.dOrificeD = 0.05; // 示例喉径 +// gasProps.dMu = 0.00001; // 示例动力粘度 +// gasProps.dRhof = 1.0; // 示例工况密度 +// gasProps.dCstar = 0.9; // 示例临界流函数 +// +// // 执行计算 +// double massFlow = calculateMassFlow(flowProps, gasProps, nozzleType); +// System.out.println("计算的质量流量: " + massFlow); +// } + + + + +} diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/NozellFlowCalcISO9300.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/NozellFlowCalcISO9300.java new file mode 100644 index 0000000..06ce505 --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/NozellFlowCalcISO9300.java @@ -0,0 +1,132 @@ +package com.ruoyi.caltools.controller; + + +import com.ruoyi.caltools.model.FlowProps; +import com.ruoyi.caltools.model.GasProps; +import com.ruoyi.caltools.service.DetailService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RestController; + +@RestController + +public class NozellFlowCalcISO9300 { + + @Autowired + private GasController gasController = new GasController(); + + + // 计算流出系数Cd、雷诺数Re及流量(示例中仅展示关键步骤) + public void calculateFlowCoefficient(FlowProps flowProps, GasProps gasProps) { + + // 获取基本参数 + double dOrifice = flowProps.getdOrificeD(); + double dPipe = flowProps.getdPipeD(); + double PStatic = flowProps.getdPf(); + double TStatic = flowProps.getdTf(); + // 气体属性 + double R = DetailService.RGASKJ; + double kappa = gasProps.getdKappa(); + double rhoRef = gasProps.getdRhob(); + double Cstar = gasProps.getdCstar(); + + // 计算几何参数 + double AOrifice = Math.PI * Math.pow(dOrifice, 2) / 4; + double APipe = Math.PI * Math.pow(dPipe / 2, 2); + + +// 初始参数 + double Cd = 0.99; // 初始猜测值 + double rho = gasProps.getdRhof(); // 基于静态条件的初始密度 + double mu = gasProps.getdMu(); // 初始粘度 + double Re; + + // 迭代控制 + double tolerance = 1e-6; + int maxIterations = 100; + boolean converged = false; + + for (int i = 0; i < maxIterations; i++) { + // 计算质量流量 + double qm = Cd * AOrifice * Cstar * Math.sqrt(gasProps.getdZf() * rho * gasProps.getdPf()); + + // 计算流速 + double velocity = qm / (rho * APipe); + + // 计算滞止参数 + double[] stagnation = calculateStagnationProperties( + PStatic, TStatic, velocity, kappa, R); + double P0 = stagnation[0]; + double T0 = stagnation[1]; + + // 更新物性参数 + gasProps.setdPf(P0); + gasProps.setdTf(T0); + gasController.ngCalc(flowProps); + double newRho =gasProps.getdRhof(); + double newMu = gasProps.getdMu(); + + // 计算雷诺数 + Re = (4 * qm) / (Math.PI * dOrifice * newMu); + + // 获取喷嘴系数 + double[] coeffs = getNozzleCoefficients(flowProps.getdNozzleType()); + double a = coeffs[0], b = coeffs[1], c = coeffs[2], + d = coeffs[3], e = coeffs[4], f = coeffs[5], n = coeffs[6]; + + // 计算新的Cd + double term1 = a - b * Math.pow(Re, -n); + double term2 = (c - d * Math.pow(Re, -n)) / (1 + Math.exp(e - Re / f)); + double newCd = term1 - term2; + + // 检查收敛 + if (Math.abs(newCd - Cd) < tolerance && + Math.abs(newRho - rho) < tolerance && + Math.abs(newMu - mu) < tolerance) { + converged = true; + Cd = newCd; + rho = newRho; + mu = newMu; + break; + } + + // 更新参数 + Cd = newCd; + rho = newRho; + mu = newMu; + } + + if (!converged) throw new RuntimeException("迭代未收敛"); + + // 计算标况体积流量 + double qm = Cd * AOrifice * Cstar * Math.sqrt(gasProps.getdZf() * rho * gasProps.getdPf()); + + // 保存结果 + flowProps.setdCd(Cd); + flowProps.setdRnPipe(rho); + flowProps.setdMFlowb(qm); + flowProps.setdVFlowb(qm/gasProps.getdRhob()); + } + + private static double[] getNozzleCoefficients(int coreType) { + // 返回喷嘴系数数组 [a, b, c, d, e, f, n] + if (coreType == 0) { // 圆环形 + return new double[]{0.9990, 3.415, 0.0031, 0.690, 10, 120000, 0.5}; + } else { // 圆柱形 + return new double[]{1.0000, 6.341, 0.008, 3.000, 6, 170000, 0.5}; + } + } + + + public static double[] calculateStagnationProperties( + double staticP, double staticT, double velocity, + double kappa, double R) { + + double soundSpeed = Math.sqrt(kappa * R * staticT); + double mach = velocity / soundSpeed; + double T0 = staticT * (1 + 0.5 * (kappa - 1) * mach * mach); + double P0 = staticP * Math.pow(T0 / staticT, kappa / (kappa - 1)); + return new double[]{P0, T0}; + } + + +} diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/TestController.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/TestController.java new file mode 100644 index 0000000..b120683 --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/controller/TestController.java @@ -0,0 +1,19 @@ +package com.ruoyi.caltools.controller; + +import com.ruoyi.common.core.domain.AjaxResult; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping("/test") +public class TestController { + + @PostMapping("/flowTest") + public AjaxResult testFlow(@RequestBody Map params) { + return AjaxResult.success("测试成功: " + params); + } +} \ No newline at end of file diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/enums/GasComponent.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/enums/GasComponent.java new file mode 100644 index 0000000..9db5f66 --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/enums/GasComponent.java @@ -0,0 +1,8 @@ +package com.ruoyi.caltools.enums; + +public enum GasComponent { + XiC1, XiN2, XiCO2, XiC2, XiC3, + XiH2O, XiH2S, XiH2, XiCO, XiO2, XiIC4, XiNC4, + XiIC5, XiNC5, XiNC6, XiNC7, XiNC8, XiNC9, XiNC10, XiHe, XiAr +} + diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/model/FlowProps.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/model/FlowProps.java new file mode 100644 index 0000000..eb0cd43 --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/model/FlowProps.java @@ -0,0 +1,632 @@ +package com.ruoyi.caltools.model; + +public class FlowProps { + // 流量计算输入参数信息 + private int dFlowCalbz; // 流量计算标准 + + + private int dZcalbz; // 压缩因子计算标准 + private int dCbtj; // 计量参比条件压力 + private double dPb_M; // 计量参比条件压力 + private double dTb_M; // 计量参比条件温度 + private double dPb_E; // 燃烧参比条件压力 + private double dTb_E; // 燃烧参比条件温度 + private double dPatm; // 当地大气压 + private int dPatmUnit; // 当地大气压单位 + private String dngComponents; // 天然气组分 + + private int dMeterType; // 流量计类别 + private int dCoreType; // 节流装置类型 + private int dPtmode; // 取压方式 + private int dPipeType; // 管道类型 + private double dPipeD; // 管道内径 + private int dLenUnit; // 长度单位 + private double dPipeDtemp; // 管道内径参考温度 + private int dPileDtempUint; // 温度单位 + private double dPipeMaterial; // 管道材料 + + private double dOrificeD; // 孔板孔径 + private int dOrificeUnit; // 长度单位 + private double dOrificeDtemp; // 孔板内径参考温度 + private int dOrificeDtempUnit; // 温度单位 + private double dOrificeMaterial; // 孔板材料 + private int dOrificeSharpness; // 锐利度系数计算方法 + private double dOrificeRk; // 孔板入口圆弧半径 + private int dOrificeRkLenUint; // 长度单位 + private double dPf; // 输入压力 + private int dPfUnit; // 压力单位 + private int dPfType; // 压力类型 + private double dTf; // 输入温度 + private int dTfUnit; // 温度单位 + private double dDp; // 输入差压 + private int dDpUnit; // 压力单位 + private int dVFlowUnit; // 体积流量单位 + private int dMFlowUnit; // 质量流量单位 + private int dEFlowUnit; // 能量流量单位 + private double dCd; // 流出系数 + private double dMeterFactor; // 仪表系数 + private double dPulseNum; // 脉冲数 + private double dVFlowMax; // 最大体积流量 + private double dVFlowMin; // 最小体积流量 + private double dVFlowCon; // 常用流量 + private double dPfRange; // 压力量程 + private double dDpRange; // 差压量程 + private double dTfRange; // 温度计量程 + + // 流量计算输出参数 + private double dE; // 求渐近速度系数 E + private double dFG; // 求相对密度系数 FG + private double dFT; // 求流动温度系数 FT + + + + private double dFpv; // 求超压缩因子 Fpv + + private double dDViscosity; // 求动力粘度 dlnd + private double dDExpCoefficient; // 求可膨胀系数 + private double dRnPipe; // 管道雷诺数 + private double dBk; // 孔板锐利度系数Bk + private double dRoughNessPipe; // 管道粗糙度系数 Gme + private double dCdCorrect; // 修正后的流出系数 + private double dCdNozell; // 喷嘴的流出系数 + private double dVFlowb; // 标况体积流量 m³、s + private double dVFlowf; // 工况体积流量 + private double dMFlowb; // 标况质量流量 + private double dEFlowb; // 标况能量流量 + private double dVelocityFlow; // 管道内天然气流速 + private double dPressLost; // 压力损失 + private double dBeta; // 直径比 + private double dKappa; // 等熵指数 + + private int dNozzleType; // 0圆环形喷嘴 2 圆柱形喉部文丘里喷嘴 + private double dUpstreamRadius ;// + + + + + + public double getdUpstreamRadius() { + return dUpstreamRadius; + } + + public void setdUpstreamRadius(double dUpstreamRadius) { + this.dUpstreamRadius = dUpstreamRadius; + } + + + + + + + public int getdZcalbz() { + return dZcalbz; + } + + public void setdZcalbz(int dZcalbz) { + this.dZcalbz = dZcalbz; + } + + public int getdFlowCalbz() { + return dFlowCalbz; + } + + public void setdFlowCalbz(int dFlowCalbz) { + this.dFlowCalbz = dFlowCalbz; + } + + public int getdCbtj() { + return dCbtj; + } + + public void setdCbtj(int dCbtj) { + this.dCbtj = dCbtj; + } + + public double getdPb_M() { + return dPb_M; + } + + public void setdPb_M(double dPb_M) { + this.dPb_M = dPb_M; + } + + public double getdTb_M() { + return dTb_M; + } + + public void setdTb_M(double dTb_M) { + this.dTb_M = dTb_M; + } + + public double getdPb_E() { + return dPb_E; + } + + public void setdPb_E(double dPb_E) { + this.dPb_E = dPb_E; + } + + public double getdTb_E() { + return dTb_E; + } + + public void setdTb_E(double dTb_E) { + this.dTb_E = dTb_E; + } + + public double getdPatm() { + return dPatm; + } + + public void setdPatm(double dPatm) { + this.dPatm = dPatm; + } + + public int getdPatmUnit() { + return dPatmUnit; + } + + public void setdPatmUnit(int dPatmUnit) { + this.dPatmUnit = dPatmUnit; + } + + public String getdngComponents() { + return dngComponents; + } + + public void setdngComponents(String dngComponents) { + this.dngComponents = dngComponents; + } + + public int getdMeterType() { + return dMeterType; + } + + public void setdMeterType(int dMeterType) { + this.dMeterType = dMeterType; + } + + public int getdCoreType() { + return dCoreType; + } + + public void setdCoreType(int dCoreType) { + this.dCoreType = dCoreType; + } + + public int getdPtmode() { + return dPtmode; + } + + public void setdPtmode(int dPtmode) { + this.dPtmode = dPtmode; + } + + public int getdPipeType() { + return dPipeType; + } + + public void setdPipeType(int dPipeType) { + this.dPipeType = dPipeType; + } + + public double getdPipeD() { + return dPipeD; + } + + public void setdPipeD(double dPipeD) { + this.dPipeD = dPipeD; + } + + public int getdLenUnit() { + return dLenUnit; + } + + public void setdLenUnit(int dLenUnit) { + this.dLenUnit = dLenUnit; + } + + public double getdPipeDtemp() { + return dPipeDtemp; + } + + public void setdPipeDtemp(double dPipeDtemp) { + this.dPipeDtemp = dPipeDtemp; + } + + public int getdPileDtempUint() { + return dPileDtempUint; + } + + public void setdPileDtempUint(int dPileDtempUint) { + this.dPileDtempUint = dPileDtempUint; + } + + public double getdPipeMaterial() { + return dPipeMaterial; + } + + public void setdPipeMaterial(double dPipeMaterial) { + this.dPipeMaterial = dPipeMaterial; + } + + public double getdOrificeD() { + return dOrificeD; + } + + public void setdOrificeD(double dOrificeD) { + this.dOrificeD = dOrificeD; + } + + public int getdOrificeUnit() { + return dOrificeUnit; + } + + public void setdOrificeUnit(int dOrificeUnit) { + this.dOrificeUnit = dOrificeUnit; + } + + public double getdOrificeDtemp() { + return dOrificeDtemp; + } + + public void setdOrificeDtemp(double dOrificeDtemp) { + this.dOrificeDtemp = dOrificeDtemp; + } + + public int getdOrificeDtempUnit() { + return dOrificeDtempUnit; + } + + public void setdOrificeDtempUnit(int dOrificeDtempUnit) { + this.dOrificeDtempUnit = dOrificeDtempUnit; + } + + public double getdOrificeMaterial() { + return dOrificeMaterial; + } + + public void setdOrificeMaterial(double dOrificeMaterial) { + this.dOrificeMaterial = dOrificeMaterial; + } + + public int getdOrificeSharpness() { + return dOrificeSharpness; + } + + public void setdOrificeSharpness(int dOrificeSharpness) { + this.dOrificeSharpness = dOrificeSharpness; + } + + public double getdOrificeRk() { + return dOrificeRk; + } + + public void setdOrificeRk(double dOrificeRk) { + this.dOrificeRk = dOrificeRk; + } + + public int getdOrificeRkLenUint() { + return dOrificeRkLenUint; + } + + public void setdOrificeRkLenUint(int dOrificeRkLenUint) { + this.dOrificeRkLenUint = dOrificeRkLenUint; + } + + public double getdPf() { + return dPf; + } + + public void setdPf(double dPf) { + this.dPf = dPf; + } + + public int getdPfUnit() { + return dPfUnit; + } + + public void setdPfUnit(int dPfUnit) { + this.dPfUnit = dPfUnit; + } + + public int getdPfType() { + return dPfType; + } + + public void setdPfType(int dPfType) { + this.dPfType = dPfType; + } + + public double getdTf() { + return dTf; + } + + public void setdTf(double dTf) { + this.dTf = dTf; + } + + public int getdTfUnit() { + return dTfUnit; + } + + public void setdTfUnit(int dTfUnit) { + this.dTfUnit = dTfUnit; + } + + public double getdDp() { + return dDp; + } + + public void setdDp(double dDp) { + this.dDp = dDp; + } + + public int getdDpUnit() { + return dDpUnit; + } + + public void setdDpUnit(int dDpUnit) { + this.dDpUnit = dDpUnit; + } + + public int getdVFlowUnit() { + return dVFlowUnit; + } + + public void setdVFlowUnit(int dVFlowUnit) { + this.dVFlowUnit = dVFlowUnit; + } + + public int getdMFlowUnit() { + return dMFlowUnit; + } + + public void setdMFlowUnit(int dMFlowUnit) { + this.dMFlowUnit = dMFlowUnit; + } + + public int getdEFlowUnit() { + return dEFlowUnit; + } + + public void setdEFlowUnit(int dEFlowUnit) { + this.dEFlowUnit = dEFlowUnit; + } + + public double getdCd() { + return dCd; + } + + public void setdCd(double dCd) { + this.dCd = dCd; + } + + public double getdMeterFactor() { + return dMeterFactor; + } + + public void setdMeterFactor(double dMeterFactor) { + this.dMeterFactor = dMeterFactor; + } + + public double getdPulseNum() { + return dPulseNum; + } + + public void setdPulseNum(double dPulseNum) { + this.dPulseNum = dPulseNum; + } + + public double getdVFlowMax() { + return dVFlowMax; + } + + public void setdVFlowMax(double dVFlowMax) { + this.dVFlowMax = dVFlowMax; + } + + public double getdVFlowMin() { + return dVFlowMin; + } + + public void setdVFlowMin(double dVFlowMin) { + this.dVFlowMin = dVFlowMin; + } + + public double getdVFlowCon() { + return dVFlowCon; + } + + public void setdVFlowCon(double dVFlowCon) { + this.dVFlowCon = dVFlowCon; + } + + public double getdPfRange() { + return dPfRange; + } + + public void setdPfRange(double dPfRange) { + this.dPfRange = dPfRange; + } + + public double getdDpRange() { + return dDpRange; + } + + public void setdDpRange(double dDpRange) { + this.dDpRange = dDpRange; + } + + public double getdTfRange() { + return dTfRange; + } + + public void setdTfRange(double dTfRange) { + this.dTfRange = dTfRange; + } + + public double getdE() { + return dE; + } + + public void setdE(double dE) { + this.dE = dE; + } + + public double getdFG() { + return dFG; + } + + public void setdFG(double dFG) { + this.dFG = dFG; + } + + public double getdFT() { + return dFT; + } + + public void setdFT(double dFT) { + this.dFT = dFT; + } + + public double getdDViscosity() { + return dDViscosity; + } + + public void setdDViscosity(double dDViscosity) { + this.dDViscosity = dDViscosity; + } + + public double getdDExpCoefficient() { + return dDExpCoefficient; + } + + public void setdDExpCoefficient(double dDExpCoefficient) { + this.dDExpCoefficient = dDExpCoefficient; + } + + public double getdRnPipe() { + return dRnPipe; + } + + public void setdRnPipe(double dRnPipe) { + this.dRnPipe = dRnPipe; + } + + public double getdBk() { + return dBk; + } + + public void setdBk(double dBk) { + this.dBk = dBk; + } + + public double getdRoughNessPipe() { + return dRoughNessPipe; + } + + public void setdRoughNessPipe(double dRoughNessPipe) { + this.dRoughNessPipe = dRoughNessPipe; + } + + public double getdCdCorrect() { + return dCdCorrect; + } + + public void setdCdCorrect(double dCdCorrect) { + this.dCdCorrect = dCdCorrect; + } + + public double getdCdNozell() { + return dCdNozell; + } + + public void setdCdNozell(double dCdNozell) { + this.dCdNozell = dCdNozell; + } + + public double getdVFlowb() { + return dVFlowb; + } + + public void setdVFlowb(double dVFlowb) { + this.dVFlowb = dVFlowb; + } + + public double getdVFlowf() { + return dVFlowf; + } + + public void setdVFlowf(double dVFlowf) { + this.dVFlowf = dVFlowf; + } + + public double getdMFlowb() { + return dMFlowb; + } + + public void setdMFlowb(double dMFlowb) { + this.dMFlowb = dMFlowb; + } + + public double getdEFlowb() { + return dEFlowb; + } + + public void setdEFlowb(double dEFlowb) { + this.dEFlowb = dEFlowb; + } + + public double getdVelocityFlow() { + return dVelocityFlow; + } + + public void setdVelocityFlow(double dVelocityFlow) { + this.dVelocityFlow = dVelocityFlow; + } + + public double getdPressLost() { + return dPressLost; + } + + public void setdPressLost(double dPressLost) { + this.dPressLost = dPressLost; + } + + public double getdBeta() { + return dBeta; + } + + public void setdBeta(double dBeta) { + this.dBeta = dBeta; + } + + public double getdKappa() { + return dKappa; + } + + public void setdKappa(double dKappa) { + this.dKappa = dKappa; + } + + public double getdFpv() { + return dFpv; + } + + public void setdFpv(double dFpv) { + this.dFpv = dFpv; + } + + + public int getdNozzleType() { + return dNozzleType; + } + + public void setdNozzleType(int dNozzleType) { + this.dNozzleType = dNozzleType; + } + + + +} diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/model/GasProps.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/model/GasProps.java new file mode 100644 index 0000000..7df9668 --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/model/GasProps.java @@ -0,0 +1,569 @@ +package com.ruoyi.caltools.model; + +public class GasProps implements Cloneable { + + // 重写 clone() 方法 + @Override + public GasProps clone() { + try { + return (GasProps) super.clone(); + } catch (CloneNotSupportedException e) { + // 因为已经实现了 Cloneable 接口,所以不会抛出该异常 + throw new AssertionError(); + } + } + + + // corresponds to the control group in meter classes + public int lStatus; // calculation status 计算状态 + public boolean bForceUpdate; // 执行全部计算的标志 signal to perform full calculation + public double[] adMixture; // 气体摩尔组成 Composition in mole fraction + public double[] adMixtureV; // 气体体积组成 Composition in mole fraction + public double[] adMixtureD; // 气体质量组成 Composition in mole fraction + public int dCbtj; // 参比条件 101325 0,15,20 + public double dPb; // 参比压力 Contract base Pressure (Pa) + public double dTb; // 参比温度Contract base temperature (K) + public double dPf; // 绝对压力 Absolute Pressure (Pa) + public double dTf; // 工况温度 Flowing temperature (K) + // basic output from AGA 8 Detail method + public double dMrx; // 分子量 mixture molar mass + public double dZb; // 标况压缩因子compressibility at contract base condition + public double dZf; // 工况压缩因子compressibility at flowing condition + public double dFpv; // 超压缩系数 supercompressibility + public double dDb; // 标况摩尔密度molar density at contract base conditions (moles/dm3) + public double dDf; // 工况摩尔密度 molar density at flowing conditions (moles/dm3) + public double dRhob; // 标况质量密度mass density at contract base conditions (kg/m3) + public double dRhof; // 工况质量密度 mass density at flowing conditions (kg/m3) + public double dRD_Ideal; // 理想气体的相对密度ideal gas relative density + public double dRD_Real; // 真实气体的相对密度real gas relative density + // additional output + public double dHo; // 理想气体的比焓 ideal gas specific enthalpy + public double dH; // 真实气体的焓 real gas specific enthalpy (J/kg) + public double dS; // 真实气体的熵real gas specific entropy (J/kg-mol.K) + public double dCpi; // 理想气体定压热容 ideal gas constant pressure heat capacity (J/kg-mol.K) + public double dCp; // 定压热容real gas constant pressure heat capacity (J/kg-mol.K) + public double dCv; // 定容积热容 real gas constant volume heat capacity (J/kg-mol.K) + public double dk; // 比热比ratio of specific heats + public double dKappa; // 等熵指数 isentropic exponent, denoted with Greek letter kappa + public double dSOS; // 声速speed of sound (m/s) + public double dCstar; // 临界流函数 critical flow factor C* + + // 11062 输出 + public double dHhvMol; // 摩尔高位发热量 + public double dLhvMol; // 摩尔低位发热量 + public double dHhvv; // 体积高位发热量 + public double dLhvv; // 体积低位发热量 + public double dHhvm; // 质量高位发热量 + public double dLhvm; // 质量地位发热量 + + public double dZb11062; // 标况压缩因子 + public double dRhob11062; // 标况质量密度mass density at contract base conditions (kg/m3) + public double dRhof11062; // 工况质量密度 mass density at flowing conditions (kg/m3) + public double dRD_Ideal11062; // 理想气体的相对密度ideal gas relative density + public double dRD_Real11062; // 真实气体的相对密度real gas relative density + public double dWobbeIndex; // 真实气体的沃泊指数 + + public double dPc; // 临界压力 + public double dTC; // 临界温度 + public double dBzsx; // 爆炸上限 + public double dBzxx; // 爆炸下限 + public double dTotalC; // 总炭含量 (kg/m3) + public double dC2; // C2组分含量 (kg/m3) + public double dC2j; // C2以上组分含量 (kg/m3) + public double dC3j; // C3以上组分含量 (kg/m3) + public double dC4j; // C4以上组分含量 (kg/m3) + public double dC5j; // C5以上组分含量 (kg/m3) + public double dC6j; // C6以上组分含量 (kg/m3) + public double dC3C4; // C3C4组分含量 (kg/m3) + public String dngComponents; //组分的组合字符串 从前端传过来 + + // 新增高精度物性参数 + private double dMu; // 动态粘度 (Pa·s) + private double dNu; // 运动粘度 (m²/s) + // 新增滞止参数 + public double dP0; // 滞止压力(Pa) + public double dT0; // 滞止温度(K) + + + + + + public double getdP0() { + return dP0; + } + + public void setdP0(double dP0) { + this.dP0 = dP0; + } + + public double getdT0() { + return dT0; + } + + public void setdT0(double dT0) { + this.dT0 = dT0; + } + + + + public double[] getAdMixtureD() { + return adMixtureD; + } + + public void setAdMixtureD(double[] adMixtureD) { + this.adMixtureD = adMixtureD; + } + + public int getlStatus() { + return lStatus; + } + + public void setlStatus(int lStatus) { + this.lStatus = lStatus; + } + + public boolean isbForceUpdate() { + return bForceUpdate; + } + + public void setbForceUpdate(boolean bForceUpdate) { + this.bForceUpdate = bForceUpdate; + } + + public double[] getAdMixture() { + return adMixture; + } + + public void setAdMixture(double[] adMixture) { + this.adMixture = adMixture; + } + + public double[] getAdMixtureV() { + return adMixtureV; + } + + public void setAdMixtureV(double[] adMixtureV) { + this.adMixtureV = adMixtureV; + } + + public int getdCbtj() { + return dCbtj; + } + + public void setdCbtj(int dCbtj) { + this.dCbtj = dCbtj; + } + + public double getdPb() { + return dPb; + } + + public void setdPb(double dPb) { + this.dPb = dPb; + } + + public double getdTb() { + return dTb; + } + + public void setdTb(double dTb) { + this.dTb = dTb; + } + + public double getdPf() { + return dPf; + } + + public void setdPf(double dPf) { + this.dPf = dPf; + } + + public double getdTf() { + return dTf; + } + + public void setdTf(double dTf) { + this.dTf = dTf; + } + + public double getdMrx() { + return dMrx; + } + + public void setdMrx(double dMrx) { + this.dMrx = dMrx; + } + + public double getdZb() { + return dZb; + } + + public void setdZb(double dZb) { + this.dZb = dZb; + } + + public double getdZf() { + return dZf; + } + + public void setdZf(double dZf) { + this.dZf = dZf; + } + + public double getdFpv() { + return dFpv; + } + + public void setdFpv(double dFpv) { + this.dFpv = dFpv; + } + + public double getdDb() { + return dDb; + } + + public void setdDb(double dDb) { + this.dDb = dDb; + } + + public double getdDf() { + return dDf; + } + + public void setdDf(double dDf) { + this.dDf = dDf; + } + + public double getdRhob() { + return dRhob; + } + + public void setdRhob(double dRhob) { + this.dRhob = dRhob; + } + + public double getdRhof() { + return dRhof; + } + + public void setdRhof(double dRhof) { + this.dRhof = dRhof; + } + + public double getdRD_Ideal() { + return dRD_Ideal; + } + + public void setdRD_Ideal(double dRD_Ideal) { + this.dRD_Ideal = dRD_Ideal; + } + + public double getdRD_Real() { + return dRD_Real; + } + + public void setdRD_Real(double dRD_Real) { + this.dRD_Real = dRD_Real; + } + + public double getdHo() { + return dHo; + } + + public void setdHo(double dHo) { + this.dHo = dHo; + } + + public double getdH() { + return dH; + } + + public void setdH(double dH) { + this.dH = dH; + } + + public double getdS() { + return dS; + } + + public void setdS(double dS) { + this.dS = dS; + } + + public double getdCpi() { + return dCpi; + } + + public void setdCpi(double dCpi) { + this.dCpi = dCpi; + } + + public double getdCp() { + return dCp; + } + + public void setdCp(double dCp) { + this.dCp = dCp; + } + + public double getdCv() { + return dCv; + } + + public void setdCv(double dCv) { + this.dCv = dCv; + } + + public double getDk() { + return dk; + } + + public void setDk(double dk) { + this.dk = dk; + } + + public double getdKappa() { + return dKappa; + } + + public void setdKappa(double dKappa) { + this.dKappa = dKappa; + } + + public double getdSOS() { + return dSOS; + } + + public void setdSOS(double dSOS) { + this.dSOS = dSOS; + } + + public double getdCstar() { + return dCstar; + } + + public void setdCstar(double dCstar) { + this.dCstar = dCstar; + } + + public double getdHhvMol() { + return dHhvMol; + } + + public void setdHhvMol(double dHhvMol) { + this.dHhvMol = dHhvMol; + } + + public double getdLhvMol() { + return dLhvMol; + } + + public void setdLhvMol(double dLhvMol) { + this.dLhvMol = dLhvMol; + } + + public double getdHhvv() { + return dHhvv; + } + + public void setdHhvv(double dHhvv) { + this.dHhvv = dHhvv; + } + + public double getdLhvv() { + return dLhvv; + } + + public void setdLhvv(double dLhvv) { + this.dLhvv = dLhvv; + } + + public double getdHhvm() { + return dHhvm; + } + + public void setdHhvm(double dHhvm) { + this.dHhvm = dHhvm; + } + + public double getdLhvm() { + return dLhvm; + } + + public void setdLhvm(double dLhvm) { + this.dLhvm = dLhvm; + } + + public double getdZb11062() { + return dZb11062; + } + + public void setdZb11062(double dZb11062) { + this.dZb11062 = dZb11062; + } + + public double getdRhob11062() { + return dRhob11062; + } + + public void setdRhob11062(double dRhob11062) { + this.dRhob11062 = dRhob11062; + } + + public double getdRhof11062() { + return dRhof11062; + } + + public void setdRhof11062(double dRhof11062) { + this.dRhof11062 = dRhof11062; + } + + public double getdRD_Ideal11062() { + return dRD_Ideal11062; + } + + public void setdRD_Ideal11062(double dRD_Ideal11062) { + this.dRD_Ideal11062 = dRD_Ideal11062; + } + + public double getdRD_Real11062() { + return dRD_Real11062; + } + + public void setdRD_Real11062(double dRD_Real11062) { + this.dRD_Real11062 = dRD_Real11062; + } + + public double getdWobbeIndex() { + return dWobbeIndex; + } + + public void setdWobbeIndex(double dWobbeIndex) { + this.dWobbeIndex = dWobbeIndex; + } + + public double getdPc() { + return dPc; + } + + public void setdPc(double dPc) { + this.dPc = dPc; + } + + public double getdTC() { + return dTC; + } + + public void setdTC(double dTC) { + this.dTC = dTC; + } + + public double getdBzsx() { + return dBzsx; + } + + public void setdBzsx(double dBzsx) { + this.dBzsx = dBzsx; + } + + public double getdBzxx() { + return dBzxx; + } + + public void setdBzxx(double dBzxx) { + this.dBzxx = dBzxx; + } + + public double getdTotalC() { + return dTotalC; + } + + public void setdTotalC(double dTotalC) { + this.dTotalC = dTotalC; + } + + public double getdC2() { + return dC2; + } + + public void setdC2(double dC2) { + this.dC2 = dC2; + } + + public double getdC2j() { + return dC2j; + } + + public void setdC2j(double dC2j) { + this.dC2j = dC2j; + } + + public double getdC3j() { + return dC3j; + } + + public void setdC3j(double dC3j) { + this.dC3j = dC3j; + } + + public double getdC4j() { + return dC4j; + } + + public void setdC4j(double dC4j) { + this.dC4j = dC4j; + } + + public double getdC5j() { + return dC5j; + } + + public void setdC5j(double dC5j) { + this.dC5j = dC5j; + } + + public double getdC6j() { + return dC6j; + } + + public void setdC6j(double dC6j) { + this.dC6j = dC6j; + } + + public double getdC3C4() { + return dC3C4; + } + + public void setdC3C4(double dC3C4) { + this.dC3C4 = dC3C4; + } + + public String getDngComponents() { + return dngComponents; + } + + public void setDngComponents(String dngComponents) { + this.dngComponents = dngComponents; + } + + + public double getdMu() { + return dMu; + } + + public void setdMu(double dMu) { + this.dMu = dMu; + } + + public double getdNu() { + return dNu; + } + + public void setdNu(double dNu) { + this.dNu = dNu; + } + + + + +} diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/service/DetailService.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/service/DetailService.java new file mode 100644 index 0000000..6c1d874 --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/service/DetailService.java @@ -0,0 +1,1389 @@ +package com.ruoyi.caltools.service; + + +import com.ruoyi.caltools.model.GasProps; +import com.ruoyi.caltools.utils.GasConstants; +import org.springframework.stereotype.Service; + +@Service +public class DetailService { + + public static final double RGASKJ = 8.314510e-3; + + // 成员变量转换 + // 组件数量 + private int iNCC; + // 组件ID数组,长度为21 + private int[] aiCID = new int[21]; + + // 五个历史变量,用于在重复计算时提高效率 +// 上一次计算的混合物ID + double dOldMixID; + // 上一次计算的Pb值 + double dOldPb; + // 上一次计算的Tb值 + double dOldTb; + // 上一次计算的Pf值 + double dOldPf; + // 上一次计算的Tf值 + double dOldTf; + + // 来自表4第1列的EOS参数 +// 长度为58的数组adAn + double[] adAn = new double[58]; + // 长度为58的数组adUn + double[] adUn = new double[58]; + + // 来自表5的特征参数 +// 第i个组件的分子量 + double[] dMri = new double[21]; + // 第i个组件的特征能量参数 + double[] dEi = new double[21]; + // 第i个组件的尺寸参数 - m^3/kg-mol ^1/3 + double[] dKi = new double[21]; + // 取向参数 + double[] dGi = new double[21]; + // 四极矩参数 + double[] dQi = new double[21]; + // 高温参数 + double[] dFi = new double[21]; + // 偶极矩参数 + double[] dSi = new double[21]; + // 关联参数 + double[] dWi = new double[21]; + // 维里系数能量二元相互作用参数 + double[][] dEij = new double[21][21]; + // 共形能量的二元相互作用参数 + double[][] dUij = new double[21][21]; + // 尺寸的二元相互作用参数 + double[][] dKij = new double[21][21]; + // 取向的二元相互作用参数 + double[][] dGij = new double[21][21]; + // 表6常量 + double[][] adTable6Eij = new double[21][21]; + // 表6常量 + double[][] adTable6Uij = new double[21][21]; + // 表6常量 + double[][] adTable6Kij = new double[21][21]; + // 表6常量 + double[][] adTable6Gij = new double[21][21]; + double[] adTable5Qi = new double[21]; // table 5 constants + double[] adTable5Fi = new double[21]; // table 5 constants + double[] adTable5Si = new double[21]; // table 5 constants + double[] adTable5Wi = new double[21]; // table 5 constants + // 组件i的摩尔分数数组,长度为21 + double[] dXi = new double[21]; + // 由pdetail()方法计算得到的压力 + double dPCalc; + // 当前温度 + double dT; + // 当前压力 + double dP; + // 在温度T和压力P下的摩尔密度 + double dRhoTP; + // 第二维里系数B + double dB; + // 用于计算B的18个系数的数组 + double[] adBcoef = new double[18]; + // 密度系数的函数数组,长度为58 + double[] adFn = new double[58]; + // 用于3个导数的修正系数数组,长度为58 + double[] fx = new double[58]; + // 混合能量参数 + double dU; + // 混合尺寸参数的三次方 + double dKp3; + // 混合取向参数 + double dW; + // 混合四极矩参数的平方 + double dQp2; + // 高温参数 + double dF; + // 摩尔密度 + double dRho; + // 在braket函数中使用的低密度 + double dRhoL; + // 在braket函数中使用的高密度 + double dRhoH; + // 在braket函数中使用的低压 + double dPRhoL; + // 在braket函数中使用的高压 + double dPRhoH; + + // 也用于高级流体性质计算的公共变量 +// 当前压缩因子 + public double dZ; + // Z对T的一阶偏导数 + public double ddZdT; + // Z对T的二阶偏导数 + public double dd2ZdT2; + // Z对摩尔密度的一阶偏导数 + public double ddZdD; + // B对T的一阶偏导数 + public double ddBdT; + // B对T的二阶偏导数 + public double dd2BdT2; + + + // 构造函数 + public DetailService() + { + //initialize history-sensitive variables + dOldMixID = 0.0; // mixture ID from previous calc + dOldPb = 0.0; // base pressure from previous calc + dOldTb = 0.0; // base temperature from previous calc + dOldPf = 0.0; // flowing pressure from previous calc + dOldTf = 0.0; // flowing temperature from previous calc + + //initialize gas component array used within this class + for (int i = 0; i < GasConstants.NUMBEROFCOMPONENTS; i++) dXi[i] = 0; + // function table() populates tables of static constants + table(); + + } + + public void run(GasProps gasProps) { + // 实现转换后的逻辑 + + + int i; + // Check for gas composition change + gasProps.bForceUpdate = (gasProps.bForceUpdate || compositionChange(gasProps)); + + // assign component IDs and values + if (gasProps.bForceUpdate) { + iNCC = -1; + for (i = 0; i < GasConstants.NUMBEROFCOMPONENTS; i++) { + if (gasProps.adMixture[i] > 0.0) { + iNCC = iNCC + 1; + aiCID[iNCC] = i; + dXi[iNCC] = gasProps.adMixture[i]; + } + } + iNCC = iNCC + 1; + //calculate composition dependent quantities; ported from original + //FORTRAN functions paramdl() and chardl() + paramdl(); + chardl(gasProps); + } + + //evaluate T & P dependent parms at base pressure and temperature, + //but only if necessary + if (Math.abs(gasProps.dPb - dOldPb) > GasConstants.P_CHG_TOL || Math.abs(gasProps.dTb - dOldTb) > GasConstants.T_CHG_TOL || gasProps.bForceUpdate) { + dP = gasProps.dPb * 1.0e-6; // AGA 8 uses MPa internally + dT = gasProps.dTb; + //calculate temperature dependent parms + temp(); + //determine molar density + ddetail(gasProps); + gasProps.dDb = dRho; + //determine compressibility + gasProps.dZb = zdetail(dRho); + // calculate mass density + dRhoTP = (dP * gasProps.dMrx) / (gasProps.dZb * GasConstants.RGASKJ * dT); + //calculate relative density + relativedensity(gasProps); + //copy density to data structure member + gasProps.dRhob = dRhoTP; + //update history and clear the ForceUpdate flag + dOldTb = gasProps.dTb; + dOldPb = gasProps.dPb; + gasProps.bForceUpdate = true; + } + + //repeat the process using flowing conditions + //begin by loading P & T from data structure + //AGA 8 uses MPa internally; converted from Pa here + dP = gasProps.dPf * 1.0e-6; + dT = gasProps.dTf; + + //check whether to calculate temperature dependent parms + if (Math.abs(gasProps.dTf - dOldTf) > GasConstants.T_CHG_TOL || gasProps.bForceUpdate) { + //if temperature has changed, we must follow through + temp(); + //force ForceUpdate flag to true + gasProps.bForceUpdate = true; + } + + // check whether to calculate other parms + if (Math.abs(gasProps.dPf - dOldPf) > GasConstants.P_CHG_TOL || gasProps.bForceUpdate) { + //determine molar density + ddetail(gasProps); + gasProps.dDf = dRho; + //determine compressibility + gasProps.dZf = zdetail(dRho); + //calculate mass density + dRhoTP = (dP * gasProps.dMrx) / (gasProps.dZf * GasConstants.RGASKJ * dT); + //copy density to data structure member + gasProps.dRhof = dRhoTP; + //update history + dOldTf = gasProps.dTf; + dOldPf = gasProps.dPf; + } + + //calculate legacy factor Fpv + //NOTE: as implemented here, Fpv is not constrained to 14.73 psi and 60F + if (gasProps.dZb > 0.0 && gasProps.dZf > 0.0) { + gasProps.dFpv = Math.sqrt(gasProps.dZb / gasProps.dZf); + } else { + //if either Zb or Zf is zero at this point, we have a serious unexpected problem + gasProps.dFpv = gasProps.dZb = gasProps.dZf = 0.0; + gasProps.lStatus = GasConstants.GENERAL_CALCULATION_FAILURE; + } + + //we are now up to date; toggle off the update flag + gasProps.bForceUpdate = false; + + + // 其他计算逻辑... + } + + private boolean compositionChange(GasProps gasProps) { + double dMixID = 0.0; + for (int i = 0; i < GasConstants.NUMBEROFCOMPONENTS; i++) { + dMixID += ((i + 2) * gasProps.adMixture[i]); + }if (dMixID != dOldMixID) { + dOldMixID = dMixID; + return true; + } + return false; + } + + public void paramdl() { + + int j, k; + // table 5 parameters; declared locally to this function + double[] adTable5Mri; + double[] adTable5Ei; + double[] adTable5Ki; + double[] adTable5Gi; + + // 初始化adTable5Mri数组 + adTable5Mri = new double[]{16.0430, 28.0135, 44.0100, 30.0700, 44.0970, 18.0153, 34.0820, 2.0159, 28.0100, 31.9988, 58.1230, 58.1230, 72.1500, 72.1500, 86.1770, 100.2040, 114.2310, 128.2580, 142.2850, 4.0026, 39.9480}; + + // 初始化adTable5Ei数组 + adTable5Ei = new double[]{151.318300, 99.737780, 241.960600, 244.166700, 298.118300, 514.015600, 296.355000, 26.957940, 105.534800, 122.766700, 324.068900, 337.638900, 365.599900, 370.682300, 402.636293, 427.722630, 450.325022, 470.840891, 489.558373, 2.610111, 119.629900}; + + // 初始化adTable5Ki数组 + adTable5Ki = new double[]{0.4619255, 0.4479153, 0.4557489, 0.5279209, 0.5837490, 0.3825868, 0.4618263, 0.3514916, 0.4533894, 0.4186954, 0.6406937, 0.6341423, 0.6738577, 0.6798307, 0.7175118, 0.7525189, 0.7849550, 0.8152731, 0.8437826, 0.3589888, 0.4216551}; + + // 初始化adTable5Gi数组 + adTable5Gi = new double[]{0.000000, 0.027815, 0.189065, 0.079300, 0.141239, 0.332500, 0.088500, 0.034369, 0.038953, 0.021000, 0.256692, 0.281835, 0.332267, 0.366911, 0.289731, 0.337542, 0.383381, 0.427354, 0.469659, 0.000000, 0.000000}; + //most of the table 5 parameters are zero + for (j = 0; j < GasConstants.NUMBEROFCOMPONENTS; j++) { + adTable5Qi[j] = 0.0; + adTable5Fi[j] = 0.0; + adTable5Si[j] = 0.0; + adTable5Wi[j] = 0.0; + } + //a small number of exceptions + adTable5Qi[2] = 0.690000; + adTable5Qi[5] = 1.067750; + adTable5Qi[6] = 0.633276; + adTable5Fi[7] = 1.0000; + adTable5Si[5] = 1.5822; + adTable5Si[6] = 0.3900; + adTable5Wi[5] = 1.0000; + // setup characterization parameters for non-zero components + for (j = iNCC - 1; j >= 0; j--) { + dMri[j] = adTable5Mri[aiCID[j]]; + dKi[j] = adTable5Ki[aiCID[j]]; + } + for (j = 0; j < iNCC; j++) { + dGi[j] = adTable5Gi[aiCID[j]]; + dEi[j] = adTable5Ei[aiCID[j]]; + } + for (j = 0; j < iNCC; j++) { + dQi[j] = adTable5Qi[aiCID[j]]; + dFi[j] = 0.0; + if (aiCID[j] == 7) dFi[j] = adTable5Fi[7]; + dSi[j] = adTable5Si[aiCID[j]]; + dWi[j] = adTable5Wi[aiCID[j]]; + } + + // Binary interaction parameters for arrays: eij, kij, wij, uij + for (j = 0; j < iNCC; j++) { + for (k = j; k < iNCC; k++) { + dUij[j][k] = adTable6Uij[aiCID[j]][aiCID[k]]; + dKij[j][k] = adTable6Kij[aiCID[j]][aiCID[k]]; + dEij[j][k] = adTable6Eij[aiCID[j]][aiCID[k]]; + dGij[j][k] = adTable6Gij[aiCID[j]][aiCID[k]]; + } + } + } + + public void chardl(GasProps gasProps) { + //variables local to function + int i, j; + double tmfrac, k5p0, k2p5, u5p0, u2p5, q1p0; + double Xij, Eij, Gij, e0p5, e2p0, e3p0, e3p5, e4p5, e6p0; + double e7p5, e9p5, e12p0, e12p5; + double e11p0, s3; + //normalize mole fractions and calculate molar mass + tmfrac = 0.0; + for (j = 0; j < iNCC; j++) { + tmfrac = tmfrac + dXi[j]; + } + for (j = 0; j < iNCC; j++) { + dXi[j] = dXi[j] / tmfrac; + } + // reset virial coefficients + for (j = 0; j < 18; j++) { + adBcoef[j] = 0.0; + } + // initialize a key subset of the local variables + k5p0 = 0.0; + k2p5 = 0.0; + u5p0 = 0.0; + u2p5 = 0.0; + dW = 0.0; + q1p0 = 0.0; + dF = 0.0; + // calculate gas molecular weight + gasProps.dMrx = 0.0; + for (j = 0; j < iNCC; j++) { + gasProps.dMrx = gasProps.dMrx + dXi[j] * dMri[j]; + } + // calculate the composition-dependent quantities, applying a nested loop + for (i = 0; i < iNCC; i++) { + k2p5 = k2p5 + dXi[i] * dKi[i] * dKi[i] * Math.sqrt(dKi[i]); + u2p5 = u2p5 + dXi[i] * dEi[i] * dEi[i] * Math.sqrt(dEi[i]); + dW = dW + dXi[i] * dGi[i]; + q1p0 = q1p0 + dXi[i] * dQi[i]; + dF = dF + dXi[i] * dXi[i] * dFi[i]; + for (j = i; j < iNCC; j++) { + if (i != j) Xij = 2.0 * dXi[i] * dXi[j]; + else Xij = dXi[i] * dXi[j]; + // proceed while skipping interaction terms which equal 1.0 + if (dKij[i][j] != 1.0) + k5p0 += Xij * (Math.pow(dKij[i][j], 5.0) - 1.0) * Math.pow((Math.pow(dKi[i], 5.0) * Math.pow(dKi[j], 5.0)), 0.5); + if (dUij[i][j] != 1.0) + u5p0 += Xij * (Math.pow(dUij[i][j], 5.0) - 1.0) * Math.pow((Math.pow(dEi[i], 5.0) * Math.pow(dEi[j], 5.0)), 0.5); + if (dGij[i][j] != 1.0) + dW += Xij * (dGij[i][j] - 1.0) * ((dGi[i] + dGi[j]) / 2.0); + // calculate terms required for second virial coefficient, B + Eij = dEij[i][j] * Math.sqrt(dEi[i] * dEi[j]); + Gij = dGij[i][j] * (dGi[i] + dGi[j]) / 2.0; + e0p5 = Math.sqrt(Eij); + e2p0 = Eij * Eij; + e3p0 = Eij * e2p0; + e3p5 = e3p0 * e0p5; + e4p5 = Eij * e3p5; + e6p0 = e3p0 * e3p0; + e11p0 = e4p5 * e4p5 * e2p0; + e7p5 = e4p5 * Eij * e2p0; + e9p5 = e7p5 * e2p0; + e12p0 = e11p0 * Eij; + e12p5 = e12p0 * e0p5; + s3 = Xij * Math.pow((Math.pow(dKi[i], 3.0) * Math.pow(dKi[j], 3)), 0.5); + adBcoef[0] = adBcoef[0] + s3; + adBcoef[1] = adBcoef[1] + s3 * e0p5; + adBcoef[2] = adBcoef[2] + s3 * Eij; + adBcoef[3] = adBcoef[3] + s3 * e3p5; + adBcoef[4] = adBcoef[4] + s3 * Gij / e0p5; + adBcoef[5] = adBcoef[5] + s3 * Gij * e4p5; + adBcoef[6] = adBcoef[6] + s3 * dQi[i] * dQi[j] * e0p5; + adBcoef[7] = adBcoef[7] + s3 * dSi[i] * dSi[j] * e7p5; + adBcoef[8] = adBcoef[8] + s3 * dSi[i] * dSi[j] * e9p5; + adBcoef[9] = adBcoef[9] + s3 * dWi[i] * dWi[j] * e6p0; + adBcoef[10] = adBcoef[10] + s3 * dWi[i] * dWi[j] * e12p0; + adBcoef[11] = adBcoef[11] + s3 * dWi[i] * dWi[j] * e12p5; + adBcoef[12] = adBcoef[12] + s3 * dFi[i] * dFi[j] / e6p0; + adBcoef[13] = adBcoef[13] + s3 * e2p0; + adBcoef[14] = adBcoef[14] + s3 * e3p0; + adBcoef[15] = adBcoef[15] + s3 * dQi[i] * dQi[j] * e2p0; + adBcoef[16] = adBcoef[16] + s3 * e2p0; + adBcoef[17] = adBcoef[17] + s3 * e11p0; + } + } + + //grab the first 18 constants from table 4, completing Bnij + for (i = 0; i < 18; i++) adBcoef[i] *= adAn[i]; + //final products of chardl are mixture size parameter K, energy parameter U, + //and quadrupole parameter Q + dKp3 = Math.pow((k5p0 + k2p5 * k2p5), 0.6); + dU = Math.pow((u5p0 + u2p5 * u2p5), 0.2); + dQp2 = q1p0 * q1p0; + } + // 其他方法转换... + + public void table() { + int j, k; + GasProps gasProps; + // 58 constants from table 4 - column A(n) + adAn[0] = 0.153832600; + adAn[1] = 1.341953000; + adAn[2] = -2.998583000; + adAn[3] = -0.048312280; + adAn[4] = 0.375796500; + adAn[5] = -1.589575000; + adAn[6] = -0.053588470; + adAn[7] = 0.886594630; + adAn[8] = -0.710237040; + adAn[9] = -1.471722000; + adAn[10] = 1.321850350; + adAn[11] = -0.786659250; + adAn[12] = 2.29129E-09; + adAn[13] = 0.157672400; + adAn[14] = -0.436386400; + adAn[15] = -0.044081590; + adAn[16] = -0.003433888; + adAn[17] = 0.032059050; + adAn[18] = 0.024873550; + adAn[19] = 0.073322790; + adAn[20] = -0.001600573; + adAn[21] = 0.642470600; + adAn[22] = -0.416260100; + adAn[23] = -0.066899570; + adAn[24] = 0.279179500; + adAn[25] = -0.696605100; + adAn[26] = -0.002860589; + adAn[27] = -0.008098836; + adAn[28] = 3.150547000; + adAn[29] = 0.007224479; + adAn[30] = -0.705752900; + adAn[31] = 0.534979200; + adAn[32] = -0.079314910; + adAn[33] = -1.418465000; + adAn[34] = -5.99905E-17; + adAn[35] = 0.105840200; + adAn[36] = 0.034317290; + adAn[37] = -0.007022847; + adAn[38] = 0.024955870; + adAn[39] = 0.042968180; + adAn[40] = 0.746545300; + adAn[41] = -0.291961300; + adAn[42] = 7.294616000; + adAn[43] = -9.936757000; + adAn[44] = -0.005399808; + adAn[45] = -0.243256700; + adAn[46] = 0.049870160; + adAn[47] = 0.003733797; + adAn[48] = 1.874951000; + adAn[49] = 0.002168144; + adAn[50] = -0.658716400; + adAn[51] = 0.000205518; + adAn[52] = 0.009776195; + adAn[53] = -0.020487080; + adAn[54] = 0.015573220; + adAn[55] = 0.006862415; + adAn[56] = -0.001226752; + adAn[57] = 0.002850908; + + // 58 constants from table 4 - column Un + adUn[0] = 0.0; + adUn[1] = 0.5; + adUn[2] = 1.0; + adUn[3] = 3.5; + adUn[4] = -0.5; + adUn[5] = 4.5; + adUn[6] = 0.5; + adUn[7] = 7.5; + adUn[8] = 9.5; + adUn[9] = 6.0; + adUn[10] = 12.0; + adUn[11] = 12.5; + adUn[12] = -6.0; + adUn[13] = 2.0; + adUn[14] = 3.0; + adUn[15] = 2.0; + adUn[16] = 2.0; + adUn[17] = 11.0; + adUn[18] = -0.5; + adUn[19] = 0.5; + adUn[20] = 0.0; + adUn[21] = 4.0; + adUn[22] = 6.0; + adUn[23] = 21.0; + adUn[24] = 23.0; + adUn[25] = 22.0; + adUn[26] = -1.0; + adUn[27] = -0.5; + adUn[28] = 7.0; + adUn[29] = -1.0; + adUn[30] = 6.0; + adUn[31] = 4.0; + adUn[32] = 1.0; + adUn[33] = 9.0; + adUn[34] = -13.0; + adUn[35] = 21.0; + adUn[36] = 8.0; + adUn[37] = -0.5; + adUn[38] = 0.0; + adUn[39] = 2.0; + adUn[40] = 7.0; + adUn[41] = 9.0; + adUn[42] = 22.0; + adUn[43] = 23.0; + adUn[44] = 1.0; + adUn[45] = 9.0; + adUn[46] = 3.0; + adUn[47] = 8.0; + adUn[48] = 23.0; + adUn[49] = 1.5; + adUn[50] = 5.0; + adUn[51] = -0.5; + adUn[52] = 4.0; + adUn[53] = 7.0; + adUn[54] = 3.0; + adUn[55] = 0.0; + adUn[56] = 1.0; + adUn[57] = 0.0; + //Most of the tables are filled with 1.0 or 0.0 + //It is up to us to set non-zero values + for (j = 0; j < GasConstants.NUMBEROFCOMPONENTS; j++) { + for (k = j; k < GasConstants.NUMBEROFCOMPONENTS; k++) { + adTable6Eij[j][k] = 1.0; + adTable6Uij[j][k] = 1.0; + adTable6Kij[j][k] = 1.0; + adTable6Gij[j][k] = 1.0; + } + } + //Lnsert the 132 items of non-zero and non-1.0 data + //This looks more cumbersome than it is, considering table 6 has 1764 members + adTable6Eij[0][1] = 0.971640; + adTable6Eij[0][2] = 0.960644; + adTable6Eij[0][4] = 0.994635; + adTable6Eij[0][5] = 0.708218; + adTable6Eij[0][6] = 0.931484; + adTable6Eij[0][7] = 1.170520; + adTable6Eij[0][8] = 0.990126; + adTable6Eij[0][10] = 1.019530; + adTable6Eij[0][11] = 0.989844; + adTable6Eij[0][12] = 1.002350; + adTable6Eij[0][13] = 0.999268; + adTable6Eij[0][14] = 1.107274; + adTable6Eij[0][15] = 0.880880; + adTable6Eij[0][16] = 0.880973; + adTable6Eij[0][17] = 0.881067; + adTable6Eij[0][18] = 0.881161; + adTable6Eij[1][2] = 1.022740; + adTable6Eij[1][3] = 0.970120; + adTable6Eij[1][4] = 0.945939; + adTable6Eij[1][5] = 0.746954; + adTable6Eij[1][6] = 0.902271; + adTable6Eij[1][7] = 1.086320; + adTable6Eij[1][8] = 1.005710; + adTable6Eij[1][9] = 1.021000; + adTable6Eij[1][10] = 0.946914; + adTable6Eij[1][11] = 0.973384; + adTable6Eij[1][12] = 0.959340; + adTable6Eij[1][13] = 0.945520; + adTable6Eij[2][3] = 0.925053; + adTable6Eij[2][4] = 0.960237; + adTable6Eij[2][5] = 0.849408; + adTable6Eij[2][6] = 0.955052; + adTable6Eij[2][7] = 1.281790; + adTable6Eij[2][8] = 1.500000; + adTable6Eij[2][10] = 0.906849; + adTable6Eij[2][11] = 0.897362; + adTable6Eij[2][12] = 0.726255; + adTable6Eij[2][13] = 0.859764; + adTable6Eij[2][14] = 0.855134; + adTable6Eij[2][15] = 0.831229; + adTable6Eij[2][16] = 0.808310; + adTable6Eij[2][17] = 0.786323; + adTable6Eij[2][18] = 0.765171; + adTable6Eij[3][4] = 1.022560; + adTable6Eij[3][5] = 0.693168; + adTable6Eij[3][6] = 0.946871; + adTable6Eij[3][7] = 1.164460; + adTable6Eij[3][11] = 1.013060; + adTable6Eij[3][13] = 1.005320; + adTable6Eij[4][7] = 1.034787; + adTable6Eij[4][11] = 1.004900; + adTable6Eij[6][14] = 1.008692; + adTable6Eij[6][15] = 1.010126; + adTable6Eij[6][16] = 1.011501; + adTable6Eij[6][17] = 1.012821; + adTable6Eij[6][18] = 1.014089; + adTable6Eij[7][8] = 1.100000; + adTable6Eij[7][10] = 1.300000; + adTable6Eij[7][11] = 1.300000; + adTable6Uij[0][1] = 0.886106; + adTable6Uij[0][2] = 0.963827; + adTable6Uij[0][4] = 0.990877; + adTable6Uij[0][6] = 0.736833; + adTable6Uij[0][7] = 1.156390; + adTable6Uij[0][11] = 0.992291; + adTable6Uij[0][13] = 1.003670; + adTable6Uij[0][14] = 1.302576; + adTable6Uij[0][15] = 1.191904; + adTable6Uij[0][16] = 1.205769; + adTable6Uij[0][17] = 1.219634; + adTable6Uij[0][18] = 1.233498; + adTable6Uij[1][2] = 0.835058; + adTable6Uij[1][3] = 0.816431; + adTable6Uij[1][4] = 0.915502; + adTable6Uij[1][6] = 0.993476; + adTable6Uij[1][7] = 0.408838; + adTable6Uij[1][11] = 0.993556; + adTable6Uij[2][3] = 0.969870; + adTable6Uij[2][6] = 1.045290; + adTable6Uij[2][8] = 0.900000; + adTable6Uij[2][14] = 1.066638; + adTable6Uij[2][15] = 1.077634; + adTable6Uij[2][16] = 1.088178; + adTable6Uij[2][17] = 1.098291; + adTable6Uij[2][18] = 1.108021; + adTable6Uij[3][4] = 1.065173; + adTable6Uij[3][6] = 0.971926; + adTable6Uij[3][7] = 1.616660; + adTable6Uij[3][10] = 1.250000; + adTable6Uij[3][11] = 1.250000; + adTable6Uij[3][12] = 1.250000; + adTable6Uij[3][13] = 1.250000; + adTable6Uij[6][14] = 1.028973; + adTable6Uij[6][15] = 1.033754; + adTable6Uij[6][16] = 1.038338; + adTable6Uij[6][17] = 1.042735; + adTable6Uij[6][18] = 1.046966; + adTable6Kij[0][1] = 1.003630; + adTable6Kij[0][2] = 0.995933; + adTable6Kij[0][4] = 1.007619; + adTable6Kij[0][6] = 1.000080; + adTable6Kij[0][7] = 1.023260; + adTable6Kij[0][11] = 0.997596; + adTable6Kij[0][13] = 1.002529; + adTable6Kij[0][14] = 0.982962; + adTable6Kij[0][15] = 0.983565; + adTable6Kij[0][16] = 0.982707; + adTable6Kij[0][17] = 0.981849; + adTable6Kij[0][18] = 0.980991; + adTable6Kij[1][2] = 0.982361; + adTable6Kij[1][3] = 1.007960; + adTable6Kij[1][6] = 0.942596; + adTable6Kij[1][7] = 1.032270; + adTable6Kij[2][3] = 1.008510; + adTable6Kij[2][6] = 1.007790; + adTable6Kij[2][14] = 0.910183; + adTable6Kij[2][15] = 0.895362; + adTable6Kij[2][16] = 0.881152; + adTable6Kij[2][17] = 0.867520; + adTable6Kij[2][18] = 0.854406; + adTable6Kij[3][4] = 0.986893; + adTable6Kij[3][6] = 0.999969; + adTable6Kij[3][7] = 1.020340; + adTable6Kij[6][14] = 0.968130; + adTable6Kij[6][15] = 0.962870; + adTable6Kij[6][16] = 0.957828; + adTable6Kij[6][17] = 0.952441; + adTable6Kij[6][18] = 0.948338; + adTable6Gij[0][2] = 0.807653; + adTable6Gij[0][7] = 1.957310; + adTable6Gij[1][2] = 0.982746; + adTable6Gij[2][3] = 0.370296; + adTable6Gij[2][5] = 1.673090; + } + + public void bvir() { + //variables local to function + double t0p5, t2p0, t3p0, t3p5, t4p5, t6p0, t11p0; + double t7p5, t9p5, t12p0, t12p5; + double t1p5, t4p0; + double[] Bx = new double[18]; + int i; + //reset B and partial devivatives to 0.0 + dB = ddBdT = dd2BdT2 = 0.0; + //pre-calculate Math .Powers of T + t0p5 = Math.sqrt(dT); + t2p0 = dT * dT; + t3p0 = dT * t2p0; + t3p5 = t3p0 * t0p5; + t4p5 = dT * t3p5; + t6p0 = t3p0 * t3p0; + t11p0 = t4p5 * t4p5 * t2p0; + t7p5 = t6p0 * dT * t0p5; + t9p5 = t7p5 * t2p0; + t12p0 = t9p5 * t0p5 * t2p0; + t12p5 = t12p0 * t0p5; + t1p5 = dT * t0p5; + t4p0 = t2p0 * t2p0; + //coefficients for B + Bx[0] = adBcoef[0]; + Bx[1] = adBcoef[1] / t0p5; + Bx[2] = adBcoef[2] / dT; + Bx[3] = adBcoef[3] / t3p5; + Bx[4] = adBcoef[4] * t0p5; + Bx[5] = adBcoef[5] / t4p5; + Bx[6] = adBcoef[6] / t0p5; + Bx[7] = adBcoef[7] / t7p5; + Bx[8] = adBcoef[8] / t9p5; + Bx[9] = adBcoef[9] / t6p0; + Bx[10] = adBcoef[10] / t12p0; + Bx[11] = adBcoef[11] / t12p5; + Bx[12] = adBcoef[12] * t6p0; + Bx[13] = adBcoef[13] / t2p0; + Bx[14] = adBcoef[14] / t3p0; + Bx[15] = adBcoef[15] / t2p0; + Bx[16] = adBcoef[16] / t2p0; + Bx[17] = adBcoef[17] / t11p0; + //sum up the pieces for second virial coefficient, B + for (i = 0; i < 18; i++) { + dB += Bx[i]; + } + //calculate terms for first derivative of B, wrt T + for (i = 0; i < 18; i++) { + if (adUn[i] != 0) + Bx[i] *= adUn[i]; + } + //sum up the pieces of first derivative of B + //note div by dT; changes exponent of T + for (i = 0; i < 18; i++) { + if (adUn[i] != 0) + ddBdT += Bx[i] / dT; + } + //sign change here + ddBdT = -ddBdT; + //calculate terms for second derivative of B, wrt T + for (i = 0; i < 18; i++) { + if (adUn[i] != 0 && adUn[i] != -1.0) Bx[i] *= (adUn[i] + 1.0); + } + //sum up the pieces of second derivative of B + //note division by dT, thereby changing the exponent of T + //loop will ignore Bx[0] which is = 0.0 + for (i = 0; i < 18; i++) { + if (adUn[i] != 0 && adUn[i] != -1.0) dd2BdT2 += Bx[i] / t2p0; + } + } + + public void temp() { + // Note: this function was ported from the AGA Report No.8 FORTRAN listing, + // retaining as much of the original content as possible + // variables local to function + double tr0p5, tr1p5, tr2p0, tr3p0, tr4p0, tr5p0, tr6p0; + double tr7p0, tr8p0, tr9p0, tr11p0, tr13p0, tr21p0; + double tr22p0, tr23p0, tr; + + /*calculate second virial coefficient B*/ + bvir(); + + // calculate adFn(12) through adFn(57) + // adFn(0)-adFn(11) do not contribute to csm terms + tr = dT / dU; + tr0p5 = Math.sqrt(tr); + tr1p5 = tr * tr0p5; + tr2p0 = tr * tr; + tr3p0 = tr * tr2p0; + tr4p0 = tr * tr3p0; + tr5p0 = tr * tr4p0; + tr6p0 = tr * tr5p0; + tr7p0 = tr * tr6p0; + tr8p0 = tr * tr7p0; + tr9p0 = tr * tr8p0; + tr11p0 = tr6p0 * tr5p0; + tr13p0 = tr6p0 * tr7p0; + tr21p0 = tr9p0 * tr9p0 * tr3p0; + tr22p0 = tr * tr21p0; + tr23p0 = tr * tr22p0; + + adFn[12] = adAn[12] * dF * tr6p0; + adFn[13] = adAn[13] / tr2p0; + adFn[14] = adAn[14] / tr3p0; + adFn[15] = adAn[15] * dQp2 / tr2p0; + adFn[16] = adAn[16] / tr2p0; + adFn[17] = adAn[17] / tr11p0; + adFn[18] = adAn[18] * tr0p5; + adFn[19] = adAn[19] / tr0p5; + adFn[20] = adAn[20]; + + adFn[21] = adAn[21] / tr4p0; + adFn[22] = adAn[22] / tr6p0; + adFn[23] = adAn[23] / tr21p0; + adFn[24] = adAn[24] * dW / tr23p0; + adFn[25] = adAn[25] * dQp2 / tr22p0; + adFn[26] = adAn[26] * dF * tr; + adFn[27] = adAn[27] * dQp2 * tr0p5; + adFn[28] = adAn[28] * dW / tr7p0; + adFn[29] = adAn[29] * dF * tr; + adFn[30] = adAn[30] / tr6p0; + adFn[31] = adAn[31] * dW / tr4p0; + adFn[32] = adAn[32] * dW / tr; + adFn[33] = adAn[33] * dW / tr9p0; + adFn[34] = adAn[34] * dF * tr13p0; + adFn[35] = adAn[35] / tr21p0; + adFn[36] = adAn[36] * dQp2 / tr8p0; + adFn[37] = adAn[37] * tr0p5; + adFn[38] = adAn[38]; + adFn[39] = adAn[39] / tr2p0; + adFn[40] = adAn[40] / tr7p0; + adFn[41] = adAn[41] * dQp2 / tr9p0; + adFn[42] = adAn[42] / tr22p0; + adFn[43] = adAn[43] / tr23p0; + adFn[44] = adAn[44] / tr; + adFn[45] = adAn[45] / tr9p0; + adFn[46] = adAn[46] * dQp2 / tr3p0; + adFn[47] = adAn[47] / tr8p0; + adFn[48] = adAn[48] * dQp2 / tr23p0; + adFn[49] = adAn[49] / tr1p5; + adFn[50] = adAn[50] * dW / tr5p0; + adFn[51] = adAn[51] * dQp2 * tr0p5; + adFn[52] = adAn[52] / tr4p0; + adFn[53] = adAn[53] * dW / tr7p0; + adFn[54] = adAn[54] / tr3p0; + adFn[55] = adAn[55] * dW; + adFn[56] = adAn[56] / tr; + adFn[57] = adAn[57] * dQp2; + } + + public void ddetail(GasProps gasProps) { + int imax, i; + double epsp, epsr, epsmin; + double x1, x2, x3, y1, y2, y3; + double delx, delprv, delmin, delbis, xnumer, xdenom, sgndel; + double y2my3, y3my1, y1my2, boundn; + //initialize convergence tolerances + imax = 150; + epsp = 1.0e-6; + epsr = 1.0e-6; + epsmin = 1.0e-7; + dRho = 0.0; + //call subroutine braket to bracket density solution + braket(gasProps); + //check value of "lStatus" returned from subroutine braket + if (gasProps.lStatus == GasConstants.MAX_NUM_OF_ITERATIONS_EXCEEDED || gasProps.lStatus == GasConstants.NEGATIVE_DENSITY_DERIVATIVE) { + return; + } + //set up to start Brent's method + //x is the independent variable, y the dependent variable + //delx is the current iteration change in x + //delprv is the previous iteration change in x + x1 = dRhoL; + x2 = dRhoH; + y1 = dPRhoL - dP; + y2 = dPRhoH - dP; + delx = x1 - x2; + delprv = delx; + //solution is bracketed between x1 and x2 + //a third point x3 is introduced for quadratic interpolation + x3 = x1; + y3 = y1; + for (i = 0; i < imax; i++) { + //y3 must be opposite in sign from y2 so solution between x2,x3 + if (y2 * y3 > 0.0) { + x3 = x1; + y3 = y1; + delx = x1 - x2; + delprv = delx; + } + + //y2 must be value of y closest to y=0.0, then x2new=x2old+delx + + if (Math.abs(y3) < Math.abs(y2)) { + x1 = x2; + x2 = x3; + x3 = x1; + y1 = y2; + y2 = y3; + y3 = y1; + } + + //delmin is minimum allowed step size for unconverged iteration + delmin = epsmin * Math.abs(x2); + //if procedure is not converging or if delprv is less than delmin + //use bisection instead + //delbis = 0.5d0*(x3 - x2) is the bisection delx + delbis = 0.5 * (x3 - x2); + // tests to select numerical method for current iteration + + if (Math.abs(delprv) < delmin || Math.abs(y1) < Math.abs(y2)) { + // use bisection + delx = delbis; + delprv = delbis; + } else { + if (x3 != x1) { + // use inverse quadratic interpolation + y2my3 = y2 - y3; + y3my1 = y3 - y1; + y1my2 = y1 - y2; + xdenom = -(y1my2) * (y2my3) * (y3my1); + xnumer = x1 * y2 * y3 * (y2my3) + + x2 * y3 * y1 * (y3my1) + + x3 * y1 * y2 * (y1my2) - x2 * xdenom; + } else { + // use inverse linear interpolation + xnumer = (x2 - x1) * y2; + xdenom = y1 - y2; + } + // before calculating delx check delx=xnumer/xdenom is not out of bounds + if (2.0 * Math.abs(xnumer) < Math.abs(delprv * xdenom)) { + // procedure converging, use interpolation + delprv = delx; + delx = xnumer / xdenom; + } else { + // procedure diverging, use bisection + delx = delbis; + delprv = delbis; + } + } + + // check for convergence + if ((Math.abs(y2) < epsp * dP) && (Math.abs(delx) < epsr * Math.abs(x2))) { + dRho = x2 + delx; + return; + } + + //when unconverged, abs(delx) must be greater than delmin + //minimum allowed magnitude of change in x2 is 1.0000009*delmin + //sgndel, the sign of change in x2 is sign of delbis + + if (Math.abs(delx) < delmin) { + sgndel = delbis / Math.abs(delbis); + delx = 1.0000009 * sgndel * delmin; + delprv = delx; + } + + //final check to insure that new x2 is in range of old x2 and x3 + //boundn is negative if new x2 is in range of old x2 and x3 + boundn = delx * (x2 + delx - x3); + if (boundn > 0.0) { + + // procedure stepping out of bounds, use bisection + delx = delbis; + delprv = delbis; + } + //relable variables for next iteration + //x1new = x2old, y1new=y2old + x1 = x2; + y1 = y2; + // next iteration values for x2, y2 + x2 = x2 + delx; + pdetail(x2); + y2 = dPCalc - dP; + } + // ddetail: maximum number of iterations exceeded + gasProps.lStatus = GasConstants.MAX_NUM_OF_ITERATIONS_EXCEEDED; + dRho = x2; + }// ddetail() + + + public void braket(GasProps gasProps) { + //variables local to function + int imax, it; + double del, rhomax, videal; + double rho1, rho2, p1, p2; + //initialize + imax = 200; + rho1 = 0.0; + p1 = 0.0; + rhomax = 1.0 / dKp3; + if (dT > 1.2593 * dU) rhomax = 20.0 * rhomax; + videal = GasConstants.RGASKJ * dT / dP; + if (Math.abs(dB) < (0.167 * videal)) { + rho2 = 0.95 / (videal + dB); + } else { + rho2 = 1.15 / videal; + } + del = rho2 / 20.0; + // start iterative density search loop + for (it = 0; it < imax; it++) { + if (rho2 > rhomax && gasProps.lStatus != GasConstants.MAX_DENSITY_IN_BRAKET_EXCEEDED) { + // density in braket exceeds maximum allowable density + gasProps.lStatus = GasConstants.MAX_DENSITY_IN_BRAKET_EXCEEDED; + del = 0.01 * (rhomax - rho1) + (dP / (GasConstants.RGASKJ * dT)) / 20.0; + rho2 = rho1 + del; + continue; + } + //calculate pressure p2 at density rho2 + pdetail(rho2); + p2 = dPCalc; + //test value of p2 relative to p and relative to p1 + if (p2 > dP) { + //the density root is bracketed (p1

p) + dRhoL = rho1; + dPRhoL = p1; + dRhoH = rho2; + dPRhoH = p2; + gasProps.lStatus = GasConstants.NORMAL; + return; + + } else if (p2 > p1) { + if (gasProps.lStatus == GasConstants.MAX_DENSITY_IN_BRAKET_EXCEEDED) del *= 2.0; + rho1 = rho2; + p1 = p2; + rho2 = rho1 + del; + continue; + } else { + + //lStatus= NEGATIVE_DENSITY_DERIVATIVEindicates that + //pressure has a negative density derivative, since p2 is less than + //some previous pressure + + gasProps.lStatus = GasConstants.NEGATIVE_DENSITY_DERIVATIVE; + dRho = rho1; + return; + } + } + // maximum number of iterations exceeded if we fall through the bottom + gasProps.lStatus = GasConstants.MAX_NUM_OF_ITERATIONS_EXCEEDED; + dRho = rho2; + return; + }// braket() + + public void pdetail(double dD) { + dPCalc = zdetail(dD) * dD * GasConstants.RGASKJ * dT; + }// pdetail() + + + public double zdetail(double d) { + // variables local to function + double D1, D2, D3, D4, D5, D6, D7, D8, D9, exp1, exp2, exp3, exp4; + // Math .Powers of reduced density + D1 = dKp3 * d; + D2 = D1 * D1; + D3 = D2 * D1; + D4 = D3 * D1; + D5 = D4 * D1; + D6 = D5 * D1; + D7 = D6 * D1; + D8 = D7 * D1; + D9 = D8 * D1; + + exp1 = Math.exp(-D1); + exp2 = Math.exp(-D2); + exp3 = Math.exp(-D3); + exp4 = Math.exp(-D4); + + // the following expression for Z was adopted from FORTRAN example in AGA8 + dZ = 1.0 + dB * d + + adFn[12] * D1 * (exp3 - 1.0 - 3.0 * D3 * exp3) + + (adFn[13] + adFn[14] + adFn[15]) * D1 * (exp2 - 1.0 - 2.0 * D2 * exp2) + + (adFn[16] + adFn[17]) * D1 * (exp4 - 1.0 - 4.0 * D4 * exp4) + + (adFn[18] + adFn[19]) * D2 * 2.0 + + (adFn[20] + adFn[21] + adFn[22]) * D2 * (2.0 - 2.0 * D2) * exp2 + + (adFn[23] + adFn[24] + adFn[25]) * D2 * (2.0 - 4.0 * D4) * exp4 + + adFn[26] * D2 * (2.0 - 4.0 * D4) * exp4 + + adFn[27] * D3 * 3.0 + + (adFn[28] + adFn[29]) * D3 * (3.0 - D1) * exp1 + + (adFn[30] + adFn[31]) * D3 * (3.0 - 2.0 * D2) * exp2 + + (adFn[32] + adFn[33]) * D3 * (3.0 - 3.0 * D3) * exp3 + + (adFn[34] + adFn[35] + adFn[36]) * D3 * (3.0 - 4.0 * D4) * exp4 + + (adFn[37] + adFn[38]) * D4 * 4.0 + + (adFn[39] + adFn[40] + adFn[41]) * D4 * (4.0 - 2.0 * D2) * exp2 + + (adFn[42] + adFn[43]) * D4 * (4.0 - 4.0 * D4) * exp4 + + adFn[44] * D5 * 5.0 + + (adFn[45] + adFn[46]) * D5 * (5.0 - 2.0 * D2) * exp2 + + (adFn[47] + adFn[48]) * D5 * (5.0 - 4.0 * D4) * exp4 + + adFn[49] * D6 * 6.0 + + adFn[50] * D6 * (6.0 - 2.0 * D2) * exp2 + + adFn[51] * D7 * 7.0 + + adFn[52] * D7 * (7.0 - 2.0 * D2) * exp2 + + adFn[53] * D8 * (8.0 - D1) * exp1 + + (adFn[54] + adFn[55]) * D8 * (8.0 - 2.0 * D2) * exp2 + + (adFn[56] + adFn[57]) * D9 * (9.0 - 2.0 * D2) * exp2; + return dZ; + + }// zdetail() + + + public double dZdT(double d) { + //variables local to function + double tmp; + int i; + double D1, D2, D3, D4, D5, D6, D7, D8, D9, exp1, exp2, exp3, exp4; + //set up Math .Powers of reduced density + D1 = dKp3 * d; + D2 = D1 * D1; + D3 = D2 * D1; + D4 = D3 * D1; + D5 = D4 * D1; + D6 = D5 * D1; + D7 = D6 * D1; + D8 = D7 * D1; + D9 = D8 * D1; + exp1 = Math.exp(-D1); + exp2 = Math.exp(-D2); + exp3 = Math.exp(-D3); + exp4 = Math.exp(-D4); + // create terms uC*T^-(un+1) from coefficients we've already computed (An[n]) + for (i = 12; i < 58; i++) { + if (adUn[i] != 0 && adFn[i] != 0) { + fx[i] = (adFn[i] * adUn[i] * D1) / dT; + } else { + fx[i] = 0.0; + } + } + //initial part of equation + ddZdT = d * ddBdT; + //n=13 evaluates to zero except for hydrogen, for whom fn = 1 + if (dF != 0) ddZdT += fx[12] - (fx[12] * (1.0 - 3.0 * D3) * exp3); + tmp = (1.0 - 2.0 * D2) * exp2; + ddZdT += (fx[13] - (fx[13] * tmp)); + ddZdT += fx[14] - (fx[14] * tmp); + ddZdT += fx[15] - (fx[15] * tmp); + tmp = (1.0 - 4.0 * D4) * exp4; + ddZdT += fx[16] - (fx[16] * tmp); + ddZdT += fx[17] - (fx[17] * tmp); + ddZdT = ddZdT - (fx[18] + fx[19]) * D1 * 2.0 + - (fx[21] + fx[22]) * D1 * (2.0 - 2.0 * D2) * exp2 + - (fx[23] + fx[24] + fx[25]) * D1 * (2.0 - 4.0 * D4) * exp4 + - fx[26] * D1 * (2.0 - 4.0 * D4) * exp4 + - fx[27] * D2 * 3.0 + - (fx[28] + fx[29]) * D2 * (3.0 - D1) * exp1 + - (fx[30] + fx[31]) * D2 * (3.0 - 2.0 * D2) * exp2 + - (fx[32] + fx[33]) * D2 * (3.0 - 3.0 * D3) * exp3 + - (fx[34] + fx[35] + fx[36]) * D2 * (3.0 - 4.0 * D4) * exp4 + - fx[37] * D3 * 4.0 + - (fx[39] + fx[40] + fx[41]) * D3 * (4.0 - 2.0 * D2) * exp2 + - (fx[42] + fx[43]) * D3 * (4.0 - 4.0 * D4) * exp4 + - fx[44] * D4 * 5.0 + - (fx[45] + fx[46]) * D4 * (5.0 - 2.0 * D2) * exp2 + - (fx[47] + fx[48]) * D4 * (5.0 - 4.0 * D4) * exp4 + - fx[49] * D5 * 6.0 + - fx[50] * D5 * (6.0 - 2.0 * D2) * exp2 + - fx[51] * D6 * 7.0 + - fx[52] * D6 * (7.0 - 2.0 * D2) * exp2 + - fx[53] * D7 * (8.0 - D1) * exp1 + - fx[54] * D7 * (8.0 - 2.0 * D2) * exp2 + - fx[56] * D8 * (9.0 - 2.0 * D2) * exp2; + return ddZdT; + } + + + public double d2ZdT2(double d) { + //variables local to function + double tmp; + int i; + double D1, D2, D3, D4, D5, D6, D7, D8, D9, exp1, exp2, exp3, exp4; + //set up Math .Powers of reduced density + D1 = dKp3 * d; + D2 = D1 * D1; + D3 = D2 * D1; + D4 = D3 * D1; + D5 = D4 * D1; + D6 = D5 * D1; + D7 = D6 * D1; + D8 = D7 * D1; + D9 = D8 * D1; + exp1 = Math.exp(-D1); + exp2 = Math.exp(-D2); + exp3 = Math.exp(-D3); + exp4 = Math.exp(-D4); + // create terms uC*T^-(un+1) from coefficients we've already computed (An[n]) + for (i = 12; i < 58; i++) { + if (adUn[i] != 0 && adFn[i] != 0) { + + fx[i] = (adFn[i] * D1 * adUn[i] * (adUn[i] + 1.0)) / (dT * dT); + } else { + + fx[i] = 0.0; + + } + + } + //initial part of equation + dd2ZdT2 = d * dd2BdT2; + + //n=13 evaluates to zero except for hydrogen, for whom fn = 1 + if (dF != 0) dd2ZdT2 += fx[12] - (fx[12] * (1.0 - 3.0 * D3) * exp3); + tmp = (1.0 - 2.0 * D2) * exp2; + dd2ZdT2 += -fx[13] + (fx[13] * tmp); + dd2ZdT2 += -fx[14] + (fx[14] * tmp); + dd2ZdT2 += -fx[15] + (fx[15] * tmp); + tmp = (1.0 - 4.0 * D4) * exp4; + dd2ZdT2 += -fx[16] + (fx[16] * tmp); + dd2ZdT2 += -fx[17] + (fx[17] * tmp); + dd2ZdT2 = dd2ZdT2 + (fx[18] + fx[19]) * D1 * 2.0 + + (fx[21] + fx[22]) * D1 * (2.0 - 2.0 * D2) * exp2 + + (fx[23] + fx[24] + fx[25]) * D1 * (2.0 - 4.0 * D4) * exp4 + + fx[26] * D1 * (2.0 - 4.0 * D4) * exp4 + + fx[27] * D2 * 3.0 + + (fx[28] + fx[29]) * D2 * (3.0 - D1) * exp1 + + (fx[30] + fx[31]) * D2 * (3.0 - 2.0 * D2) * exp2 + + (fx[32] + fx[33]) * D2 * (3.0 - 3.0 * D3) * exp3 + + (fx[34] + fx[35] + fx[36]) * D2 * (3.0 - 4.0 * D4) * exp4 + + fx[37] * D3 * 4.0 + + (fx[39] + fx[40] + fx[41]) * D3 * (4.0 - 2.0 * D2) * exp2 + + (fx[42] + fx[43]) * D3 * (4.0 - 4.0 * D4) * exp4 + + fx[44] * D4 * 5.0 + + (fx[45] + fx[46]) * D4 * (5.0 - 2.0 * D2) * exp2 + + (fx[47] + fx[48]) * D4 * (5.0 - 4.0 * D4) * exp4 + + fx[49] * D5 * 6.0 + + fx[50] * D5 * (6.0 - 2.0 * D2) * exp2 + + fx[51] * D6 * 7.0 + + fx[52] * D6 * (7.0 - 2.0 * D2) * exp2 + + fx[53] * D7 * (8.0 - D1) * exp1 + + fx[54] * D7 * (8.0 - 2.0 * D2) * exp2 + + fx[56] * D8 * (9.0 - 2.0 * D2) * exp2; + + return dd2ZdT2; + + }// d2ZdT2() + + + public double dZdD(double d) { + double temp, temp1, temp2, temp3; + int i; + double D1, D2, D3, D4, D5, D6, D7, D8, D9, exp1, exp2, exp3, exp4; + // set up Math .Powers of reduced density + D1 = dKp3 * d; + D2 = D1 * D1; + D3 = D2 * D1; + D4 = D3 * D1; + D5 = D4 * D1; + D6 = D5 * D1; + D7 = D6 * D1; + D8 = D7 * D1; + D9 = D8 * D1; + exp1 = Math.exp(-D1); + exp2 = Math.exp(-D2); + exp3 = Math.exp(-D3); + exp4 = Math.exp(-D4); + //create terms uC*T^-(un+1) from coefficients we've already computed (An[n]) + for (i = 12; i < 58; i++) { + fx[i] = adFn[i]; + } + //initial part of equation + ddZdD = dB / dKp3; + //evaluate all remaining terms, simplifying where possible + + //n=13 evaluates to zero except for hydrogen, for whom fn = 1 + if (dF != 0) { + temp1 = -9.0 * D3 * exp3; + temp2 = (1.0 - 3.0 * D3) * exp3; + temp3 = -temp2 * 3.0 * D6; + temp = temp1 + temp2 + temp3; + ddZdD += -fx[12] + fx[12] * temp; + } + //n = 14..16 + temp1 = -4.0 * D2 * exp2; + temp2 = (1.0 - 2.0 * D2) * exp2; + temp3 = -temp2 * 2.0 * D2; + temp = temp1 + temp2 + temp3; + ddZdD += -fx[13] + fx[13] * temp; + ddZdD += -fx[14] + fx[14] * temp; + ddZdD += -fx[15] + fx[15] * temp; + // n =17..18 + temp1 = -16.0 * D4 * exp4; + temp2 = (1.0 - 4.0 * D4) * exp4; + temp3 = -temp2 * 4.0 * D4; + temp = temp1 + temp2 + temp3; + ddZdD += -fx[16] + fx[16] * temp; + ddZdD += -fx[17] + fx[17] * temp; + // n = 19..20 + temp = 4.0 * D1; + ddZdD += fx[18] * temp; + ddZdD += fx[19] * temp; + // n =21..23 + temp1 = -4.0 * D3 * exp2; + temp2 = (2.0 - 2.0 * D2) * 2.0 * D1 * exp2; + temp3 = -temp2 * D2; + temp = temp1 + temp2 + temp3; + ddZdD += fx[20] * temp; + ddZdD += fx[21] * temp; + ddZdD += fx[22] * temp; + // n =24..27 + temp1 = -16.0 * D5 * exp4; + temp2 = (2.0 - 4.0 * D4) * 2.0 * D1 * exp4; + temp3 = -temp2 * 2.0 * D4; + temp = temp1 + temp2 + temp3; + ddZdD += fx[23] * temp; + ddZdD += fx[24] * temp; + ddZdD += fx[25] * temp; + ddZdD += fx[26] * temp; + // n =28 + temp = 9.0 * D2; + ddZdD += fx[27] * temp; + // n =29..30 + temp = -D3 * exp1 + (3.0 - D1) * 3.0 * D2 * exp1; + temp -= (3.0 - D1) * D3 * exp1; + ddZdD += fx[28] * temp; + ddZdD += fx[29] * temp; + // n =31..32 + temp1 = -4.0 * D4 * exp2; + temp2 = (3.0 - 2.0 * D2) * 3.0 * D2 * exp2; + temp3 = -(3.0 - 2.0 * D2) * 2.0 * D4 * exp2; + temp = temp1 + temp2 + temp3; + ddZdD += fx[30] * temp; + ddZdD += fx[31] * temp; + // n =33..34 + temp1 = -9.0 * D5 * exp3; + temp2 = (3.0 - 3.0 * D3) * 3.0 * D2 * exp3; + temp3 = -(3.0 - 3.0 * D3) * 3.0 * D5 * exp3; + temp = temp1 + temp2 + temp3; + ddZdD += fx[32] * temp; + ddZdD += fx[33] * temp; + // n =35..37 + temp1 = -16.0 * D6 * exp4; + temp2 = (3.0 - 4.0 * D4) * 3.0 * D2 * exp4; + temp3 = -(3.0 - 4.0 * D4) * D6 * 4.0 * exp4; + temp = temp1 + temp2 + temp3; + ddZdD += fx[34] * temp; + ddZdD += fx[35] * temp; + ddZdD += fx[36] * temp; + //n = 38..39 + temp = 16.0 * D3; + ddZdD += fx[37] * temp; + ddZdD += fx[38] * temp; + //n = 40..42 + temp1 = -4.0 * D5 * exp2; + temp2 = (4.0 - 2.0 * D2) * 4.0 * D3 * exp2; + temp3 = -(4.0 - 2.0 * D2) * 2.0 * D5 * exp2; + temp = temp1 + temp2 + temp3; + ddZdD += fx[39] * temp; + ddZdD += fx[40] * temp; + ddZdD += fx[41] * temp; + // n =43..44 + temp = -16.0 * D7 * exp4 + (4.0 - 4.0 * D4) * 4.0 * D3 * exp4; + temp -= (4.0 - 4.0 * D4) * D7 * 4.0 * exp4; + ddZdD += fx[42] * temp; + ddZdD += fx[43] * temp; + // n =45 + temp = 25.0 * D4; + ddZdD += fx[44] * temp; + // n =46..47 + temp = -4.0 * D6 * exp2 + (5.0 - 2.0 * D2) * 5.0 * D4 * exp2; + temp -= (5.0 - 2.0 * D2) * D6 * 2.0 * exp2; + ddZdD += fx[45] * temp; + ddZdD += fx[46] * temp; + // n =48..49 + temp = -16.0 * D8 * exp4 + (5.0 - 4.0 * D4) * 5.0 * D4 * exp4; + temp -= (5.0 - 4.0 * D4) * D8 * 4.0 * exp4; + ddZdD += fx[47] * temp; + ddZdD += fx[48] * temp; + // n =50 + temp = 36.0 * D5; + ddZdD += fx[49] * temp; + // n =51 + temp = -4.0 * D7 * exp2 + (6.0 - 2.0 * D2) * 6.0 * D5 * exp2; + temp -= (6.0 - 2.0 * D2) * D7 * 2.0 * exp2; + ddZdD += fx[50] * temp; + // n =52 + temp = 49.0 * D6; + ddZdD += fx[51] * temp; + // n =53 + temp = -4.0 * D8 * exp2 + (7.0 - 2.0 * D2) * 7.0 * D6 * exp2; + temp -= (7.0 - 2.0 * D2) * D8 * 2.0 * exp2; + ddZdD += fx[52] * temp; + // n =54 + temp = -1.0 * D8 * exp1 + (8.0 - D1) * 8.0 * D7 * exp1; + temp -= (8.0 - D1) * D8 * exp1; + ddZdD += fx[53] * temp; + // n =55..56 + temp = -4.0 * D1 * D8 * exp2 + (8.0 - 2.0 * D2) * 8.0 * D7 * exp2; + temp -= (8.0 - 2.0 * D2) * D8 * 2.0 * D1 * exp2; + ddZdD += fx[54] * temp; + ddZdD += fx[55] * temp; + // n =57..58 + temp = -4.0 * D2 * D8 * exp2 + (9.0 - 2.0 * D2) * 9.0 * D8 * exp2; + temp -= (9.0 - 2.0 * D2) * D2 * D8 * 2.0 * exp2; + ddZdD += fx[56] * temp; + ddZdD += fx[57] * temp; + ddZdD *= dKp3; + return ddZdD; + } + + public void relativedensity(GasProps gasProps) { + double dBX, dZa; + double dMWair = 28.96256; + + dBX = -0.12527 + 5.91e-4 * gasProps.dTb - 6.62e-7 * gasProps.dTb * gasProps.dTb; + // calculate compressibility of air + dZa = 1.0 + (dBX * dP) / (GasConstants.RGASKJ * gasProps.dTb); + // calculate ideal gas and real gas relative densities + gasProps.dRD_Ideal = gasProps.dMrx / dMWair; + gasProps.dRD_Real = gasProps.dRD_Ideal * (dZa / gasProps.dZb); + } + + +} \ No newline at end of file diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/service/GBT11062Service.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/service/GBT11062Service.java new file mode 100644 index 0000000..024e3dd --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/service/GBT11062Service.java @@ -0,0 +1,142 @@ +package com.ruoyi.caltools.service; + + +import com.ruoyi.caltools.model.GasProps; +import com.ruoyi.caltools.utils.GasConstants; +import org.springframework.stereotype.Service; +@Service +public class GBT11062Service { + + int iNCC;// number of components + int[] aiCID = new int[21];// component IDs + double[] dXi = new double[21];// mole fraction of component i + // 初始化 adTableMri 数组 + static double[] adTableMri = { 16.0430, 28.0135, 44.0100, 30.0700, 44.0970, 18.0153, 34.0820, 2.0159, 28.0100, 31.9988, 58.1230, 58.1230, 72.1500, 72.1500, 86.1770, 100.2040, 114.2310, 128.2580, 142.2850, 4.0026, 39.9480 }; + // 初始化 adTablePc 数组 + double[] adTablePc = { 4.604, 3.399, 7.382, 4.88, 4.249, 22.118, 9.005, 1.297, 3.499, 5.081, 3.648, 3.797, 3.381, 3.369, 3.012, 2.736, 2.486, 0, 0, 0.2275, 4.876 }; + // 初始化 adTableTc 数组 + double[] adTableTc = { 190.55, 126.1, 304.19, 305.43, 369.82, 647.3, 373.5, 33.2, 132.92, 154.7, 408.13, 425.16, 460.39, 469.6, 507.4, 540.2, 568.76, 0, 0, 5.2, 150.82 }; + // 初始化 adTableBzsx 数组 + double[] adTableBzsx = { 15, 0, 0, 13, 9.5, 0, 45.5, 74.2, 74.2, 0, 8.4, 8.4, 8.3, 8.3, 7.7, 7.0, 0, 0, 0, 0, 0 }; + // 初始化 adTableBzxx 数组 + double[] adTableBzxx = { 5.0, 0, 0, 2.9, 2.1, 0, 4.3, 4.0, 12.5, 0, 1.8, 1.8, 1.4, 1.4, 1.2, 1.0, 0.96, 0, 0, 0, 0 }; + + + double[][] adTableZn = { + {0.9976, 0.9995, 0.9933, 0.99, 0.9789, 0.93, 0.99, 1.0006, 0.9993, 0.999, 0.958, 0.9572, 0.9377, 0.918, 0.892, 0.83, 0.742, 0.613, 0.434, 1.0005, 0.999}, + {0.998, 0.9997, 0.9944, 0.9915, 0.9821, 0.945, 0.99, 1.0006, 0.9995, 0.9992, 0.968, 0.965, 0.948, 0.937, 0.913, 0.866, 0.802, 0.71, 0.584, 1.0005, 0.9992}, + {0.9981, 0.9997, 0.9944, 0.992, 0.9834, 0.952, 0.99, 1.0006, 0.9996, 0.9993, 0.971, 0.9682, 0.953, 0.945, 0.919, 0.876, 0.817, 0.735, 0.623, 1.0005, 0.9993} + }; + + double[][] adTableSqrtbj = { + {0.049, 0.0224, 0.0819, 0.1, 0.1453, 0.2646, 0.1, -0.004, 0.0265, 0.0316, 0.2049, 0.2069, 0.251, 0.2864, 0.3286, 0.4123, 0.5079, 0.6221, 0.7523, 0.0006, 0.0316}, + {0.0447, 0.0173, 0.0748, 0.0922, 0.1338, 0.2345, 0.1, -0.0048, 0.0224, 0.0283, 0.1789, 0.1871, 0.228, 0.251, 0.295, 0.3661, 0.445, 0.5385, 0.645, 0.0002, 0.0283}, + {0.0436, 0.0173, 0.0728, 0.0894, 0.1288, 0.2191, 0.1, -0.0051, 0.02, 0.0265, 0.1703, 0.1783, 0.2168, 0.2345, 0.2846, 0.3521, 0.4278, 0.5148, 0.614, 0, 0.0265} + }; + + double[][] adTableHhvMol = { + {892.97, 0, 0, 1564.34, 2224.01, 45.074, 562.94, 286.63, 282.8, 0, 2874.2, 2883.82, 3535.98, 3542.89, 4203.23, 4862.87, 5522.4, 6182.91, 6842.69, 0, 0}, + {891.56, 0, 0, 1562.14, 2221.1, 44.433, 562.38, 286.15, 282.91, 0, 2870.58, 2879.76, 3531.68, 3538.6, 4198.24, 4857.18, 5516.01, 6175.82, 6834.9, 0, 0}, + {891.09, 0, 0, 1561.41, 2220.13, 44.224, 562.19, 285.99, 282.95, 0, 2869.38, 2878.57, 3530.24, 3537.17, 4196.58, 4855.29, 5513.88, 6173.46, 6832.31, 0, 0}, + {890.63, 0, 0, 1560.69, 2219.17, 44.016, 562.01, 285.83, 282.98, 0, 2868.2, 2877.4, 3528.83, 3535.77, 4194.95, 4853.43, 5511.8, 6171.15, 6829.77, 0, 0} + }; + + double[][] adTableLhvMol = { + {802.82, 0, 0, 1429.12, 2043.71, 0, 517.87, 241.56, 282.8, 0, 2648.83, 2658.45, 3265.54, 3272.45, 3887.71, 4502.28, 5116.73, 5732.17, 6346.88, 0, 0}, + {802.69, 0, 0, 1428.84, 2043.37, 0, 517.95, 241.72, 282.91, 0, 2648.42, 2657.6, 3265.08, 3272, 3887.21, 4501.72, 5116.11, 5731.49, 6346.14, 0, 0}, + {802.65, 0, 0, 1428.74, 2043.23, 0, 517.97, 241.76, 282.95, 0, 2648.26, 2657.45, 3264.89, 3271.83, 3887.01, 4501.49, 5115.87, 5731.22, 6345.85, 0, 0}, + {802.6, 0, 0, 1428.64, 2043.11, 0, 517.99, 241.81, 282.98, 0, 2648.12, 2657.32, 3264.73, 3271.67, 3886.84, 4501.3, 5115.66, 5730.99, 6345.59, 0, 0} + }; + int i; + + double dMair = 28.9626; + double dZair = 0; + + public void Run(GasProps gasProps) { + for (int i = 0; i < GasConstants.NUMBEROFCOMPONENTS; i++) dXi[i] = 0; + iNCC = -1; + for (i = 0; i < GasConstants.NUMBEROFCOMPONENTS; i++) { + if (gasProps.adMixture[i] > 0.0) { + iNCC = iNCC + 1; + aiCID[iNCC] = i; + dXi[iNCC] = gasProps.adMixture[i]; + } + } + iNCC = iNCC + 1; + + for (i = 0; i < GasConstants.NUMBEROFCOMPONENTS; i++) { + if (gasProps.adMixture[i] != 0) { + gasProps.dPc += adTablePc[i] * gasProps.adMixture[i]; + gasProps.dTC += adTableTc[i] * gasProps.adMixture[i]; + if (adTableBzsx[i] != 0) { + gasProps.dBzsx += gasProps.adMixture[i] / adTableBzsx[i]; + gasProps.dBzxx += gasProps.adMixture[i] / adTableBzxx[i]; + } + + if (i >= 10 & i <= 18) { + gasProps.dC4j += gasProps.adMixture[i] * adTableMri[i]; + } + if (i >= 12 & i <= 18) { + gasProps.dC5j += gasProps.adMixture[i] * adTableMri[i]; + } + if (i >= 14 & i <= 18) { + gasProps.dC6j += gasProps.adMixture[i] * adTableMri[i]; + } + if (i == 3) { + gasProps.dC2 += gasProps.adMixture[i] * adTableMri[i]; + } + + switch (gasProps.dCbtj) { + case 2: + gasProps.dZb11062 += adTableSqrtbj[0][i] * gasProps.adMixture[i]; + gasProps.dHhvMol += adTableHhvMol[0][i] * gasProps.adMixture[i]; + gasProps.dLhvMol += adTableLhvMol[0][i] * gasProps.adMixture[i]; + dZair = 0.99941; + break; + case 1: + gasProps.dZb11062 += adTableSqrtbj[1][i] * gasProps.adMixture[i]; + gasProps.dHhvMol += adTableHhvMol[1][i] * gasProps.adMixture[i]; + gasProps.dLhvMol += adTableLhvMol[1][i] * gasProps.adMixture[i]; + dZair = 0.99958; + break; + case 0: + gasProps.dZb11062 += adTableSqrtbj[2][i] * gasProps.adMixture[i]; + gasProps.dHhvMol += adTableHhvMol[2][i] * gasProps.adMixture[i]; + gasProps.dLhvMol += adTableLhvMol[2][i] * gasProps.adMixture[i]; + dZair = 0.99963; + break; + } + } + } + gasProps.dBzsx = 1 / gasProps.dBzsx; + gasProps.dBzxx = 1 / gasProps.dBzxx; + gasProps.dZb11062 = 1 - gasProps.dZb11062 * gasProps.dZb11062; + gasProps.dHhvm = gasProps.dHhvMol / gasProps.dMrx; + gasProps.dLhvm = gasProps.dLhvMol / gasProps.dMrx; + gasProps.dHhvv = gasProps.dHhvMol * gasProps.dPb / gasProps.dTb / 8314.510 / gasProps.dZb11062; + gasProps.dLhvv = gasProps.dLhvMol * gasProps.dPb / gasProps.dTb / 8314.510 / gasProps.dZb11062; + gasProps.dRD_Ideal11062 = gasProps.dMrx / dMair; + gasProps.dRD_Real11062 = gasProps.dRD_Ideal11062 * dZair / gasProps.dZb11062; + + gasProps.dRhob11062 = gasProps.dMrx * gasProps.dPb / 8314.51 / gasProps.dTb / gasProps.dZb11062; + gasProps.dRhof11062 = gasProps.dMrx * gasProps.dPf / 8314.51 / gasProps.dTf / gasProps.dZf; + gasProps.dWobbeIndex = gasProps.dHhvv / Math.sqrt(gasProps.dRD_Real11062); + gasProps.dC3j = gasProps.dC4j + gasProps.adMixture[4] * adTableMri[4]; + gasProps.dC2j = gasProps.dC3j + gasProps.adMixture[3] * adTableMri[3]; + gasProps.dC3C4 = gasProps.adMixture[4] * adTableMri[4] + gasProps.adMixture[10] * adTableMri[10] + gasProps.adMixture[11] * adTableMri[11]; + gasProps.dTotalC = gasProps.dC2j + gasProps.adMixture[0] * adTableMri[0]; + + gasProps.dTotalC = gasProps.dTotalC * gasProps.dPb / 8314.51 / gasProps.dTb / gasProps.dZb11062; + gasProps.dC2 = gasProps.dC2 * gasProps.dPb / 8314.51 / gasProps.dTb / gasProps.dZb11062; + gasProps.dC3C4 = gasProps.dC3C4 * gasProps.dPb / 8314.51 / gasProps.dTb / gasProps.dZb11062; + gasProps.dC2j = gasProps.dC2j * gasProps.dPb / 8314.51 / gasProps.dTb / gasProps.dZb11062; + gasProps.dC3j = gasProps.dC3j * gasProps.dPb / 8314.51 / gasProps.dTb / gasProps.dZb11062; + gasProps.dC4j = gasProps.dC4j * gasProps.dPb / 8314.51 / gasProps.dTb / gasProps.dZb11062; + gasProps.dC5j = gasProps.dC5j * gasProps.dPb / 8314.51 / gasProps.dTb / gasProps.dZb11062; + gasProps.dC6j = gasProps.dC6j * gasProps.dPb / 8314.51 / gasProps.dTb / gasProps.dZb11062; + + ISO9300Service.calculateViscosity(gasProps); + + } + +} diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/service/ISO9300Service.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/service/ISO9300Service.java new file mode 100644 index 0000000..c074dba --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/service/ISO9300Service.java @@ -0,0 +1,175 @@ +package com.ruoyi.caltools.service; + + +import com.ruoyi.caltools.model.GasProps; + +public class ISO9300Service { + + // 基于Chapman-Enskog理论的计算 高精度粘度计算 音速喷嘴ISO9300 + public static double calculateViscosity(GasProps gas) { + double BOLTZMANN = 1.380649e-23; // 玻尔兹曼常数 (J/K) + double Avogadro = 6.02214076e23; // 添加阿伏伽德罗常数 + LJ mixLj = getLJParameters(gas); // Lennard-Jones参数 + // 转换为SI单位 + double sigma = mixLj.sigma ; // Å → m + double epsilon = mixLj.epsilonK * BOLTZMANN; // K → + + // 计算无量纲温度 + double T = gas.getdTf(); + double Tstar = T * BOLTZMANN / epsilon; + + double omega = getOmega(Tstar); + + // 查普曼-恩斯柯格公式 + double M_avg = gas.getdMrx() / 1000; // g/mol → kg/mol + double m = M_avg / Avogadro; + double viscosity = 5.0 / 16.0 * Math.sqrt(Math.PI * m * BOLTZMANN * T) + / (Math.PI * Math.pow(sigma, 2) * omega); + + gas.setdMu(viscosity); + return viscosity; + + } + + + + public static LJ getLJParameters(GasProps gas) { + double sigmaMix = 0; + double epsilonKMix = 0; + int n = gas.adMixture.length; + + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + + double xixj = gas.adMixture[i] * gas.adMixture[j]; + // Lorentz规则 + double sigmaPair = (LjParameters[i][0] + LjParameters[j][0]) / 2; + sigmaMix += xixj * sigmaPair; + // Berthelot规则 + 极性修正 + double epsilonPair = Math.sqrt(LjParameters[i][1] * LjParameters[j][1]) * polarityFactors[i] * polarityFactors[j]; + epsilonKMix += xixj * epsilonPair; + } + } + + return new LJ(sigmaMix, epsilonKMix); + } + + // 天然气21个组分的LJ参数 + + public static double[][] LjParameters = { + {3.758e-10, 148.6}, // 甲烷 C1 + {3.798e-10, 71.4}, // 氮气 N2 + {3.941e-10, 195.2}, // 二氧化碳 CO2 + {4.443e-10, 215.7}, // 乙烷 C2 + {5.118e-10, 237.1}, // 丙烷 C3 + {2.75e-10, 80.0}, // 水 H2O + {3.623e-10, 301.1}, // 硫化氢 H2S + {2.827e-10, 59.7}, // 氢气 H2 + {3.690e-10, 91.7}, // 一氧化碳 CO + {3.467e-10, 106.7}, // 氧气 O2 + {5.278e-10, 267.0}, // 异丁烷 iC4 + {5.341e-10, 274.7}, // 正丁烷 nC4 + {5.734e-10, 330.1}, // 异戊烷 iC5 + {5.784e-10, 341.1}, // 正戊烷 nC5 + {6.260e-10, 412.3}, // 己烷 C6 + {6.812e-10, 467.3}, // 庚烷 C7(注:此处庚烷参数按趋势估算) + {7.294e-10, 532.1}, // 辛烷 C8(注:此处辛烷参数按趋势估算) + {7.700e-10, 614.2}, // 壬烷 C9(注:此处壬烷参数按趋势估算) + {8.170e-10, 681.5}, // 癸烷 C10(注:此处癸烷参数按趋势估算) + {2.551e-10, 10.22}, // 氦气 He + {3.405e-10, 124.0} // 氩气 Ar + }; + // 极性修正系数(1.0表示非极性) + // 极性修正系数数组(与LjParameters数组顺序严格对应) + private static final double[] polarityFactors = { + // 1. 甲烷 C1 + 1.00, // 非极性分子(对称四面体结构) + + // 2. 氮气 N2 + 1.02, // 微弱四极矩(J. Chem. Phys. 129, 034306) + + // 3. 二氧化碳 CO2 + 1.05, // 四极矩修正(Ind. Eng. Chem. Res. 2019, 58, 5, 1964–1972) + + // 4. 乙烷 C2 + 1.00, // 非极性(对称结构) + + // 5. 丙烷 C3 + 1.00, // 非极性(链状烷烃) + + // 6. 水 H2O + 1.18, // 强极性修正(J. Phys. Chem. B 2005, 109, 15, 7053–7062) + + // 7. 硫化氢 H2S + 1.12, // 中等极性(J. Chem. Eng. Data 2008, 53, 3, 726–729) + + // 8. 氢气 H2 + 1.00, // 非极性(同核双原子) + + // 9. 一氧化碳 CO + 1.03, // 微弱偶极矩(J. Mol. Liq. 2020, 320, 114432) + + // 10. 氧气 O2 + 1.01, // 微弱顺磁性(通常视为非极性) + + // 11. 异丁烷 iC4 + 1.00, // 支链烷烃(非极性) + + // 12. 正丁烷 nC4 + 1.00, // 直链烷烃(非极性) + + // 13. 异戊烷 iC5 + 1.00, // 支链烷烃(非极性) + + // 14. 正戊烷 nC5 + 1.00, // 直链烷烃(非极性) + + // 15. 己烷 C6 + 1.00, // 长链烷烃(非极性) + + // 16. 庚烷 C7 + 1.00, // 长链烷烃(非极性) + + // 17. 辛烷 C8 + 1.00, // 长链烷烃(非极性) + + // 18. 壬烷 C9 + 1.00, // 长链烷烃(非极性) + + // 19. 癸烷 C10 + 1.00, // 长链烷烃(非极性) + + // 20. 氦气 He + 1.00, // 惰性气体(非极性) + + // 21. 氩气 Ar + 1.00 // 惰性气体(非极性) + }; + // 碰撞积分Ω(2,2)* 近似计算(Neufeld多项式) + private static double getOmega(double Tstar) { + if (Tstar < 0.1) { + return 2.0 / (3.0 * Tstar); // 低温量子修正 + } else if (Tstar > 400) { + return 0.92 * Math.log(Tstar) / Tstar; // 高温渐近解 + } else { + double A = 1.16145; + double B = 0.14874; + double C = 0.52487; + double D = 0.77320; + double E = 2.16178; + double F = 2.43787; + return A / Math.pow(Tstar, B) + + C / Math.exp(D * Tstar) + + E / Math.exp(F * Tstar); + } + } + public static class LJ { + public final double sigma; // 碰撞直径 (m) + public final double epsilonK; // ε/k (K) + + public LJ(double sigma, double epsilonK) { + this.sigma = sigma; + this.epsilonK = epsilonK; + } + } +} diff --git a/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/service/ThermService.java b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/service/ThermService.java new file mode 100644 index 0000000..e59e521 --- /dev/null +++ b/ruoyi-models/ruoyi-ngtools/src/main/java/com/ruoyi/caltools/service/ThermService.java @@ -0,0 +1,431 @@ +package com.ruoyi.caltools.service; + + +import com.ruoyi.caltools.model.GasProps; +import com.ruoyi.caltools.utils.GasConstants; +import org.springframework.stereotype.Service; + +@Service +public class ThermService { + private static final double CAL_TH = 4.1840; + public int coefA = 0; + public int coefB = 1; + public int coefC = 2; + public int coefD = 3; + public int coefE = 4; + public int coefF = 5; + public int coefG = 6; + public int coefH = 7; + public int coefI = 8; + public int coefJ = 9; + public int coefK = 10; + + double dPdD;// partial deriv of P wrt D + double dPdT;// partial deriv of P wrt T + double dSi;// ideal gas specific entropy, kJ/kg.K + double dTold;// temperature previously used + double dMrxold;// mixture molar mass previously used + //set the number of points for quadrature + int GK_points = 5; + // 初始化 GK_root 数组 + double[] GK_root = { 0.14887433898163121088, 0.43339539412924719080, 0.67940956829902440263, 0.86506336668898451073, 0.97390652851717172008 }; + // 初始化 GK_weight 数组 + double[] GK_weight = { 0.29552422471475286217, 0.26926671930999634918, 0.21908636251598204295, 0.14945134915058059038, 0.066671344308688137179 }; + + private final double[][] ThermConstants = { + {-29776.4, 7.95454, 43.9417, 1037.09, 1.56373, 813.205, -24.9027, 1019.98, -10.1601, 1070.14, -20.0615}, + {-3495.34, 6.95587, 0.272892, 662.738, -0.291318, -680.562, 1.78980, 1740.06, 0.0, 100.0, 4.49823}, + {20.7307, 6.96237, 2.68645, 500.371, -2.56429, -530.443, 3.91921, 500.198, 2.13290, 2197.22, 5.81381}, + {-37524.4, 7.98139, 24.3668, 752.320, 3.53990, 272.846, 8.44724, 1020.13, -13.2732, 869.510, -22.4010}, + {-56072.1, 8.14319, 37.0629, 735.402, 9.38159, 247.190, 13.4556, 1454.78, -11.7342, 984.518, -24.0426}, + {-13773.1, 7.97183, 6.27078, 2572.63, 2.05010, 1156.72, 0.0, 100.0, 0.0, 100.0, -3.24989}, + {-10085.4, 7.94680, -0.08380, 433.801, 2.85539, 843.792, 6.31595, 1481.43, -2.88457, 1102.23, -0.51551}, + {-5565.60, 6.66789, 2.33458, 2584.98, 0.749019, 559.656, 0.0, 100.0, 0.0, 100.0, -7.94821}, + {-2753.49, 6.95854, 2.02441, 1541.22, 0.096774, 3674.81, 0.0, 100.0, 0.0, 100.0, 6.23387}, + {-3497.45, 6.96302, 2.40013, 2522.05, 2.21752, 1154.15, 0.0, 100.0, 0.0, 100.0, 9.19749}, + {-72387.0, 17.8143, 58.2062, 1787.39, 40.7621, 808.645, 0.0, 100.0, 0.0, 100.0, -44.1341}, + {-72674.8, 18.6383, 57.4178, 1792.73, 38.6599, 814.151, 0.0, 100.0, 0.0, 100.0, -46.1938}, + {-91505.5, 21.3861, 74.3410, 1701.58, 47.0587, 775.899, 0.0, 100.0, 0.0, 100.0, -60.2474}, + {-83845.2, 22.5012, 69.5789, 1719.58, 46.2164, 802.174, 0.0, 100.0, 0.0, 100.0, -62.2197}, + {-94982.5, 26.6225, 80.3819, 1718.49, 55.6598, 802.069, 0.0, 100.0, 0.0, 100.0, -77.5366}, + {-103353.0, 30.4029, 90.6941, 1669.32, 63.2028, 786.001, 0.0, 100.0, 0.0, 100.0, -92.0164}, + {-109674.0, 34.0847, 100.253, 1611.55, 69.7675, 768.847, 0.0, 100.0, 0.0, 100.0, -106.149}, + {-122599.0, 38.5014, 111.446, 1646.48, 80.5015, 781.588, 0.0, 100.0, 0.0, 100.0, -122.444}, + {-133564.0, 42.7143, 122.173, 1654.85, 90.2255, 785.564, 0.0, 100.0, 0.0, 100.0, -138.006}, + {0.0, 4.9680, 0.0, 100.0, 0.0, 100.0, 0.0, 100.0, 0.0, 100.0, 0.0}, + {0.0, 4.9680, 0.0, 100.0, 0.0, 100.0, 0.0, 100.0, 0.0, 100.0, 0.0} + }; + public ThermService() + { + // initialize 3 history-sensitive variables + dSi = 0.0; + dTold = 0.0; + dMrxold = 0.0; + } + + public void Run(GasProps gasProps, com.ruoyi.caltools.service.DetailService detailService) + { + //local variables + double c, x, y, z; + //first run basic set of functions within AGA 8 (1994) Detail Method + detailService.run(gasProps); + //find first partial derivative of Z wrt D + detailService.dZdD(gasProps.dDf); + //find real gas cv, cp, specific enthalpy and entropy + CprCvrHS(gasProps, detailService); + //ratio of real gas specific heats + gasProps.dk = gasProps.dCp / gasProps.dCv; + //solve c in three steps, for clarity and ease of debugging + x = gasProps.dk * GasConstants.RGAS * 1000.0 * gasProps.dTf; + y = gasProps.dMrx; + z = gasProps.dZf + gasProps.dDf * detailService.ddZdD; + //calculate c, which is SOS^2 + c = (x / y) * z; + //speed of sound + gasProps.dSOS = Math.sqrt(c); + //calculate the real gas isentropic exponent + //using expression functionally equivalent to Equation 3.2 + gasProps.dKappa = (c * gasProps.dRhof) / gasProps.dPf; + return; + } + private double CpiMolar(GasProps gasProps) { + double cp = 0.0; + double Cpx; + double DT, FT, HT, JT; + double Dx, Fx, Hx, Jx; + double T; + T = gasProps.dTf; + for (int i = 0; i < GasConstants.NUMBEROFCOMPONENTS; i++) { + if (gasProps.getAdMixture()[i] > 0) { + // 计算每个组分的贡献 + Cpx = 0.0; + // calculate species intermediate terms + DT = ThermConstants[i][ coefD] / T; + FT = ThermConstants[i][ coefF] / T; + HT = ThermConstants[i][ coefH] / T; + JT = ThermConstants[i][ coefJ] / T; + // use intermediate terms to avoid redundant calcs + Dx = DT / Math.sinh(DT); + Fx = FT / Math.cosh(FT); + Hx = HT / Math.sinh(HT); + Jx = JT / Math.cosh(JT); + Cpx += ThermConstants[i][ coefB]; + Cpx += ThermConstants[i][ coefC] * Dx * Dx; + Cpx += ThermConstants[i][ coefE] * Fx * Fx; + Cpx += ThermConstants[i][ coefG] * Hx * Hx; + Cpx += ThermConstants[i][ coefI] * Jx * Jx; + //use current mole fraction to weight the contribution + Cpx *= gasProps.adMixture[i]; + //add this contribution to the sum + cp += Cpx; + } + } + return cp * CAL_TH; + } + // coth 函数实现 + private static double coth(double x) { + return 1.0 / Math.tanh(x); + } + // Ho 函数 + public double Ho(GasProps gasProps) { + double H = 0.0; + double Hx; + double DT, FT, HT, JT; + double cothDT, tanhFT, cothHT, tanhJT; + double T = gasProps.dTf; + + for (int i = 0; i < GasConstants.NUMBEROFCOMPONENTS; i++) { + if (gasProps.adMixture[i] <= 0.0) continue; + Hx = 0.0; + + DT = ThermConstants[i][coefD] / T; + FT = ThermConstants[i][coefF] / T; + HT = ThermConstants[i][coefH] / T; + JT = ThermConstants[i][coefJ] / T; + + cothDT = coth(DT); + tanhFT = Math.tanh(FT); + cothHT = coth(HT); + tanhJT = Math.tanh(JT); + + Hx += ThermConstants[i][coefA]; + Hx += ThermConstants[i][coefB] * T; + Hx += ThermConstants[i][coefC] * ThermConstants[i][coefD] * cothDT; + Hx -= ThermConstants[i][coefE] * ThermConstants[i][coefF] * tanhFT; + Hx += ThermConstants[i][coefG] * ThermConstants[i][coefH] * cothHT; + Hx -= ThermConstants[i][coefI] * ThermConstants[i][coefJ] * tanhJT; + + Hx *= gasProps.adMixture[i]; + H += Hx; + } + + H *= CAL_TH; + H /= gasProps.dMrx; + return H * 1000.0; + } + + // So 函数 + public double So(GasProps gasProps) { + double S = 0.0; + double Sx; + double DT, FT, HT, JT; + double cothDT, tanhFT, cothHT, tanhJT; + double sinhDT, coshFT, sinhHT, coshJT; + double T = gasProps.dTf; + + for (int i = 0; i < GasConstants.NUMBEROFCOMPONENTS; i++) { + if (gasProps.adMixture[i] <= 0.0) continue; + Sx = 0.0; + + DT = ThermConstants[i][coefD] / T; + FT = ThermConstants[i][coefF] / T; + HT = ThermConstants[i][coefH] / T; + JT = ThermConstants[i][coefJ] / T; + + cothDT = coth(DT); + tanhFT = Math.tanh(FT); + cothHT = coth(HT); + tanhJT = Math.tanh(JT); + + sinhDT = Math.sinh(DT); + coshFT = Math.cosh(FT); + sinhHT = Math.sinh(HT); + coshJT = Math.cosh(JT); + + Sx += ThermConstants[i][coefK]; + Sx += ThermConstants[i][coefB] * Math.log(T); + Sx += ThermConstants[i][coefC] * (DT * cothDT - Math.log(sinhDT)); + Sx -= ThermConstants[i][coefE] * (FT * tanhFT - Math.log(coshFT)); + Sx += ThermConstants[i][coefG] * (HT * cothHT - Math.log(sinhHT)); + Sx -= ThermConstants[i][coefI] * (JT * tanhJT - Math.log(coshJT)); + + Sx *= gasProps.adMixture[i]; + S += Sx; + } + + S *= CAL_TH; + S /= gasProps.dMrx; + return S * 1000.0; + } + + // CprCvrHS 函数 + public void CprCvrHS(GasProps gasProps, com.ruoyi.caltools.service.DetailService detailService) { + double Cvinc = 0.0; + double Cvr, Cpr; + double Hinc = 0.0; + double Sinc = 0.0; + double Smixing = 0.0; + double Cp = CpiMolar(gasProps); + double Si; + + gasProps.dHo = Ho(gasProps); + Si = So(gasProps); + + gasProps.dCpi = (Cp * 1000.0) / gasProps.dMrx; + + for (int i = 0; i < GK_points; i++) { + double x = gasProps.dDf * (1.0 + GK_root[i]) / 2.0; + detailService.zdetail(x); + detailService.dZdT(x); + detailService.d2ZdT2(x); + + Hinc += GK_weight[i] * detailService.ddZdT / x; + Cvinc += GK_weight[i] * (2.0 * detailService.ddZdT + gasProps.dTf * detailService.dd2ZdT2) / x; + Sinc += GK_weight[i] * (detailService.dZ + gasProps.dTf * detailService.ddZdT - 1.0) / x; + + x = gasProps.dDf * (1.0 - GK_root[i]) / 2.0; + detailService.zdetail(x); + detailService.dZdT(x); + detailService.d2ZdT2(x); + + Hinc += GK_weight[i] * detailService.ddZdT / x; + Cvinc += GK_weight[i] * (2.0 * detailService.ddZdT + gasProps.dTf * detailService.dd2ZdT2) / x; + Sinc += GK_weight[i] * (detailService.dZ + gasProps.dTf * detailService.ddZdT - 1.0) / x; + } + + detailService.zdetail(gasProps.dDf); + detailService.dZdT(gasProps.dDf); + detailService.d2ZdT2(gasProps.dDf); + + Cvr = Cp - GasConstants.RGAS * (1.0 + gasProps.dTf * Cvinc * 0.5 * gasProps.dDf); + + double a = (gasProps.dZf + gasProps.dTf * detailService.ddZdT); + double b = (gasProps.dZf + gasProps.dDf * detailService.ddZdD); + + double dPdT =GasConstants. RGAS * gasProps.dDf * a; + double dPdD = GasConstants.RGAS * gasProps.dTf * b; + + Cpr = Cvr + GasConstants.RGAS * ((a * a) / b); + + Cpr /= gasProps.dMrx; + Cvr /= gasProps.dMrx; + + gasProps.dCv = Cvr * 1000.0; + gasProps.dCp = Cpr * 1000.0; + + gasProps.dH = gasProps.dHo + 1000.0 * GasConstants.RGAS * gasProps.dTf * (gasProps.dZf - 1.0 - gasProps.dTf * Hinc * 0.5 * gasProps.dDf) / gasProps.dMrx; + + for (int i = 0; i < GasConstants.NUMBEROFCOMPONENTS; i++) { + if (gasProps.adMixture[i] != 0) Smixing += gasProps.adMixture[i] * Math.log(gasProps.adMixture[i]); + } + Smixing *= GasConstants.RGAS; + + gasProps.dS = Si - Smixing - 1000.0 * GasConstants.RGAS * (Math.log(gasProps.dPf / 101325.0) - Math.log(gasProps.dZf) + Sinc * 0.5 * gasProps.dDf) / gasProps.dMrx; + } + + // HS_Mode 函数 + public void HS_Mode( GasProps gasProps, DetailService detailService, double H, double S, boolean bGuess) { + double s0 = S; + double h0 = H; + double t1, t2, tmin, tmax; + double p1, p2, px, pmin, pmax; + double delta1, delta2; + double tolerance = 0.001; + if (bGuess) { + t1 = gasProps.dTf; + px = gasProps.dPf; + pmax = px * 2.0; + pmin = px * 0.1; + tmax = t1 * 1.5; + tmin = t1 * 0.67; + } else { + t1 = 273.15; + px = 1013250.0; + pmax = GasConstants.P_MAX; + pmin = 10000.0; + tmax = GasConstants.T_MAX; + tmin = GasConstants.T_MIN; + } + + t2 = t1 + 10.0; + + detailService.run(gasProps); + double h1 = H(gasProps, detailService) - h0; + + for (int i = 0; i < GasConstants.MAX_NUM_OF_ITERATIONS; i++) { + gasProps.dTf = t2; + p1 = px; + p2 = px * 0.1; + gasProps.dPf = p1; + detailService.run(gasProps); + double s1 = S(gasProps, detailService) - s0; + + for (int j = 0; j < GasConstants.MAX_NUM_OF_ITERATIONS; j++) { + gasProps.dPf = p2; + detailService.run(gasProps); + double s2 = S(gasProps, detailService) - s0; + + delta2 = Math.abs(s1 - s2) / s0; + if (delta2 < tolerance) break; + + double p0 = p2; + p2 = (p1 * s2 - p2 * s1) / (s2 - s1); + + if (p2 <= pmin) p2 = pmin; + if (p2 >= pmax) p2 = pmax; + + p1 = p0; + s1 = s2; + } + + if (gasProps.lStatus == GasConstants.MAX_NUM_OF_ITERATIONS_EXCEEDED) break; + + double h2 = H(gasProps, detailService) - h0; + delta1 = Math.abs(h1 - h2) / h0; + + if (delta1 < tolerance && i > 0) break; + + double t0 = t2; + t2 = (t1 * h2 - t2 * h1) / (h2 - h1); + + if (t2 >= tmax) t2 = tmax; + if (t2 <= tmin) { + t2 = t0 + 10.0; + gasProps.dTf = t2; + detailService.run(gasProps); + h2 = H(gasProps, detailService) - h0; + } + + t1 = t0; + h1 = h2; + } + + if (gasProps.lStatus == GasConstants.MAX_NUM_OF_ITERATIONS_EXCEEDED) { + gasProps.lStatus = GasConstants.MAX_NUM_OF_ITERATIONS_EXCEEDED; + } + } + + // H 函数 + public double H(GasProps gasProps, DetailService detailService) { + double Hinc = 0.0; + gasProps.dHo = Ho(gasProps); + + for (int i = 0; i < GK_points; i++) { + double x = gasProps.dDf * (1.0 + GK_root[i]) / 2.0; + detailService.zdetail(x); + detailService.dZdT(x); + detailService.d2ZdT2(x); + + Hinc += GK_weight[i] * detailService.ddZdT / x; + if (i == 10) break; + + x = gasProps.dDf * (1.0 - GK_root[i]) / 2.0; + detailService.zdetail(x); + detailService.dZdT(x); + detailService.d2ZdT2(x); + + Hinc += GK_weight[i] * detailService.ddZdT / x; + } + + detailService.zdetail(gasProps.dDf); + detailService.dZdT(gasProps.dDf); + detailService.d2ZdT2(gasProps.dDf); + + gasProps.dH = gasProps.dHo + 1000.0 * GasConstants.RGAS * gasProps.dTf * (gasProps.dZf - 1.0 - gasProps.dTf * Hinc * 0.5 * gasProps.dDf) / gasProps.dMrx; + return gasProps.dH; + } + + + + double S(GasProps gasProps, DetailService detailService) + { + double Sinc; double Smixing; double x; + int i; + //initialize integral + Sinc = 0.0; + //initialize entropy of mixing + Smixing = 0.0; + //integrate partial derivatives from D=0 to D=D, applying Gauss-Kronrod quadrature + for (i = 0; i + + + ruoyi-models + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-online + + + online系统模块 + + + + + + com.ruoyi.geekxd + ruoyi-common + + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/controller/OnLineController.java b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/controller/OnLineController.java new file mode 100644 index 0000000..32a945b --- /dev/null +++ b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/controller/OnLineController.java @@ -0,0 +1,141 @@ +package com.ruoyi.online.controller; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.security.service.IPermissionService; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.online.domain.OnlineMb; +import com.ruoyi.online.service.IOnlineMbService; +import com.ruoyi.online.utils.SqlMapper; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 在线接口 + * + * @author Dftre + * @date 2024-01-26 + */ +@RestController +@Anonymous +@RequestMapping("/online") +public class OnLineController extends BaseController { + @Autowired + private IOnlineMbService onlineMbService; + + @Autowired + private IPermissionService permissionService; + + @Autowired + private SqlSessionFactory sqlSessionFactory; + + @SuppressWarnings("unchecked") + public Map getParams(HashMap params, HashMap data) { + Map object = new HashMap<>(); + HashMap object_params = new HashMap(); + String keyregex = "params\\[(.*?)\\]"; + Pattern pattern = Pattern.compile(keyregex); + if (params != null) { + params.keySet().forEach(key -> { + Matcher matcher = pattern.matcher(key); + if (matcher.find()) { + object_params.put(matcher.group(1), params.get(key)); + } else { + object.put(key, params.get(key)); + } + }); + } + if (data != null) { + if (data.containsKey("params")) { + object_params.putAll((HashMap) data.get("params")); + data.remove("params"); + } + object.putAll(data); + } + object.put("params", object_params); + return object; + } + + public Boolean checkPermission(String permissionType, String permissionValue) { + if (permissionType == null) { + return true; + } + return switch (permissionType) { + case "hasPermi" -> permissionService.hasPermi(permissionValue); + case "lacksPermi" -> permissionService.lacksPermi(permissionValue); + case "hasAnyPermi" -> permissionService.hasAnyPermi(permissionValue); + case "hasRole" -> permissionService.hasRole(permissionValue); + case "lacksRole" -> permissionService.lacksRole(permissionValue); + case "hasAnyRoles" -> permissionService.hasAnyRoles(permissionValue); + default -> true; + }; + } + + public Object processingMapper(String sqlContext, String actuatot, Map params) { + String sql = ""; + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + SqlMapper sqlMapper = new SqlMapper(sqlSession); + Object res = null; + res = switch (actuatot) { + case "selectList" -> getDataTable(sqlMapper.selectList(sql, params)); + case "insert" -> toAjax(sqlMapper.insert(sql, params)); + case "selectOne" -> success(sqlMapper.selectOne(sql, params)); + case "update" -> toAjax(sqlMapper.update(sql, params)); + case "delete" -> toAjax(sqlMapper.delete(sql, params)); + default -> AjaxResult.error(500, "系统错误,执行器错误"); + }; + return res; + } finally { + sqlSession.close(); + } + + } + + @RequestMapping("/api/**") + public Object api(@RequestParam(required = false) HashMap params, + @RequestBody(required = false) HashMap data, HttpServletRequest request, + HttpServletResponse response) { + OnlineMb selectOnlineMb = new OnlineMb(); + selectOnlineMb.setPath(request.getRequestURI().replace("/online/api", "")); + selectOnlineMb.setMethod(request.getMethod()); + + Map object = getParams(params, data); + + List selectOnlineMbList = onlineMbService.selectOnlineMbList(selectOnlineMb); + if (selectOnlineMbList.size() == 0) { + return AjaxResult.error("没有资源" + selectOnlineMb.getPath()); + } else if (selectOnlineMbList.size() > 1) { + return AjaxResult.error(500, "系统错误,在线接口重复"); + } else { + OnlineMb onlineMb = selectOnlineMbList.get(0); + if (!checkPermission(onlineMb.getPermissionType(), onlineMb.getPermissionValue())) { + return AjaxResult.error(403, "没有权限,请联系管理员授权"); + } + if (onlineMb.getDeptId() != null && onlineMb.getDeptId().equals("1")) { + object.put("deptId", SecurityUtils.getDeptId()); + } + if (onlineMb.getUserId() != null && onlineMb.getUserId().equals("1")) { + object.put("userId", SecurityUtils.getUserId()); + } + return processingMapper(onlineMb.getSqlText(), onlineMb.getActuator(), object); + } + } + +} diff --git a/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/controller/OnlineDbController.java b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/controller/OnlineDbController.java new file mode 100644 index 0000000..20a0ce4 --- /dev/null +++ b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/controller/OnlineDbController.java @@ -0,0 +1,39 @@ +package com.ruoyi.online.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.online.mapper.OnlineDbMapper; + + +/** + * mysql数据库Controller接口 + * + * @author Dftre + * @date 2024-01-26 + */ +@RestController +@RequestMapping("/online/db") +@Anonymous +public class OnlineDbController extends BaseController { + + @Autowired + private OnlineDbMapper onlineDbMapper; + + @GetMapping("/table/list") + public TableDataInfo selectDbTableList(BaseEntity baseEntity){ + startPage(); + return getDataTable(onlineDbMapper.selectDbTableList(baseEntity)); + } + + @GetMapping("/column/list") + public TableDataInfo selectDbColumnsListByTableName(String tableName){ + return getDataTable(onlineDbMapper.selectDbColumnsListByTableName(tableName)); + } +} diff --git a/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/controller/OnlineMbController.java b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/controller/OnlineMbController.java new file mode 100644 index 0000000..f8902d8 --- /dev/null +++ b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/controller/OnlineMbController.java @@ -0,0 +1,116 @@ +package com.ruoyi.online.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.online.domain.OnlineMb; +import com.ruoyi.online.service.IOnlineMbService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * mybatis在线接口Controller + * + * @author Dftre + * @date 2024-01-26 + */ +@RestController +@RequestMapping("/online/mb") +@Tag(name = "【mybatis在线接口】管理") +public class OnlineMbController extends BaseController +{ + @Autowired + private IOnlineMbService onlineMbService; + + /** + * 查询mybatis在线接口列表 + */ + @Operation(summary = "查询mybatis在线接口列表") + @PreAuthorize("@ss.hasPermi('online:mb:list')") + @GetMapping("/list") + public TableDataInfo list(OnlineMb onlineMb) + { + startPage(); + List list = onlineMbService.selectOnlineMbList(onlineMb); + return getDataTable(list); + } + + /** + * 导出mybatis在线接口列表 + */ + @Operation(summary = "导出mybatis在线接口列表") + @PreAuthorize("@ss.hasPermi('online:mb:export')") + @Log(title = "mybatis在线接口", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, OnlineMb onlineMb) + { + List list = onlineMbService.selectOnlineMbList(onlineMb); + ExcelUtil util = new ExcelUtil(OnlineMb.class); + util.exportExcel(response, list, "mybatis在线接口数据"); + } + + /** + * 获取mybatis在线接口详细信息 + */ + @Operation(summary = "获取mybatis在线接口详细信息") + @PreAuthorize("@ss.hasPermi('online:mb:query')") + @GetMapping(value = "/{mbId}") + public AjaxResult getInfo(@PathVariable("mbId") Long mbId) + { + return success(onlineMbService.selectOnlineMbByMbId(mbId)); + } + + /** + * 新增mybatis在线接口 + */ + @Operation(summary = "新增mybatis在线接口") + @PreAuthorize("@ss.hasPermi('online:mb:add')") + @Log(title = "mybatis在线接口", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody OnlineMb onlineMb) + { + return toAjax(onlineMbService.insertOnlineMb(onlineMb)); + } + + /** + * 修改mybatis在线接口 + */ + @Operation(summary = "修改mybatis在线接口") + @PreAuthorize("@ss.hasPermi('online:mb:edit')") + @Log(title = "mybatis在线接口", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody OnlineMb onlineMb) + { + return toAjax(onlineMbService.updateOnlineMb(onlineMb)); + } + + /** + * 删除mybatis在线接口 + */ + @Operation(summary = "删除mybatis在线接口") + @PreAuthorize("@ss.hasPermi('online:mb:remove')") + @Log(title = "mybatis在线接口", businessType = BusinessType.DELETE) + @DeleteMapping("/{mbIds}") + public AjaxResult remove(@PathVariable( name = "mbIds" ) Long[] mbIds) + { + return toAjax(onlineMbService.deleteOnlineMbByMbIds(mbIds)); + } +} diff --git a/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/domain/OnlineMb.java b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/domain/OnlineMb.java new file mode 100644 index 0000000..8f2d075 --- /dev/null +++ b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/domain/OnlineMb.java @@ -0,0 +1,221 @@ +package com.ruoyi.online.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * mybatis在线接口对象 online_mb + * + * @author Dftre + * @date 2024-01-26 + */ +@Schema(description = "mybatis在线接口对象") +public class OnlineMb extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 主键 */ + @Schema(title = "主键") + private Long mbId; + + /** 标签名 */ + @Schema(title = "标签名") + @Excel(name = "标签名") + private String tag; + + /** 标签id */ + @Schema(title = "标签id") + @Excel(name = "标签id") + private String tagId; + + /** 参数类型 */ + @Schema(title = "参数类型") + @Excel(name = "参数类型") + private String parameterType; + + /** 结果类型 */ + @Schema(title = "结果类型") + @Excel(name = "结果类型") + private String resultMap; + + /** sql语句 */ + @Schema(title = "sql语句") + @Excel(name = "sql语句") + private String sqlText; + + /** 请求路径 */ + @Schema(title = "请求路径") + @Excel(name = "请求路径") + private String path; + + /** 请求方式 */ + @Schema(title = "请求方式") + @Excel(name = "请求方式") + private String method; + + /** 响应类型 */ + @Schema(title = "响应类型") + @Excel(name = "响应类型") + private String resultType; + + /** 执行器 */ + @Schema(title = "执行器") + @Excel(name = "执行器") + private String actuator; + + /** 是否需要userId */ + @Schema(title = "是否需要userId") + @Excel(name = "是否需要userId") + private String userId; + + /** 是否需要deptId */ + @Schema(title = "是否需要deptId") + @Excel(name = "是否需要deptId") + private String deptId; + + /** 许可类型 */ + @Schema(title = "许可类型") + @Excel(name = "许可类型") + private String permissionType; + + /** 许可值 */ + @Schema(title = "许可值") + @Excel(name = "许可值") + private String permissionValue; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getPermissionType() { + return permissionType; + } + + public void setPermissionType(String permissionType) { + this.permissionType = permissionType; + } + + public String getPermissionValue() { + return permissionValue; + } + + public void setPermissionValue(String permissionValue) { + this.permissionValue = permissionValue; + } + + public void setMbId(Long mbId) { + this.mbId = mbId; + } + + public Long getMbId() { + return mbId; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public String getTag() { + return tag; + } + + public void setTagId(String tagId) { + this.tagId = tagId; + } + + public String getTagId() { + return tagId; + } + + public void setParameterType(String parameterType) { + this.parameterType = parameterType; + } + + public String getParameterType() { + return parameterType; + } + + public void setResultMap(String resultMap) { + this.resultMap = resultMap; + } + + public String getResultMap() { + return resultMap; + } + + public void setSqlText(String sqlText) { + this.sqlText = sqlText; + } + + public String getSqlText() { + return sqlText; + } + + public void setPath(String path) { + this.path = path; + } + + public String getPath() { + return path; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getMethod() { + return method; + } + + public void setResultType(String resultType) { + this.resultType = resultType; + } + + public String getResultType() { + return resultType; + } + + public void setActuator(String actuator) { + this.actuator = actuator; + } + + public String getActuator() { + return actuator; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("mbId", getMbId()) + .append("tag", getTag()) + .append("tagId", getTagId()) + .append("parameterType", getParameterType()) + .append("resultMap", getResultMap()) + .append("sqlText", getSqlText()) + .append("path", getPath()) + .append("method", getMethod()) + .append("resultType", getResultType()) + .append("actuator", getActuator()) + .append("userId",getUserId()) + .append("deptId",getDeptId()) + .append("permissionType",getPermissionType()) + .append("permissionValue",getPermissionValue()) + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/mapper/OnlineDbMapper.java b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/mapper/OnlineDbMapper.java new file mode 100644 index 0000000..2a6ab97 --- /dev/null +++ b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/mapper/OnlineDbMapper.java @@ -0,0 +1,17 @@ +package com.ruoyi.online.mapper; + +import java.util.List; +import java.util.Map; + +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * mysql数据库Mapper接口 + * + * @author Dftre + * @date 2024-01-26 + */ +public interface OnlineDbMapper { + public List> selectDbTableList(BaseEntity baseEntity); + public List> selectDbColumnsListByTableName(String tableName); +} diff --git a/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/mapper/OnlineMbMapper.java b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/mapper/OnlineMbMapper.java new file mode 100644 index 0000000..0d1cc27 --- /dev/null +++ b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/mapper/OnlineMbMapper.java @@ -0,0 +1,62 @@ +package com.ruoyi.online.mapper; + +import java.util.List; + +import com.ruoyi.online.domain.OnlineMb; + +/** + * mybatis在线接口Mapper接口 + * + * @author Dftre + * @date 2024-01-26 + */ +public interface OnlineMbMapper +{ + /** + * 查询mybatis在线接口 + * + * @param mbId mybatis在线接口主键 + * @return mybatis在线接口 + */ + public OnlineMb selectOnlineMbByMbId(Long mbId); + + /** + * 查询mybatis在线接口列表 + * + * @param onlineMb mybatis在线接口 + * @return mybatis在线接口集合 + */ + public List selectOnlineMbList(OnlineMb onlineMb); + + /** + * 新增mybatis在线接口 + * + * @param onlineMb mybatis在线接口 + * @return 结果 + */ + public int insertOnlineMb(OnlineMb onlineMb); + + /** + * 修改mybatis在线接口 + * + * @param onlineMb mybatis在线接口 + * @return 结果 + */ + public int updateOnlineMb(OnlineMb onlineMb); + + /** + * 删除mybatis在线接口 + * + * @param mbId mybatis在线接口主键 + * @return 结果 + */ + public int deleteOnlineMbByMbId(Long mbId); + + /** + * 批量删除mybatis在线接口 + * + * @param mbIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteOnlineMbByMbIds(Long[] mbIds); +} diff --git a/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/service/IOnlineMbService.java b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/service/IOnlineMbService.java new file mode 100644 index 0000000..51bd3f6 --- /dev/null +++ b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/service/IOnlineMbService.java @@ -0,0 +1,62 @@ +package com.ruoyi.online.service; + +import java.util.List; + +import com.ruoyi.online.domain.OnlineMb; + +/** + * mybatis在线接口Service接口 + * + * @author Dftre + * @date 2024-01-26 + */ +public interface IOnlineMbService +{ + /** + * 查询mybatis在线接口 + * + * @param mbId mybatis在线接口主键 + * @return mybatis在线接口 + */ + public OnlineMb selectOnlineMbByMbId(Long mbId); + + /** + * 查询mybatis在线接口列表 + * + * @param onlineMb mybatis在线接口 + * @return mybatis在线接口集合 + */ + public List selectOnlineMbList(OnlineMb onlineMb); + + /** + * 新增mybatis在线接口 + * + * @param onlineMb mybatis在线接口 + * @return 结果 + */ + public int insertOnlineMb(OnlineMb onlineMb); + + /** + * 修改mybatis在线接口 + * + * @param onlineMb mybatis在线接口 + * @return 结果 + */ + public int updateOnlineMb(OnlineMb onlineMb); + + /** + * 批量删除mybatis在线接口 + * + * @param mbIds 需要删除的mybatis在线接口主键集合 + * @return 结果 + */ + public int deleteOnlineMbByMbIds(Long[] mbIds); + + /** + * 删除mybatis在线接口信息 + * + * @param mbId mybatis在线接口主键 + * @return 结果 + */ + public int deleteOnlineMbByMbId(Long mbId); +} diff --git a/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/service/impl/OnlineMbServiceImpl.java b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/service/impl/OnlineMbServiceImpl.java new file mode 100644 index 0000000..adda07e --- /dev/null +++ b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/service/impl/OnlineMbServiceImpl.java @@ -0,0 +1,95 @@ +package com.ruoyi.online.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.online.domain.OnlineMb; +import com.ruoyi.online.mapper.OnlineMbMapper; +import com.ruoyi.online.service.IOnlineMbService; + +/** + * mybatis在线接口Service业务层处理 + * + * @author Dftre + * @date 2024-01-26 + */ +@Service +public class OnlineMbServiceImpl implements IOnlineMbService +{ + @Autowired + private OnlineMbMapper onlineMbMapper; + + /** + * 查询mybatis在线接口 + * + * @param mbId mybatis在线接口主键 + * @return mybatis在线接口 + */ + @Override + public OnlineMb selectOnlineMbByMbId(Long mbId) + { + return onlineMbMapper.selectOnlineMbByMbId(mbId); + } + + /** + * 查询mybatis在线接口列表 + * + * @param onlineMb mybatis在线接口 + * @return mybatis在线接口 + */ + @Override + public List selectOnlineMbList(OnlineMb onlineMb) + { + return onlineMbMapper.selectOnlineMbList(onlineMb); + } + + /** + * 新增mybatis在线接口 + * + * @param onlineMb mybatis在线接口 + * @return 结果 + */ + @Override + public int insertOnlineMb(OnlineMb onlineMb) + { + return onlineMbMapper.insertOnlineMb(onlineMb); + } + + /** + * 修改mybatis在线接口 + * + * @param onlineMb mybatis在线接口 + * @return 结果 + */ + @Override + public int updateOnlineMb(OnlineMb onlineMb) + { + return onlineMbMapper.updateOnlineMb(onlineMb); + } + + /** + * 批量删除mybatis在线接口 + * + * @param mbIds 需要删除的mybatis在线接口主键 + * @return 结果 + */ + @Override + public int deleteOnlineMbByMbIds(Long[] mbIds) + { + return onlineMbMapper.deleteOnlineMbByMbIds(mbIds); + } + + /** + * 删除mybatis在线接口信息 + * + * @param mbId mybatis在线接口主键 + * @return 结果 + */ + @Override + public int deleteOnlineMbByMbId(Long mbId) + { + return onlineMbMapper.deleteOnlineMbByMbId(mbId); + } +} diff --git a/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/utils/SqlMapper.java b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/utils/SqlMapper.java new file mode 100644 index 0000000..b1bdc6a --- /dev/null +++ b/ruoyi-models/ruoyi-online/src/main/java/com/ruoyi/online/utils/SqlMapper.java @@ -0,0 +1,411 @@ +package com.ruoyi.online.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.builder.StaticSqlSource; +import org.apache.ibatis.exceptions.TooManyResultsException; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.ResultMapping; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.scripting.LanguageDriver; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSession; + +/** + * MyBatis执行sql工具,在写SQL的时候建议使用参数形式的可以是${}或#{} + * + * 不建议将参数直接拼到字符串中,当大量这么使用的时候由于缓存MappedStatement而占用更多的内存 + * + * @author liuzh + * @since 2015-03-10 + */ +public class SqlMapper { + private final MSUtils msUtils; + private final SqlSession sqlSession; + + /** + * 构造方法,默认缓存MappedStatement + * + * @param sqlSession + */ + public SqlMapper(SqlSession sqlSession) { + this.sqlSession = sqlSession; + this.msUtils = new MSUtils(sqlSession.getConfiguration()); + } + + /** + * 获取List中最多只有一个的数据 + * + * @param list List结果 + * @param 泛型类型 + * @return + */ + private T getOne(List list) { + if (list.size() == 1) { + return list.get(0); + } else if (list.size() > 1) { + throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); + } else { + return null; + } + } + + /** + * 查询返回一个结果,多个结果时抛出异常 + * + * @param sql 执行的sql + * @return + */ + public Map selectOne(String sql) { + List> list = selectList(sql); + return getOne(list); + } + + /** + * 查询返回一个结果,多个结果时抛出异常 + * + * @param sql 执行的sql + * @param value 参数 + * @return + */ + public Map selectOne(String sql, Object value) { + List> list = selectList(sql, value); + return getOne(list); + } + + /** + * 查询返回一个结果,多个结果时抛出异常 + * + * @param sql 执行的sql + * @param resultType 返回的结果类型 + * @param 泛型类型 + * @return + */ + public T selectOne(String sql, Class resultType) { + List list = selectList(sql, resultType); + return getOne(list); + } + + /** + * 查询返回一个结果,多个结果时抛出异常 + * + * @param sql 执行的sql + * @param value 参数 + * @param resultType 返回的结果类型 + * @param 泛型类型 + * @return + */ + public T selectOne(String sql, Object value, Class resultType) { + List list = selectList(sql, value, resultType); + return getOne(list); + } + + /** + * 查询返回List> + * + * @param sql 执行的sql + * @return + */ + public List> selectList(String sql) { + String msId = msUtils.select(sql); + return sqlSession.selectList(msId); + } + + /** + * 查询返回List> + * + * @param sql 执行的sql + * @param value 参数 + * @return + */ + public List> selectList(String sql, Object value) { + Class parameterType = value != null ? value.getClass() : null; + String msId = msUtils.selectDynamic(sql, parameterType); + return sqlSession.selectList(msId, value); + } + + /** + * 查询返回指定的结果类型 + * + * @param sql 执行的sql + * @param resultType 返回的结果类型 + * @param 泛型类型 + * @return + */ + public List selectList(String sql, Class resultType) { + String msId; + if (resultType == null) { + msId = msUtils.select(sql); + } else { + msId = msUtils.select(sql, resultType); + } + return sqlSession.selectList(msId); + } + + /** + * 查询返回指定的结果类型 + * + * @param sql 执行的sql + * @param value 参数 + * @param resultType 返回的结果类型 + * @param 泛型类型 + * @return + */ + public List selectList(String sql, Object value, Class resultType) { + String msId; + Class parameterType = value != null ? value.getClass() : null; + if (resultType == null) { + msId = msUtils.selectDynamic(sql, parameterType); + } else { + msId = msUtils.selectDynamic(sql, parameterType, resultType); + } + return sqlSession.selectList(msId, value); + } + + /** + * 插入数据 + * + * @param sql 执行的sql + * @return + */ + public int insert(String sql) { + String msId = msUtils.insert(sql); + return sqlSession.insert(msId); + } + + /** + * 插入数据 + * + * @param sql 执行的sql + * @param value 参数 + * @return + */ + public int insert(String sql, Object value) { + Class parameterType = value != null ? value.getClass() : null; + String msId = msUtils.insertDynamic(sql, parameterType); + return sqlSession.insert(msId, value); + } + + /** + * 更新数据 + * + * @param sql 执行的sql + * @return + */ + public int update(String sql) { + String msId = msUtils.update(sql); + return sqlSession.update(msId); + } + + /** + * 更新数据 + * + * @param sql 执行的sql + * @param value 参数 + * @return + */ + public int update(String sql, Object value) { + Class parameterType = value != null ? value.getClass() : null; + String msId = msUtils.updateDynamic(sql, parameterType); + return sqlSession.update(msId, value); + } + + /** + * 删除数据 + * + * @param sql 执行的sql + * @return + */ + public int delete(String sql) { + String msId = msUtils.delete(sql); + return sqlSession.delete(msId); + } + + /** + * 删除数据 + * + * @param sql 执行的sql + * @param value 参数 + * @return + */ + public int delete(String sql, Object value) { + Class parameterType = value != null ? value.getClass() : null; + String msId = msUtils.deleteDynamic(sql, parameterType); + return sqlSession.delete(msId, value); + } + + private class MSUtils { + private Configuration configuration; + private LanguageDriver languageDriver; + + private MSUtils(Configuration configuration) { + this.configuration = configuration; + languageDriver = configuration.getDefaultScriptingLanguageInstance(); + } + + /** + * 创建MSID + * + * @param sql 执行的sql + * @param sql 执行的sqlCommandType + * @return + */ + private String newMsId(String sql, SqlCommandType sqlCommandType) { + StringBuilder msIdBuilder = new StringBuilder(sqlCommandType.toString()); + msIdBuilder.append(".").append(sql.hashCode()); + return msIdBuilder.toString(); + } + + /** + * 是否已经存在该ID + * + * @param msId + * @return + */ + private boolean hasMappedStatement(String msId) { + return configuration.hasStatement(msId, false); + } + + /** + * 创建一个查询的MS + * + * @param msId + * @param sqlSource 执行的sqlSource + * @param resultType 返回的结果类型 + */ + private void newSelectMappedStatement(String msId, SqlSource sqlSource, final Class resultType) { + MappedStatement ms = new MappedStatement.Builder(configuration, msId, sqlSource, SqlCommandType.SELECT) + .resultMaps(new ArrayList() { + { + add(new ResultMap.Builder(configuration, "defaultResultMap", resultType, new ArrayList(0)).build()); + } + }) + .build(); + //缓存 + configuration.addMappedStatement(ms); + } + + /** + * 创建一个简单的MS + * + * @param msId + * @param sqlSource 执行的sqlSource + * @param sqlCommandType 执行的sqlCommandType + */ + private void newUpdateMappedStatement(String msId, SqlSource sqlSource, SqlCommandType sqlCommandType) { + MappedStatement ms = new MappedStatement.Builder(configuration, msId, sqlSource, sqlCommandType) + .resultMaps(new ArrayList() { + { + add(new ResultMap.Builder(configuration, "defaultResultMap", int.class, new ArrayList(0)).build()); + } + }) + .build(); + //缓存 + configuration.addMappedStatement(ms); + } + + private String select(String sql) { + String msId = newMsId(sql, SqlCommandType.SELECT); + if (hasMappedStatement(msId)) { + return msId; + } + StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); + newSelectMappedStatement(msId, sqlSource, Map.class); + return msId; + } + + private String selectDynamic(String sql, Class parameterType) { + String msId = newMsId(sql + parameterType, SqlCommandType.SELECT); + if (hasMappedStatement(msId)) { + return msId; + } + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); + newSelectMappedStatement(msId, sqlSource, Map.class); + return msId; + } + + private String select(String sql, Class resultType) { + String msId = newMsId(resultType + sql, SqlCommandType.SELECT); + if (hasMappedStatement(msId)) { + return msId; + } + StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); + newSelectMappedStatement(msId, sqlSource, resultType); + return msId; + } + + private String selectDynamic(String sql, Class parameterType, Class resultType) { + String msId = newMsId(resultType + sql + parameterType, SqlCommandType.SELECT); + if (hasMappedStatement(msId)) { + return msId; + } + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); + newSelectMappedStatement(msId, sqlSource, resultType); + return msId; + } + + private String insert(String sql) { + String msId = newMsId(sql, SqlCommandType.INSERT); + if (hasMappedStatement(msId)) { + return msId; + } + StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); + newUpdateMappedStatement(msId, sqlSource, SqlCommandType.INSERT); + return msId; + } + + private String insertDynamic(String sql, Class parameterType) { + String msId = newMsId(sql + parameterType, SqlCommandType.INSERT); + if (hasMappedStatement(msId)) { + return msId; + } + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); + newUpdateMappedStatement(msId, sqlSource, SqlCommandType.INSERT); + return msId; + } + + private String update(String sql) { + String msId = newMsId(sql, SqlCommandType.UPDATE); + if (hasMappedStatement(msId)) { + return msId; + } + StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); + newUpdateMappedStatement(msId, sqlSource, SqlCommandType.UPDATE); + return msId; + } + + private String updateDynamic(String sql, Class parameterType) { + String msId = newMsId(sql + parameterType, SqlCommandType.UPDATE); + if (hasMappedStatement(msId)) { + return msId; + } + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); + newUpdateMappedStatement(msId, sqlSource, SqlCommandType.UPDATE); + return msId; + } + + private String delete(String sql) { + String msId = newMsId(sql, SqlCommandType.DELETE); + if (hasMappedStatement(msId)) { + return msId; + } + StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); + newUpdateMappedStatement(msId, sqlSource, SqlCommandType.DELETE); + return msId; + } + + private String deleteDynamic(String sql, Class parameterType) { + String msId = newMsId(sql + parameterType, SqlCommandType.DELETE); + if (hasMappedStatement(msId)) { + return msId; + } + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); + newUpdateMappedStatement(msId, sqlSource, SqlCommandType.DELETE); + return msId; + } + } +} \ No newline at end of file diff --git a/ruoyi-models/ruoyi-online/src/main/resources/mapper/online/OnlineDbMapper.xml b/ruoyi-models/ruoyi-online/src/main/resources/mapper/online/OnlineDbMapper.xml new file mode 100644 index 0000000..e0d6f7e --- /dev/null +++ b/ruoyi-models/ruoyi-online/src/main/resources/mapper/online/OnlineDbMapper.xml @@ -0,0 +1,34 @@ + + + + + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-online/src/main/resources/mapper/online/OnlineMbMapper.xml b/ruoyi-models/ruoyi-online/src/main/resources/mapper/online/OnlineMbMapper.xml new file mode 100644 index 0000000..547cfe8 --- /dev/null +++ b/ruoyi-models/ruoyi-online/src/main/resources/mapper/online/OnlineMbMapper.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + select mb_id, tag, tag_id, parameter_type, result_map, sql_text, online_mb.path, method, result_type, actuator,user_id,dept_id,permission_type,permission_value from online_mb + + + + + + + + insert into online_mb + + mb_id, + tag, + tag_id, + parameter_type, + result_map, + sql_text, + path, + method, + result_type, + actuator, + user_id, + dept_id, + permission_type, + permission_value, + + + #{mbId}, + #{tag}, + #{tagId}, + #{parameterType}, + #{resultMap}, + #{sqlText}, + #{path}, + #{method}, + #{resultType}, + #{actuator}, + #{userId}, + #{deptId}, + #{permissionType}, + #{permissionValue}, + + + + + update online_mb + + tag = #{tag}, + tag_id = #{tagId}, + parameter_type = #{parameterType}, + result_map = #{result_map}, + sql_text = #{sqlText}, + path = #{path}, + method = #{method}, + result_type = #{resultType}, + actuator = #{actuator}, + user_id = #{userId}, + dept_id = #{deptId}, + permission_type = #{permissionType}, + permission_value = #{permissionValue}, + + where online_mb.mb_id = #{mbId} and online_mb.del_flag != '1' + + + + update online_mb set del_flag = '1' where mb_id = #{mbId} + + + + update online_mb set del_flag = '1' where mb_id in + + #{mbId} + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-quartz/pom.xml b/ruoyi-models/ruoyi-quartz/pom.xml new file mode 100644 index 0000000..6e796af --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/pom.xml @@ -0,0 +1,39 @@ + + + + ruoyi-models + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-quartz + + + quartz定时任务 + + + + + + + org.quartz-scheduler + quartz + + + com.mchange + c3p0 + + + + + + + com.ruoyi.geekxd + ruoyi-common + + + + + diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java new file mode 100644 index 0000000..a558170 --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java @@ -0,0 +1,57 @@ +//package com.ruoyi.quartz.config; +// +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.scheduling.quartz.SchedulerFactoryBean; +//import javax.sql.DataSource; +//import java.util.Properties; +// +///** +// * 定时任务配置(单机部署建议删除此类和qrtz数据库表,默认走内存会最高效) +// * +// * @author ruoyi +// */ +//@Configuration +//public class ScheduleConfig +//{ +// @Bean +// public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) +// { +// SchedulerFactoryBean factory = new SchedulerFactoryBean(); +// factory.setDataSource(dataSource); +// +// // quartz参数 +// Properties prop = new Properties(); +// prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler"); +// prop.put("org.quartz.scheduler.instanceId", "AUTO"); +// // 线程池配置 +// prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); +// prop.put("org.quartz.threadPool.threadCount", "20"); +// prop.put("org.quartz.threadPool.threadPriority", "5"); +// // JobStore配置 +// prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore"); +// // 集群配置 +// prop.put("org.quartz.jobStore.isClustered", "true"); +// prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); +// prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1"); +// prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true"); +// +// // sqlserver 启用 +// // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?"); +// prop.put("org.quartz.jobStore.misfireThreshold", "12000"); +// prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); +// factory.setQuartzProperties(prop); +// +// factory.setSchedulerName("RuoyiScheduler"); +// // 延时启动 +// factory.setStartupDelay(1); +// factory.setApplicationContextSchedulerContextKey("applicationContextKey"); +// // 可选,QuartzScheduler +// // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 +// factory.setOverwriteExistingJobs(true); +// // 设置自动启动,默认为true +// factory.setAutoStartup(true); +// +// return factory; +// } +//} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java new file mode 100644 index 0000000..f3933b9 --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java @@ -0,0 +1,188 @@ +package com.ruoyi.quartz.controller; + +import java.util.List; + +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.exception.job.TaskException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.quartz.domain.SysJob; +import com.ruoyi.quartz.service.ISysJobService; +import com.ruoyi.quartz.util.CronUtils; +import com.ruoyi.quartz.util.ScheduleUtils; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 调度任务信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/job") +public class SysJobController extends BaseController +{ + @Autowired + private ISysJobService jobService; + + /** + * 查询定时任务列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJob sysJob) + { + startPage(); + List list = jobService.selectJobList(sysJob); + return getDataTable(list); + } + + /** + * 导出定时任务列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:export')") + @Log(title = "定时任务", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysJob sysJob) + { + List list = jobService.selectJobList(sysJob); + ExcelUtil util = new ExcelUtil(SysJob.class); + util.exportExcel(response, list, "定时任务"); + } + + /** + * 获取定时任务详细信息 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobId}") + public AjaxResult getInfo(@PathVariable("jobId") Long jobId) + { + return success(jobService.selectJobById(jobId)); + } + + /** + * 新增定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:add')") + @Log(title = "定时任务", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + job.setCreateBy(getUsername()); + return toAjax(jobService.insertJob(job)); + } + + /** + * 修改定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:edit')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + job.setUpdateBy(getUsername()); + return toAjax(jobService.updateJob(job)); + } + + /** + * 定时任务状态修改 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException + { + SysJob newJob = jobService.selectJobById(job.getJobId()); + newJob.setStatus(job.getStatus()); + return toAjax(jobService.changeStatus(newJob)); + } + + /** + * 定时任务立即执行一次 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/run") + public AjaxResult run(@RequestBody SysJob job) throws SchedulerException + { + boolean result = jobService.run(job); + return result ? success() : error("任务不存在或已过期!"); + } + + /** + * 删除定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobIds}") + public AjaxResult remove(@PathVariable( name = "jobIds" ) Long[] jobIds) throws SchedulerException, TaskException + { + jobService.deleteJobByIds(jobIds); + return success(); + } +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java new file mode 100644 index 0000000..22c0d6a --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java @@ -0,0 +1,95 @@ +package com.ruoyi.quartz.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.quartz.domain.SysJobLog; +import com.ruoyi.quartz.service.ISysJobLogService; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 调度日志操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/jobLog") +public class SysJobLogController extends BaseController +{ + @Autowired + private ISysJobLogService jobLogService; + + /** + * 查询定时任务调度日志列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJobLog sysJobLog) + { + startPage(); + List list = jobLogService.selectJobLogList(sysJobLog); + return getDataTable(list); + } + + /** + * 导出定时任务调度日志列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:export')") + @Log(title = "任务调度日志", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysJobLog sysJobLog) + { + List list = jobLogService.selectJobLogList(sysJobLog); + ExcelUtil util = new ExcelUtil(SysJobLog.class); + util.exportExcel(response, list, "调度日志"); + } + + /** + * 根据调度编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobLogId}") + public AjaxResult getInfo(@PathVariable( name = "jobLogId" ) Long jobLogId) + { + return success(jobLogService.selectJobLogById(jobLogId)); + } + + + /** + * 删除定时任务调度日志 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务调度日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobLogIds}") + public AjaxResult remove(@PathVariable( name = "jobLogIds" ) Long[] jobLogIds) + { + return toAjax(jobLogService.deleteJobLogByIds(jobLogIds)); + } + + /** + * 清空定时任务调度日志 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "调度日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + jobLogService.cleanJobLog(); + return success(); + } +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java new file mode 100644 index 0000000..cea12dc --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java @@ -0,0 +1,171 @@ +package com.ruoyi.quartz.domain; + +import java.util.Date; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.constant.ScheduleConstants; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.quartz.util.CronUtils; + +/** + * 定时任务调度表 sys_job + * + * @author ruoyi + */ +public class SysJob extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 任务ID */ + @Excel(name = "任务序号", cellType = ColumnType.NUMERIC) + private Long jobId; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名 */ + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串 */ + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** cron执行表达式 */ + @Excel(name = "执行表达式 ") + private String cronExpression; + + /** cron计划策略 */ + @Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行") + private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT; + + /** 是否并发执行(0允许 1禁止) */ + @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止") + private String concurrent; + + /** 任务状态(0正常 1暂停) */ + @Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停") + private String status; + + public Long getJobId() + { + return jobId; + } + + public void setJobId(Long jobId) + { + this.jobId = jobId; + } + + @NotBlank(message = "任务名称不能为空") + @Size(min = 0, max = 64, message = "任务名称不能超过64个字符") + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + @NotBlank(message = "调用目标字符串不能为空") + @Size(min = 0, max = 500, message = "调用目标字符串长度不能超过500个字符") + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + @NotBlank(message = "Cron执行表达式不能为空") + @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符") + public String getCronExpression() + { + return cronExpression; + } + + public void setCronExpression(String cronExpression) + { + this.cronExpression = cronExpression; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public Date getNextValidTime() + { + if (StringUtils.isNotEmpty(cronExpression)) + { + return CronUtils.getNextExecution(cronExpression); + } + return null; + } + + public String getMisfirePolicy() + { + return misfirePolicy; + } + + public void setMisfirePolicy(String misfirePolicy) + { + this.misfirePolicy = misfirePolicy; + } + + public String getConcurrent() + { + return concurrent; + } + + public void setConcurrent(String concurrent) + { + this.concurrent = concurrent; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobId", getJobId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("cronExpression", getCronExpression()) + .append("nextValidTime", getNextValidTime()) + .append("misfirePolicy", getMisfirePolicy()) + .append("concurrent", getConcurrent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java new file mode 100644 index 0000000..121c035 --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java @@ -0,0 +1,155 @@ +package com.ruoyi.quartz.domain; + +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 定时任务调度日志表 sys_job_log + * + * @author ruoyi + */ +public class SysJobLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @Excel(name = "日志序号") + private Long jobLogId; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名 */ + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串 */ + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** 日志信息 */ + @Excel(name = "日志信息") + private String jobMessage; + + /** 执行状态(0正常 1失败) */ + @Excel(name = "执行状态", readConverterExp = "0=正常,1=失败") + private String status; + + /** 异常信息 */ + @Excel(name = "异常信息") + private String exceptionInfo; + + /** 开始时间 */ + private Date startTime; + + /** 停止时间 */ + private Date stopTime; + + public Long getJobLogId() + { + return jobLogId; + } + + public void setJobLogId(Long jobLogId) + { + this.jobLogId = jobLogId; + } + + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + public String getJobMessage() + { + return jobMessage; + } + + public void setJobMessage(String jobMessage) + { + this.jobMessage = jobMessage; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getExceptionInfo() + { + return exceptionInfo; + } + + public void setExceptionInfo(String exceptionInfo) + { + this.exceptionInfo = exceptionInfo; + } + + public Date getStartTime() + { + return startTime; + } + + public void setStartTime(Date startTime) + { + this.startTime = startTime; + } + + public Date getStopTime() + { + return stopTime; + } + + public void setStopTime(Date stopTime) + { + this.stopTime = stopTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobLogId", getJobLogId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("jobMessage", getJobMessage()) + .append("status", getStatus()) + .append("exceptionInfo", getExceptionInfo()) + .append("startTime", getStartTime()) + .append("stopTime", getStopTime()) + .toString(); + } +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java new file mode 100644 index 0000000..727d916 --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java @@ -0,0 +1,64 @@ +package com.ruoyi.quartz.mapper; + +import java.util.List; +import com.ruoyi.quartz.domain.SysJobLog; + +/** + * 调度任务日志信息 数据层 + * + * @author ruoyi + */ +public interface SysJobLogMapper +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 查询所有调度任务日志 + * + * @return 调度任务日志列表 + */ + public List selectJobLogAll(); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + * @return 结果 + */ + public int insertJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java new file mode 100644 index 0000000..20f45db --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java @@ -0,0 +1,67 @@ +package com.ruoyi.quartz.mapper; + +import java.util.List; +import com.ruoyi.quartz.domain.SysJob; + +/** + * 调度任务信息 数据层 + * + * @author ruoyi + */ +public interface SysJobMapper +{ + /** + * 查询调度任务日志集合 + * + * @param job 调度信息 + * @return 操作日志集合 + */ + public List selectJobList(SysJob job); + + /** + * 查询所有调度任务 + * + * @return 调度任务列表 + */ + public List selectJobAll(); + + /** + * 通过调度ID查询调度任务信息 + * + * @param jobId 调度ID + * @return 角色对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 通过调度ID删除调度任务信息 + * + * @param jobId 调度ID + * @return 结果 + */ + public int deleteJobById(Long jobId); + + /** + * 批量删除调度任务信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteJobByIds(Long[] ids); + + /** + * 修改调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int updateJob(SysJob job); + + /** + * 新增调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int insertJob(SysJob job); +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java new file mode 100644 index 0000000..8546792 --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java @@ -0,0 +1,56 @@ +package com.ruoyi.quartz.service; + +import java.util.List; +import com.ruoyi.quartz.domain.SysJobLog; + +/** + * 定时任务调度日志信息信息 服务层 + * + * @author ruoyi + */ +public interface ISysJobLogService +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + public void addJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的日志ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java new file mode 100644 index 0000000..437ade8 --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java @@ -0,0 +1,102 @@ +package com.ruoyi.quartz.service; + +import java.util.List; +import org.quartz.SchedulerException; +import com.ruoyi.common.exception.job.TaskException; +import com.ruoyi.quartz.domain.SysJob; + +/** + * 定时任务调度信息信息 服务层 + * + * @author ruoyi + */ +public interface ISysJobService +{ + /** + * 获取quartz调度器的计划任务 + * + * @param job 调度信息 + * @return 调度任务集合 + */ + public List selectJobList(SysJob job); + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 暂停任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int pauseJob(SysJob job) throws SchedulerException; + + /** + * 恢复任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int resumeJob(SysJob job) throws SchedulerException; + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + * @return 结果 + */ + public int deleteJob(SysJob job) throws SchedulerException; + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + public void deleteJobByIds(Long[] jobIds) throws SchedulerException; + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + * @return 结果 + */ + public int changeStatus(SysJob job) throws SchedulerException; + + /** + * 立即运行任务 + * + * @param job 调度信息 + * @return 结果 + */ + public boolean run(SysJob job) throws SchedulerException; + + /** + * 新增任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int insertJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 更新任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int updateJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + public boolean checkCronExpressionIsValid(String cronExpression); +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java new file mode 100644 index 0000000..812eed7 --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java @@ -0,0 +1,87 @@ +package com.ruoyi.quartz.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.quartz.domain.SysJobLog; +import com.ruoyi.quartz.mapper.SysJobLogMapper; +import com.ruoyi.quartz.service.ISysJobLogService; + +/** + * 定时任务调度日志信息 服务层 + * + * @author ruoyi + */ +@Service +public class SysJobLogServiceImpl implements ISysJobLogService +{ + @Autowired + private SysJobLogMapper jobLogMapper; + + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + @Override + public List selectJobLogList(SysJobLog jobLog) + { + return jobLogMapper.selectJobLogList(jobLog); + } + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + @Override + public SysJobLog selectJobLogById(Long jobLogId) + { + return jobLogMapper.selectJobLogById(jobLogId); + } + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + @Override + public void addJobLog(SysJobLog jobLog) + { + jobLogMapper.insertJobLog(jobLog); + } + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteJobLogByIds(Long[] logIds) + { + return jobLogMapper.deleteJobLogByIds(logIds); + } + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + */ + @Override + public int deleteJobLogById(Long jobId) + { + return jobLogMapper.deleteJobLogById(jobId); + } + + /** + * 清空任务日志 + */ + @Override + public void cleanJobLog() + { + jobLogMapper.cleanJobLog(); + } +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java new file mode 100644 index 0000000..78ebef8 --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java @@ -0,0 +1,261 @@ +package com.ruoyi.quartz.service.impl; + +import java.util.List; +import jakarta.annotation.PostConstruct; +import org.quartz.JobDataMap; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.ruoyi.common.constant.ScheduleConstants; +import com.ruoyi.common.exception.job.TaskException; +import com.ruoyi.quartz.domain.SysJob; +import com.ruoyi.quartz.mapper.SysJobMapper; +import com.ruoyi.quartz.service.ISysJobService; +import com.ruoyi.quartz.util.CronUtils; +import com.ruoyi.quartz.util.ScheduleUtils; + +/** + * 定时任务调度信息 服务层 + * + * @author ruoyi + */ +@Service +public class SysJobServiceImpl implements ISysJobService +{ + @Autowired + private Scheduler scheduler; + + @Autowired + private SysJobMapper jobMapper; + + /** + * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据) + */ + @PostConstruct + public void init() throws SchedulerException, TaskException + { + scheduler.clear(); + List jobList = jobMapper.selectJobAll(); + for (SysJob job : jobList) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + } + + /** + * 获取quartz调度器的计划任务列表 + * + * @param job 调度信息 + * @return + */ + @Override + public List selectJobList(SysJob job) + { + return jobMapper.selectJobList(job); + } + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + @Override + public SysJob selectJobById(Long jobId) + { + return jobMapper.selectJobById(jobId); + } + + /** + * 暂停任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int pauseJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 恢复任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int resumeJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.NORMAL.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + int rows = jobMapper.deleteJobById(jobId); + if (rows > 0) + { + scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteJobByIds(Long[] jobIds) throws SchedulerException + { + for (Long jobId : jobIds) + { + SysJob job = jobMapper.selectJobById(jobId); + deleteJob(job); + } + } + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int changeStatus(SysJob job) throws SchedulerException + { + int rows = 0; + String status = job.getStatus(); + if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) + { + rows = resumeJob(job); + } + else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) + { + rows = pauseJob(job); + } + return rows; + } + + /** + * 立即运行任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean run(SysJob job) throws SchedulerException + { + boolean result = false; + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + SysJob properties = selectJobById(job.getJobId()); + // 参数 + JobDataMap dataMap = new JobDataMap(); + dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties); + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + if (scheduler.checkExists(jobKey)) + { + result = true; + scheduler.triggerJob(jobKey, dataMap); + } + return result; + } + + /** + * 新增任务 + * + * @param job 调度信息 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int insertJob(SysJob job) throws SchedulerException, TaskException + { + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.insertJob(job); + if (rows > 0) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + return rows; + } + + /** + * 更新任务的时间表达式 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateJob(SysJob job) throws SchedulerException, TaskException + { + SysJob properties = selectJobById(job.getJobId()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + updateSchedulerJob(job, properties.getJobGroup()); + } + return rows; + } + + /** + * 更新任务 + * + * @param job 任务对象 + * @param jobGroup 任务组名 + */ + public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException + { + Long jobId = job.getJobId(); + // 判断是否存在 + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + if (scheduler.checkExists(jobKey)) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(jobKey); + } + ScheduleUtils.createScheduleJob(scheduler, job); + } + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + @Override + public boolean checkCronExpressionIsValid(String cronExpression) + { + return CronUtils.isValid(cronExpression); + } +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java new file mode 100644 index 0000000..853243b --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java @@ -0,0 +1,28 @@ +package com.ruoyi.quartz.task; + +import org.springframework.stereotype.Component; +import com.ruoyi.common.utils.StringUtils; + +/** + * 定时任务调度测试 + * + * @author ruoyi + */ +@Component("ryTask") +public class RyTask +{ + public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) + { + System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i)); + } + + public void ryParams(String params) + { + System.out.println("执行有参方法:" + params); + } + + public void ryNoParams() + { + System.out.println("执行无参方法"); + } +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java new file mode 100644 index 0000000..731a5eb --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java @@ -0,0 +1,107 @@ +package com.ruoyi.quartz.util; + +import java.util.Date; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.ScheduleConstants; +import com.ruoyi.common.utils.ExceptionUtil; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.bean.BeanUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.quartz.domain.SysJob; +import com.ruoyi.quartz.domain.SysJobLog; +import com.ruoyi.quartz.service.ISysJobLogService; + +/** + * 抽象quartz调用 + * + * @author ruoyi + */ +public abstract class AbstractQuartzJob implements Job +{ + private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); + + /** + * 线程本地变量 + */ + private static ThreadLocal threadLocal = new ThreadLocal<>(); + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException + { + SysJob sysJob = new SysJob(); + BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES)); + try + { + before(context, sysJob); + if (sysJob != null) + { + doExecute(context, sysJob); + } + after(context, sysJob, null); + } + catch (Exception e) + { + log.error("任务执行异常 - :", e); + after(context, sysJob, e); + } + } + + /** + * 执行前 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + */ + protected void before(JobExecutionContext context, SysJob sysJob) + { + threadLocal.set(new Date()); + } + + /** + * 执行后 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + */ + protected void after(JobExecutionContext context, SysJob sysJob, Exception e) + { + Date startTime = threadLocal.get(); + threadLocal.remove(); + + final SysJobLog sysJobLog = new SysJobLog(); + sysJobLog.setJobName(sysJob.getJobName()); + sysJobLog.setJobGroup(sysJob.getJobGroup()); + sysJobLog.setInvokeTarget(sysJob.getInvokeTarget()); + sysJobLog.setStartTime(startTime); + sysJobLog.setStopTime(new Date()); + long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime(); + sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒"); + if (e != null) + { + sysJobLog.setStatus(Constants.FAIL); + String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000); + sysJobLog.setExceptionInfo(errorMsg); + } + else + { + sysJobLog.setStatus(Constants.SUCCESS); + } + + // 写入数据库当中 + SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); + } + + /** + * 执行方法,由子类重载 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + * @throws Exception 执行过程中的异常 + */ + protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception; +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java new file mode 100644 index 0000000..dd53839 --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java @@ -0,0 +1,63 @@ +package com.ruoyi.quartz.util; + +import java.text.ParseException; +import java.util.Date; +import org.quartz.CronExpression; + +/** + * cron表达式工具类 + * + * @author ruoyi + * + */ +public class CronUtils +{ + /** + * 返回一个布尔值代表一个给定的Cron表达式的有效性 + * + * @param cronExpression Cron表达式 + * @return boolean 表达式是否有效 + */ + public static boolean isValid(String cronExpression) + { + return CronExpression.isValidExpression(cronExpression); + } + + /** + * 返回一个字符串值,表示该消息无效Cron表达式给出有效性 + * + * @param cronExpression Cron表达式 + * @return String 无效时返回表达式错误描述,如果有效返回null + */ + public static String getInvalidMessage(String cronExpression) + { + try + { + new CronExpression(cronExpression); + return null; + } + catch (ParseException pe) + { + return pe.getMessage(); + } + } + + /** + * 返回下一个执行时间根据给定的Cron表达式 + * + * @param cronExpression Cron表达式 + * @return Date 下次Cron表达式执行时间 + */ + public static Date getNextExecution(String cronExpression) + { + try + { + CronExpression cron = new CronExpression(cronExpression); + return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis())); + } + catch (ParseException e) + { + throw new IllegalArgumentException(e.getMessage()); + } + } +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java new file mode 100644 index 0000000..e3dc62c --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java @@ -0,0 +1,182 @@ +package com.ruoyi.quartz.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.quartz.domain.SysJob; + +/** + * 任务执行工具 + * + * @author ruoyi + */ +public class JobInvokeUtil +{ + /** + * 执行方法 + * + * @param sysJob 系统任务 + */ + public static void invokeMethod(SysJob sysJob) throws Exception + { + String invokeTarget = sysJob.getInvokeTarget(); + String beanName = getBeanName(invokeTarget); + String methodName = getMethodName(invokeTarget); + List methodParams = getMethodParams(invokeTarget); + + if (!isValidClassName(beanName)) + { + Object bean = SpringUtils.getBean(beanName); + invokeMethod(bean, methodName, methodParams); + } + else + { + Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance(); + invokeMethod(bean, methodName, methodParams); + } + } + + /** + * 调用任务方法 + * + * @param bean 目标对象 + * @param methodName 方法名称 + * @param methodParams 方法参数 + */ + private static void invokeMethod(Object bean, String methodName, List methodParams) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException + { + if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) + { + Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams)); + method.invoke(bean, getMethodParamsValue(methodParams)); + } + else + { + Method method = bean.getClass().getMethod(methodName); + method.invoke(bean); + } + } + + /** + * 校验是否为为class包名 + * + * @param invokeTarget 名称 + * @return true是 false否 + */ + public static boolean isValidClassName(String invokeTarget) + { + return StringUtils.countMatches(invokeTarget, ".") > 1; + } + + /** + * 获取bean名称 + * + * @param invokeTarget 目标字符串 + * @return bean名称 + */ + public static String getBeanName(String invokeTarget) + { + String beanName = StringUtils.substringBefore(invokeTarget, "("); + return StringUtils.substringBeforeLast(beanName, "."); + } + + /** + * 获取bean方法 + * + * @param invokeTarget 目标字符串 + * @return method方法 + */ + public static String getMethodName(String invokeTarget) + { + String methodName = StringUtils.substringBefore(invokeTarget, "("); + return StringUtils.substringAfterLast(methodName, "."); + } + + /** + * 获取method方法参数相关列表 + * + * @param invokeTarget 目标字符串 + * @return method方法相关参数列表 + */ + public static List getMethodParams(String invokeTarget) + { + String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")"); + if (StringUtils.isEmpty(methodStr)) + { + return null; + } + String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)"); + List classs = new LinkedList<>(); + for (int i = 0; i < methodParams.length; i++) + { + String str = StringUtils.trimToEmpty(methodParams[i]); + // String字符串类型,以'或"开头 + if (StringUtils.startsWithAny(str, "'", "\"")) + { + classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class }); + } + // boolean布尔类型,等于true或者false + else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) + { + classs.add(new Object[] { Boolean.valueOf(str), Boolean.class }); + } + // long长整形,以L结尾 + else if (StringUtils.endsWith(str, "L")) + { + classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class }); + } + // double浮点类型,以D结尾 + else if (StringUtils.endsWith(str, "D")) + { + classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class }); + } + // 其他类型归类为整形 + else + { + classs.add(new Object[] { Integer.valueOf(str), Integer.class }); + } + } + return classs; + } + + /** + * 获取参数类型 + * + * @param methodParams 参数相关列表 + * @return 参数类型列表 + */ + public static Class[] getMethodParamsType(List methodParams) + { + Class[] classs = new Class[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Class) os[1]; + index++; + } + return classs; + } + + /** + * 获取参数值 + * + * @param methodParams 参数相关列表 + * @return 参数值列表 + */ + public static Object[] getMethodParamsValue(List methodParams) + { + Object[] classs = new Object[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Object) os[0]; + index++; + } + return classs; + } +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java new file mode 100644 index 0000000..5e13558 --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java @@ -0,0 +1,21 @@ +package com.ruoyi.quartz.util; + +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import com.ruoyi.quartz.domain.SysJob; + +/** + * 定时任务处理(禁止并发执行) + * + * @author ruoyi + * + */ +@DisallowConcurrentExecution +public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java new file mode 100644 index 0000000..e975326 --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java @@ -0,0 +1,19 @@ +package com.ruoyi.quartz.util; + +import org.quartz.JobExecutionContext; +import com.ruoyi.quartz.domain.SysJob; + +/** + * 定时任务处理(允许并发执行) + * + * @author ruoyi + * + */ +public class QuartzJobExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java new file mode 100644 index 0000000..a59f61c --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java @@ -0,0 +1,130 @@ +package com.ruoyi.quartz.util; + +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; + +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.ScheduleConstants; +import com.ruoyi.common.exception.job.TaskException; +import com.ruoyi.common.exception.job.TaskException.Code; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.quartz.domain.SysJob; + +/** + * 定时任务工具类 + * + * @author ruoyi + * + */ +public class ScheduleUtils { + /** + * 得到quartz任务类 + * + * @param sysJob 执行计划 + * @return 具体执行任务类 + */ + private static Class getQuartzJobClass(SysJob sysJob) { + boolean isConcurrent = "0".equals(sysJob.getConcurrent()); + return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class; + } + + /** + * 构建任务触发对象 + */ + public static TriggerKey getTriggerKey(Long jobId, String jobGroup) { + return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 构建任务键对象 + */ + public static JobKey getJobKey(Long jobId, String jobGroup) { + return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 创建定时任务 + */ + public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException { + Class jobClass = getQuartzJobClass(job); + // 构建job信息 + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build(); + + // 表达式调度构建器 + CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); + cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder); + + // 按新的cronExpression表达式构建一个新的trigger + CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup)) + .withSchedule(cronScheduleBuilder).build(); + + // 放入参数,运行时的方法可以获取 + jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job); + + // 判断是否存在 + if (scheduler.checkExists(getJobKey(jobId, jobGroup))) { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(getJobKey(jobId, jobGroup)); + } + + // 判断任务是否过期 + if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression()))) { + // 执行调度任务 + scheduler.scheduleJob(jobDetail, trigger); + } + + // 暂停任务 + if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + } + + /** + * 设置定时任务策略 + */ + public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb) + throws TaskException { + switch (job.getMisfirePolicy()) { + case ScheduleConstants.MISFIRE_DEFAULT: + return cb; + case ScheduleConstants.MISFIRE_IGNORE_MISFIRES: + return cb.withMisfireHandlingInstructionIgnoreMisfires(); + case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED: + return cb.withMisfireHandlingInstructionFireAndProceed(); + case ScheduleConstants.MISFIRE_DO_NOTHING: + return cb.withMisfireHandlingInstructionDoNothing(); + default: + throw new TaskException("The task misfire policy '" + job.getMisfirePolicy() + + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR); + } + } + + /** + * 检查包名是否为白名单配置 + * + * @param invokeTarget 目标字符串 + * @return 结果 + */ + public static boolean whiteList(String invokeTarget) { + String packageName = StringUtils.substringBefore(invokeTarget, "("); + int count = StringUtils.countMatches(packageName, "."); + if (count > 1) { + return StringUtils.startsWithAny(invokeTarget, Constants.JOB_WHITELIST_STR); + } + Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]); + String beanPackageName = obj.getClass().getPackage().getName(); + return StringUtils.startsWithAny(beanPackageName, Constants.JOB_WHITELIST_STR) + && !StringUtils.startsWithAny(beanPackageName, Constants.JOB_ERROR_STR); + } +} diff --git a/ruoyi-models/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml b/ruoyi-models/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml new file mode 100644 index 0000000..853d707 --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time + from sys_job_log + + + + + + + + + + delete from sys_job_log where job_log_id = #{jobLogId} + + + + delete from sys_job_log where job_log_id in + + #{jobLogId} + + + + + truncate table sys_job_log + + + + insert into sys_job_log( + job_log_id, + job_name, + job_group, + invoke_target, + job_message, + status, + exception_info, + create_time + )values( + #{jobLogId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{jobMessage}, + #{status}, + #{exceptionInfo}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + \ No newline at end of file diff --git a/ruoyi-models/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml b/ruoyi-models/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml new file mode 100644 index 0000000..39cbc9f --- /dev/null +++ b/ruoyi-models/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark + from sys_job + + + + + + + + + + delete from sys_job where job_id = #{jobId} + + + + delete from sys_job where job_id in + + #{jobId} + + + + + update sys_job + + job_name = #{jobName}, + job_group = #{jobGroup}, + invoke_target = #{invokeTarget}, + cron_expression = #{cronExpression}, + misfire_policy = #{misfirePolicy}, + concurrent = #{concurrent}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = + + CURRENT_TIMESTAMP + + + sysdate() + + + + where job_id = #{jobId} + + + + insert into sys_job( + job_id, + job_name, + job_group, + invoke_target, + cron_expression, + misfire_policy, + concurrent, + status, + remark, + create_by, + create_time + )values( + #{jobId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{cronExpression}, + #{misfirePolicy}, + #{concurrent}, + #{status}, + #{remark}, + #{createBy}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + \ No newline at end of file diff --git a/ruoyi-plugins/pom.xml b/ruoyi-plugins/pom.xml new file mode 100644 index 0000000..d135728 --- /dev/null +++ b/ruoyi-plugins/pom.xml @@ -0,0 +1,134 @@ + + + + ruoyi + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-plugins + + + 3.10.8 + 3.5.8 + 4.1.112.Final + 6.0.0 + + + + + + + + + + org.ehcache + ehcache + ${ehcache.version} + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + + + io.netty + netty-all + ${netty.version} + + + + + com.atomikos + transactions-spring-boot3-starter + ${transactions.version} + + + + + com.ruoyi.geekxd + ruoyi-cache-ehcache + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-cache-redis + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-mybatis-jpa + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-mybatis-plus + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-websocket + ${ruoyi.version} + + + + com.ruoyi.geekxd + ruoyi-mybatis-interceptor + ${ruoyi.version} + + + + com.ruoyi.geekxd + ruoyi-netty + ${ruoyi.version} + + + + com.ruoyi.geekxd + ruoyi-atomikos + ${ruoyi.version} + + + + com.ruoyi.geekxd + ruoyi-rabbitmq + ${ruoyi.version} + + + + com.ruoyi.geekxd + ruoyi-plugins-starter + ${ruoyi.version} + + + + + + + ruoyi-cache-ehcache + ruoyi-mybatis-jpa + ruoyi-mybatis-plus + ruoyi-websocket + ruoyi-plugins-starter + ruoyi-mybatis-interceptor + ruoyi-netty + ruoyi-atomikos + ruoyi-rabbitmq + ruoyi-cache-redis + + pom + diff --git a/ruoyi-plugins/ruoyi-atomikos/pom.xml b/ruoyi-plugins/ruoyi-atomikos/pom.xml new file mode 100644 index 0000000..81ff068 --- /dev/null +++ b/ruoyi-plugins/ruoyi-atomikos/pom.xml @@ -0,0 +1,33 @@ + + + + ruoyi-plugins + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-atomikos + + + ruoyi-atomikos + + + + + + + com.ruoyi.geekxd + ruoyi-common + + + + + com.atomikos + transactions-spring-boot3-starter + + + + + diff --git a/ruoyi-plugins/ruoyi-atomikos/src/main/java/com/ruoyi/atomikos/config/AtomikosConfig.java b/ruoyi-plugins/ruoyi-atomikos/src/main/java/com/ruoyi/atomikos/config/AtomikosConfig.java new file mode 100644 index 0000000..155ceb5 --- /dev/null +++ b/ruoyi-plugins/ruoyi-atomikos/src/main/java/com/ruoyi/atomikos/config/AtomikosConfig.java @@ -0,0 +1,65 @@ +package com.ruoyi.atomikos.config; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.jta.JtaTransactionManager; + +import com.atomikos.icatch.jta.UserTransactionImp; +import com.atomikos.icatch.jta.UserTransactionManager; +import com.atomikos.jdbc.AtomikosDataSourceBean; + +import jakarta.annotation.PreDestroy; +import jakarta.transaction.TransactionManager; +import jakarta.transaction.UserTransaction; + +/** + * JTA 事务配置 + * + * @author ruoyi + */ +@Configuration +@ConditionalOnProperty(name = "atomikos.enabled", havingValue = "true") +public class AtomikosConfig { + @Bean(name = "userTransaction") + public UserTransaction userTransaction() throws Throwable { + UserTransaction userTransaction = new UserTransactionImp(); + // 设置事务超时时间为10000毫秒 + userTransaction.setTransactionTimeout(10000); + return userTransaction; + } + + @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close") + public TransactionManager atomikosTransactionManager() throws Throwable { + UserTransactionManager userTransactionManager = new UserTransactionManager(); + // 设置是否强制关闭事务管理器为false + userTransactionManager.setForceShutdown(false); + return userTransactionManager; + } + + @Bean(name = "transactionManager") + @DependsOn({ "userTransaction", "atomikosTransactionManager" }) + public PlatformTransactionManager transactionManager() throws Throwable { + UserTransaction userTransaction = userTransaction(); + TransactionManager atomikosTransactionManager = atomikosTransactionManager(); + return new JtaTransactionManager(userTransaction, atomikosTransactionManager); + } + + private List atomikosDataSourceBeans = new ArrayList<>(); + + public List getAtomikosDataSourceBeans() { + return atomikosDataSourceBeans; + } + + @PreDestroy + public void destroy() { + for (AtomikosDataSourceBean aDataSourceBean : this.atomikosDataSourceBeans) { + aDataSourceBean.close(); + } + } +} \ No newline at end of file diff --git a/ruoyi-plugins/ruoyi-atomikos/src/main/java/com/ruoyi/atomikos/datasource/AtomikosDataSourceCreate.java b/ruoyi-plugins/ruoyi-atomikos/src/main/java/com/ruoyi/atomikos/datasource/AtomikosDataSourceCreate.java new file mode 100644 index 0000000..79bbffe --- /dev/null +++ b/ruoyi-plugins/ruoyi-atomikos/src/main/java/com/ruoyi/atomikos/datasource/AtomikosDataSourceCreate.java @@ -0,0 +1,41 @@ +package com.ruoyi.atomikos.datasource; + +import java.util.Properties; + +import javax.sql.CommonDataSource; +import javax.sql.DataSource; +import javax.sql.XADataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.DependsOn; +import org.springframework.stereotype.Component; + +import com.atomikos.spring.AtomikosDataSourceBean; +import com.ruoyi.atomikos.config.AtomikosConfig; +import com.ruoyi.common.service.datasource.AfterCreateDataSource; + +@Component +@ConditionalOnProperty(name = "atomikos.enabled", havingValue = "true") +@DependsOn({ "transactionManager" }) +public class AtomikosDataSourceCreate implements AfterCreateDataSource { + + @Autowired + private AtomikosConfig atomikosConfig; + + public DataSource afterCreateDataSource(String name, Properties prop, CommonDataSource dataSource) { + AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); + atomikosConfig.getAtomikosDataSourceBeans().add(ds); + ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource"); + ds.setUniqueResourceName(name); + ds.setXaDataSource((XADataSource) dataSource); + ds.setXaProperties(prop); + if (prop.getProperty("minIdle") != null) { + ds.setMinPoolSize(Integer.parseInt(prop.getProperty("minIdle"))); + } + if (prop.getProperty("maxActive") != null) { + ds.setMaxPoolSize(Integer.parseInt(prop.getProperty("maxActive"))); + } + return ds; + } +} diff --git a/ruoyi-plugins/ruoyi-cache-ehcache/pom.xml b/ruoyi-plugins/ruoyi-cache-ehcache/pom.xml new file mode 100644 index 0000000..2376455 --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-ehcache/pom.xml @@ -0,0 +1,33 @@ + + + + ruoyi-plugins + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-cache-ehcache + + + 中间件 + + + + + + + com.ruoyi.geekxd + ruoyi-common + + + + + org.ehcache + ehcache + + + + + diff --git a/ruoyi-plugins/ruoyi-cache-ehcache/src/main/java/com/ruoyi/ehcache/config/Ehcache3Cache.java b/ruoyi-plugins/ruoyi-cache-ehcache/src/main/java/com/ruoyi/ehcache/config/Ehcache3Cache.java new file mode 100644 index 0000000..47330cc --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-ehcache/src/main/java/com/ruoyi/ehcache/config/Ehcache3Cache.java @@ -0,0 +1,62 @@ +package com.ruoyi.ehcache.config; + +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; + +import org.ehcache.core.EhcacheBase; +import org.ehcache.impl.internal.concurrent.ConcurrentHashMap; +import org.ehcache.impl.internal.store.heap.OnHeapStore; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.Cache; +import org.springframework.cache.jcache.JCacheCache; +import org.springframework.cache.jcache.JCacheCacheManager; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.service.cache.CacheKeys; +import com.ruoyi.common.service.cache.CacheNoTimeOut; + +@Component +@ConditionalOnProperty(prefix = "spring.cache", name = { "type" }, havingValue = "jcache", matchIfMissing = false) +public class Ehcache3Cache implements CacheNoTimeOut, CacheKeys { + + @Autowired + private JCacheCacheManager jCacheCacheManager; + + @Override + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public Set getCachekeys(Cache cache) { + Set keyset = new HashSet<>(); + try { + JCacheCache jehcache = (JCacheCache) cache; + // org.ehcache.jsr107.Eh107Cache 不公开 + Object nativeCache = jehcache.getNativeCache(); + Class nativeCacheClass = nativeCache.getClass(); + Field ehCacheField = nativeCacheClass.getDeclaredField("ehCache"); + ehCacheField.setAccessible(true); + EhcacheBase ehcache = (EhcacheBase) ehCacheField.get(nativeCache); + Field storeField = EhcacheBase.class.getDeclaredField("store"); + storeField.setAccessible(true); + OnHeapStore store = (OnHeapStore) storeField.get(ehcache); + Field mapField = OnHeapStore.class.getDeclaredField("map"); + mapField.setAccessible(true); + // org.ehcache.impl.internal.store.heap.Backend 不公开 + Object map = mapField.get(store); + Class mapClass = map.getClass(); + Field realMapField = mapClass.getDeclaredField("realMap"); + realMapField.setAccessible(true); + ConcurrentHashMap realMap = (ConcurrentHashMap) realMapField.get(map); + keyset = realMap.keySet(); + } catch (Exception e) { + } + return keyset; + } + + @Override + public void setCacheObject(String cacheName, String key, T value) { + Cache cache = jCacheCacheManager.getCache(cacheName); + cache.put(cacheName, value); + } + +} diff --git a/ruoyi-plugins/ruoyi-cache-ehcache/src/main/java/com/ruoyi/ehcache/config/Ehcache3Config.java b/ruoyi-plugins/ruoyi-cache-ehcache/src/main/java/com/ruoyi/ehcache/config/Ehcache3Config.java new file mode 100644 index 0000000..aa58738 --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-ehcache/src/main/java/com/ruoyi/ehcache/config/Ehcache3Config.java @@ -0,0 +1,52 @@ +package com.ruoyi.ehcache.config; + +import java.util.concurrent.TimeUnit; + +import javax.cache.CacheManager; +import javax.cache.Caching; +import javax.cache.configuration.MutableConfiguration; +import javax.cache.expiry.CreatedExpiryPolicy; +import javax.cache.expiry.Duration; + +import org.ehcache.jsr107.EhcacheCachingProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.jcache.JCacheCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableCaching +@ConditionalOnProperty(prefix = "spring.cache", name = { "type" }, havingValue = "jcache", matchIfMissing = false) +public class Ehcache3Config { + + @Bean + public JCacheCacheManager ehcacheManager() { + EhcacheCachingProvider cachingProvider = (EhcacheCachingProvider) Caching.getCachingProvider(); + + CacheManager cacheManager = cachingProvider.getCacheManager(); + MutableConfiguration mutableConfiguration = new MutableConfiguration<>(); + mutableConfiguration.setTypes(String.class, Object.class); + mutableConfiguration.setStoreByValue(false); // 默认值为 true,可根据需求调整 + mutableConfiguration.setManagementEnabled(true); // 启用管理功能(可选) + mutableConfiguration.setStatisticsEnabled(true); // 启用统计功能(可选) + // 设置缓存过期策略(以 timeToIdle 为例,根据实际需求调整) + mutableConfiguration.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.HOURS, 1))); + + cacheManager.createCache("temp_cache", mutableConfiguration); + cacheManager.createCache("eternal_cache", mutableConfiguration); + cacheManager.createCache("sys_dict", mutableConfiguration); + cacheManager.createCache("sys_config", mutableConfiguration); + cacheManager.createCache("repeat_submit", mutableConfiguration); + cacheManager.createCache("captcha_codes", mutableConfiguration); + cacheManager.createCache("login_tokens", mutableConfiguration); + cacheManager.createCache("ip_err_cnt_key", mutableConfiguration); + cacheManager.createCache("rate_limit", mutableConfiguration); + cacheManager.createCache("pwd_err_cnt", mutableConfiguration); + + JCacheCacheManager jCacheCacheManager = new JCacheCacheManager(cacheManager); + + return jCacheCacheManager; + + } +} diff --git a/ruoyi-plugins/ruoyi-cache-redis/pom.xml b/ruoyi-plugins/ruoyi-cache-redis/pom.xml new file mode 100644 index 0000000..85af73d --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-redis/pom.xml @@ -0,0 +1,33 @@ + + + + ruoyi-plugins + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-cache-redis + + + 中间件 + + + + + + + com.ruoyi.geekxd + ruoyi-framework + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + diff --git a/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/annotation/RateLimiter.java b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/annotation/RateLimiter.java new file mode 100644 index 0000000..d631947 --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/annotation/RateLimiter.java @@ -0,0 +1,41 @@ +package com.ruoyi.middleware.redis.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.enums.LimitType; + +/** + * 限流注解 + * + * @author ruoyi + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter +{ + /** + * 限流key + */ + public String key() default CacheConstants.RATE_LIMIT_KEY; + + /** + * 限流时间,单位秒 + */ + public int time() default 60; + + /** + * 限流次数 + */ + public int count() default 100; + + /** + * 限流类型 + */ + public LimitType limitType() default LimitType.DEFAULT; +} diff --git a/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/annotation/RedisListener.java b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/annotation/RedisListener.java new file mode 100644 index 0000000..518a6ef --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/annotation/RedisListener.java @@ -0,0 +1,14 @@ +package com.ruoyi.middleware.redis.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RedisListener { + String value(); +} diff --git a/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/aspectj/RateLimiterAspect.java b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/aspectj/RateLimiterAspect.java new file mode 100644 index 0000000..ad1f0fd --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/aspectj/RateLimiterAspect.java @@ -0,0 +1,92 @@ +package com.ruoyi.middleware.redis.aspectj; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.enums.LimitType; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.middleware.redis.annotation.RateLimiter; + +/** + * 限流处理 + * + * @author ruoyi + */ +@Aspect +@Component +@ConditionalOnProperty(prefix = "spring.cache", name = { "type" }, havingValue = "redis", matchIfMissing = false) +public class RateLimiterAspect +{ + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + private RedisTemplate redisTemplate; + + private RedisScript limitScript; + + @Autowired + public void setRedisTemplate1(RedisTemplate redisTemplate) + { + this.redisTemplate = redisTemplate; + } + + @Autowired + public void setLimitScript(RedisScript limitScript) + { + this.limitScript = limitScript; + } + + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable + { + int time = rateLimiter.time(); + int count = rateLimiter.count(); + String combineKey = getCombineKey(rateLimiter, point); + List keys = Collections.singletonList(combineKey); + try + { + Long number = redisTemplate.execute(limitScript, keys, count, time); + if (StringUtils.isNull(number) || number.intValue() > count) + { + throw new ServiceException("访问过于频繁,请稍候再试"); + } + log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey); + } + catch (ServiceException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException("服务器限流异常,请稍候再试"); + } + } + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) + { + StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); + if (rateLimiter.limitType() == LimitType.IP) + { + stringBuffer.append(IpUtils.getIpAddr()).append("-"); + } + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + stringBuffer.append(targetClass.getName()).append("-").append(method.getName()); + return stringBuffer.toString(); + } +} diff --git a/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/config/FastJson2JsonRedisSerializer.java b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..f2f6842 --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,54 @@ +package com.ruoyi.middleware.redis.config; + +import java.nio.charset.Charset; + +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +// import com.alibaba.fastjson2.filter.Filter; +import com.alibaba.fastjson2.filter.Filter; +import com.ruoyi.common.constant.Constants; + +/** + * Redis使用FastJson序列化 + * + * @author ruoyi + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer +{ + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR); + + private Class clazz; + + public FastJson2JsonRedisSerializer(Class clazz) + { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) + { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length <= 0) + { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER); + } +} diff --git a/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/config/RedisConfig.java b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/config/RedisConfig.java new file mode 100644 index 0000000..6bc1763 --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/config/RedisConfig.java @@ -0,0 +1,97 @@ +package com.ruoyi.middleware.redis.config; + +import java.time.Duration; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CachingConfigurer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @author ruoyi + */ +@Configuration +@ConditionalOnProperty(prefix = "spring.cache", name = { "type" }, havingValue = "redis", matchIfMissing = false) +public class RedisConfig implements CachingConfigurer { + + @Bean + @Primary + public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { + RedisCacheConfiguration config = instanceConfig(3600 * 24 * 15L); + return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).transactionAware().build(); + } + + @Bean + public CacheManager cacheManager30m(RedisConnectionFactory connectionFactory) { + RedisCacheConfiguration config = instanceConfig(1800L); + return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).transactionAware().build(); + } + + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + private RedisCacheConfiguration instanceConfig(Long ttl) { + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + return RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(ttl)).disableCachingNullValues() + .computePrefixWith(name -> name + ":") + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer)); + } + + @Bean + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } + + @Bean + public DefaultRedisScript limitScript() { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(limitScriptText()); + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 限流脚本 + */ + private String limitScriptText() { + return "local key = KEYS[1]\n" + + "local count = tonumber(ARGV[1])\n" + + "local time = tonumber(ARGV[2])\n" + + "local current = redis.call('get', key);\n" + + "if current and tonumber(current) > count then\n" + + " return tonumber(current);\n" + + "end\n" + + "current = redis.call('incr', key)\n" + + "if tonumber(current) == 1 then\n" + + " redis.call('expire', key, time)\n" + + "end\n" + + "return tonumber(current);"; + } +} diff --git a/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/controller/RedisCacheController.java b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/controller/RedisCacheController.java new file mode 100644 index 0000000..f045ca3 --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/controller/RedisCacheController.java @@ -0,0 +1,75 @@ +package com.ruoyi.middleware.redis.controller; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.StringUtils; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + + +/** + * 缓存监控 + * + * @author ruoyi + */ +@Tag(name = "缓存监控") +@RestController +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@ConditionalOnProperty(prefix = "spring.cache", name = { "type" }, havingValue = "redis", matchIfMissing = false) +@RequestMapping("/monitor/cache") +public class RedisCacheController { + @Autowired + private RedisTemplate redisTemplate; + + + @Operation(summary = "获取缓存信息") + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception { + Map result = new HashMap<>(3); + + Properties info = (Properties) redisTemplate + .execute((RedisCallback) connection -> connection.commands().info()); + Properties commandStats = (Properties) redisTemplate + .execute((RedisCallback) connection -> connection.commands().info("commandstats")); + Object dbSize = redisTemplate.execute((RedisCallback) connection -> connection.commands().dbSize()); + result.put("info", info); + result.put("dbSize", dbSize); + + List> pieList = new ArrayList<>(); + commandStats.stringPropertyNames().forEach(key -> { + Map data = new HashMap<>(2); + String property = commandStats.getProperty(key); + data.put("name", StringUtils.removeStart(key, "cmdstat_")); + data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); + pieList.add(data); + }); + result.put("commandStats", pieList); + return AjaxResult.success(result); + } + + @Anonymous + @GetMapping("/test") + public String getMethodName() { + redisTemplate.convertAndSend("order.channel", "messageTest"); + return new String("ok"); + } + + +} diff --git a/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/controller/RedisPubSubConfig.java b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/controller/RedisPubSubConfig.java new file mode 100644 index 0000000..4708476 --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/controller/RedisPubSubConfig.java @@ -0,0 +1,73 @@ +package com.ruoyi.middleware.redis.controller; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.listener.PatternTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import com.ruoyi.middleware.redis.annotation.RedisListener; + +@Configuration +public class RedisPubSubConfig implements ApplicationListener { + protected final Log log = LogFactory.getLog(this.getClass()); + @Bean("redisListenerExecutor") + public Executor redisListenerExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(4); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("redis-listener-"); + executor.setRejectedExecutionHandler(new CallerRunsPolicy()); + executor.initialize(); + return executor; + } + + @Bean + public RedisMessageListenerContainer redisMessageListenerContainer( + RedisConnectionFactory connectionFactory, + Executor redisListenerExecutor) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + container.setTaskExecutor(redisListenerExecutor); + return container; + } + + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + ApplicationContext context = event.getApplicationContext(); + + if (context.getParent() != null) { + return; + } + + String[] beanNames = context.getBeanNamesForAnnotation(RedisListener.class); + RedisMessageListenerContainer container = context.getBean(RedisMessageListenerContainer.class); + + for (String beanName : beanNames) { + Object bean = context.getBean(beanName); + + if (!(bean instanceof MessageListener listener)) { + throw new IllegalStateException( + "@RedisListener The annotated class must implement the MessageListener interface. Bean Name: " + beanName); + } + + RedisListener annotation = bean.getClass().getAnnotation(RedisListener.class); + String channelPattern = annotation.value(); + + container.addMessageListener(listener, new PatternTopic(channelPattern)); + log.info("Registered Redis message listener [" + beanName + "] listening channel: " + channelPattern); + } + } +} \ No newline at end of file diff --git a/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/listener/RedisMessageListener.java b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/listener/RedisMessageListener.java new file mode 100644 index 0000000..d12f614 --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/listener/RedisMessageListener.java @@ -0,0 +1,52 @@ +package com.ruoyi.middleware.redis.listener; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.stereotype.Component; + +import com.ruoyi.middleware.redis.annotation.RedisListener; + +import jakarta.annotation.PreDestroy; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Component +@RedisListener("order.channel") +public class RedisMessageListener implements MessageListener { + private final BlockingQueue messageQueue = new LinkedBlockingQueue<>(); + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + @Override + public void onMessage(Message message, byte[] pattern) { + System.out.println("收到消息:" + message.toString()); + String channel = new String(message.getChannel()); + String body = new String(message.getBody()); + log.info("收到消息 - 频道: {}, 内容: {}", channel, body); + messageQueue.offer(message); + } + + public RedisMessageListener() { + scheduler.scheduleAtFixedRate(this::processMessages, 0, 1, TimeUnit.SECONDS); + } + + private void processMessages() { + List messages = new ArrayList<>(); + messageQueue.drainTo(messages, 30); + if (!messages.isEmpty()) { + log.info("批量处理消息: {}", messages); + } + } + + @PreDestroy + public void destroy() { + scheduler.shutdown(); + } +} diff --git a/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/utils/RedisCache.java b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/utils/RedisCache.java new file mode 100644 index 0000000..6065253 --- /dev/null +++ b/ruoyi-plugins/ruoyi-cache-redis/src/main/java/com/ruoyi/middleware/redis/utils/RedisCache.java @@ -0,0 +1,288 @@ +package com.ruoyi.middleware.redis.utils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.Cache; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.service.cache.CacheKeys; +import com.ruoyi.common.service.cache.CacheTimeOut; +import com.ruoyi.common.utils.StringUtils; + +/** + * spring redis 工具类 + * + * @author ruoyi + **/ +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@Component +@ConditionalOnProperty(prefix = "spring.cache", name = { "type" }, havingValue = "redis", matchIfMissing = false) +public class RedisCache implements CacheKeys, CacheTimeOut { + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String cacheName, final String key, final T value) { + redisTemplate.opsForValue().set(cacheName + ":" + key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String cacheName, final String key, final T value, final long timeout, + final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(cacheName + ":" + key, value, timeout, timeUnit); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final long timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public boolean deleteObject(final Collection collection) { + return redisTemplate.delete(collection) > 0; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return 是否成功 + */ + public boolean deleteCacheMapValue(final String key, final String hKey) { + return redisTemplate.opsForHash().delete(key, hKey) > 0; + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) { + return redisTemplate.keys(pattern); + } + + @Override + public Set getCachekeys(Cache cache) { + Set keyset = new HashSet<>(); + Set keysets = redisTemplate.keys(cache.getName() + "*"); + for (Object s : keysets) { + keyset.add(StringUtils.replace(s.toString(), cache.getName() + ":", "")); + } + return keyset; + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/pom.xml b/ruoyi-plugins/ruoyi-mybatis-interceptor/pom.xml new file mode 100644 index 0000000..85b9fd1 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/pom.xml @@ -0,0 +1,29 @@ + + + + ruoyi-plugins + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-mybatis-interceptor + + + + com.ruoyi.geekxd + ruoyi-common + + + com.ruoyi.geekxd + ruoyi-framework + + + + org.springframework.boot + spring-boot-starter-aop + + + + diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/aspectj/DataScopeAspect.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/aspectj/DataScopeAspect.java new file mode 100644 index 0000000..e2a3cc1 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/aspectj/DataScopeAspect.java @@ -0,0 +1,208 @@ +package com.ruoyi.mybatisinterceptor.aspectj; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.annotation.DataScope; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.security.context.PermissionContextHolder; +import com.ruoyi.mybatisinterceptor.context.sqlContext.SqlContextHolder; +import com.ruoyi.mybatisinterceptor.enums.ContextKey; + +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.conditional.OrExpression; +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.expression.operators.relational.InExpression; +import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo; +import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.SelectItem; + +/** + * 数据过滤处理 + * + * @author ruoyi + */ +@Aspect +@Component +@ConditionalOnProperty(prefix = "datascope", name = "type", havingValue = "plus", matchIfMissing = false) +public class DataScopeAspect { + /** + * 全部数据权限 + */ + public static final String DATA_SCOPE_ALL = "1"; + + /** + * 自定数据权限 + */ + public static final String DATA_SCOPE_CUSTOM = "2"; + + /** + * 部门数据权限 + */ + public static final String DATA_SCOPE_DEPT = "3"; + + /** + * 部门及以下数据权限 + */ + public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; + + /** + * 仅本人数据权限 + */ + public static final String DATA_SCOPE_SELF = "5"; + + /** + * 数据权限过滤关键字 + */ + public static final String DATA_SCOPE = "dataScope"; + + @Before("@annotation(controllerDataScope)") + public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable { + SqlContextHolder.startContext(); + handleDataScope(point, controllerDataScope); + + } + + protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNotNull(loginUser)) { + SysUser currentUser = loginUser.getUser(); + // 如果是超级管理员,则不过滤数据 + if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) { + String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), + PermissionContextHolder.getContext()); + dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), + controllerDataScope.userAlias(), permission); + } + } + } + + /** + * 数据范围过滤 + * + * @param joinPoint 切点 + * @param user 用户 + * @param deptAlias 部门别名 + * @param userAlias 用户别名 + * @param permission 权限字符 + */ + public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, + String permission) { + List orExpressions = new ArrayList<>(); + List conditions = new ArrayList<>(); + List scopeCustomIds = new ArrayList<>(); + user.getRoles().forEach(role -> { + if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) + && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) + && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) { + scopeCustomIds.add(Convert.toStr(role.getRoleId())); + } + }); + + for (SysRole role : user.getRoles()) { + String dataScope = role.getDataScope(); + if (conditions.contains(dataScope) || StringUtils.equals(role.getStatus(), UserConstants.ROLE_DISABLE)) { + continue; + } + if (StringUtils.isNotEmpty(permission) + && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) { + continue; + } + if (DATA_SCOPE_ALL.equals(dataScope)) { + orExpressions.clear(); + break; + } else if (DATA_SCOPE_CUSTOM.equals(dataScope) && !scopeCustomIds.isEmpty()) { + // 自定义权限: d.dept_id IN (SELECT dept_id FROM sys_role_dept WHERE role_id IN\ + if (SqlContextHolder.getData(ContextKey.DATA_SCOPE, "deptAlias", String.class) == null) { + SqlContextHolder.addData(ContextKey.DATA_SCOPE, "deptAlias", deptAlias); + } + PlainSelect subPlainSelect = new PlainSelect(); + subPlainSelect.setSelectItems(List.of(new SelectItem<>(new Column("dept_id")))); + subPlainSelect.setFromItem(new Table("sys_role_dept")); + List roleIdExpressions = scopeCustomIds.stream().map(Long::valueOf).map(LongValue::new) + .collect(Collectors.toList()); + subPlainSelect.setWhere( + new InExpression(new Column("role_id"), new ParenthesedExpressionList<>(roleIdExpressions))); + InExpression inExpression = new InExpression(new Column(deptAlias + ".dept_id"), + new ParenthesedExpressionList<>(subPlainSelect)); + orExpressions.add(new ParenthesedExpressionList<>(inExpression)); + } else if (DATA_SCOPE_DEPT.equals(dataScope)) { + // 部门权限: d.dept_id = ? + if (SqlContextHolder.getData(ContextKey.DATA_SCOPE, "deptAlias", String.class) == null) { + SqlContextHolder.addData(ContextKey.DATA_SCOPE, "deptAlias", deptAlias); + } + orExpressions.add(new EqualsTo(new Column(deptAlias + ".dept_id"), new LongValue(user.getDeptId()))); + } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) { + // 部门及以下权限: d.dept_id IN (SELECT dept_id FROM sys_dept WHERE dept_id = ? OR + // array_position(string_to_array(ancestors, ','), CAST(? AS TEXT)) IS NOT NULL) + if (SqlContextHolder.getData(ContextKey.DATA_SCOPE, "deptAlias", String.class) == null) { + SqlContextHolder.addData(ContextKey.DATA_SCOPE, "deptAlias", deptAlias); + } + PlainSelect subPlainSelect = new PlainSelect(); + subPlainSelect.setSelectItems(List.of(new SelectItem<>(new Column("dept_id")))); + subPlainSelect.setFromItem(new Table("sys_dept")); + Function findInSet = new Function(); + findInSet.setName("find_in_set"); + findInSet.setParameters(new ExpressionList<>(new LongValue(user.getDeptId()), new Column("ancestors"))); + subPlainSelect.setWhere(new OrExpression( + new EqualsTo(new Column("dept_id"), new LongValue(user.getDeptId())), findInSet)); + InExpression inExpression = new InExpression(new Column(deptAlias + ".dept_id"), + new ParenthesedExpressionList<>(subPlainSelect)); + orExpressions.add(new ParenthesedExpressionList<>(inExpression)); + } else if (DATA_SCOPE_SELF.equals(dataScope)) { + if (StringUtils.isNotBlank(userAlias)) { + if (SqlContextHolder.getData(ContextKey.DATA_SCOPE, "userAlias", String.class) == null) { + SqlContextHolder.addData(ContextKey.DATA_SCOPE, "userAlias", userAlias); + } + orExpressions + .add(new EqualsTo(new Column(userAlias + ".user_id"), new LongValue(user.getUserId()))); + } else { + if (SqlContextHolder.getData(ContextKey.DATA_SCOPE, "deptAlias", String.class) == null) { + SqlContextHolder.addData(ContextKey.DATA_SCOPE, "deptAlias", deptAlias); + } + Expression expression = new AndExpression( + new EqualsTo(new Column(deptAlias + ".dept_id"), new LongValue(user.getDeptId())), + new NotEqualsTo(new Column(deptAlias + ".dept_id"), new LongValue(user.getDeptId()))); + orExpressions.add(expression); + } + } + conditions.add(dataScope); + } + + if (!orExpressions.isEmpty()) { + Expression finalExpression = orExpressions.stream().reduce(OrExpression::new).orElse(null); + if (finalExpression != null) { + SqlContextHolder.addData(ContextKey.DATA_SCOPE, "expression", + new ParenthesedExpressionList<>(finalExpression)); + } + } + } + + @After(value = "@annotation(controllerDataScope)") + public void doAfter(final JoinPoint point, DataScope controllerDataScope) { + SqlContextHolder.clearContext(); + } + +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/page/PageContextHolder.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/page/PageContextHolder.java new file mode 100644 index 0000000..8eb6f19 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/page/PageContextHolder.java @@ -0,0 +1,135 @@ +package com.ruoyi.mybatisinterceptor.context.page; + +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.mybatisinterceptor.context.page.model.PageInfo; + +public class PageContextHolder { + private static final ThreadLocal PAGE_CONTEXT_HOLDER = new ThreadLocal<>(); + + private static final String PAGE_FLAG = "isPage"; + + private static final String PAGE_INFO = "pageInfo"; + + private static final String TOTAL = "total"; + private static final String SKIP_QUERY = "skipQuery"; + + public static void startPage() { + JSONObject jsonObject = new JSONObject(); + jsonObject.put(PAGE_FLAG, Boolean.TRUE); + PAGE_CONTEXT_HOLDER.set(jsonObject); + } + + public static void setPageInfo() { + PAGE_CONTEXT_HOLDER.get().put(PAGE_INFO, PageInfo.defaultPageInfo()); + } + + public static PageInfo getPageInfo() { + return (PageInfo) PAGE_CONTEXT_HOLDER.get().get(PAGE_INFO); + } + + public static void clear() { + PAGE_CONTEXT_HOLDER.remove(); + } + + public static boolean isPage() { + return PAGE_CONTEXT_HOLDER.get() != null && PAGE_CONTEXT_HOLDER.get().getBooleanValue(PAGE_FLAG); + } + + public static void setTotal(Long total) { + PAGE_CONTEXT_HOLDER.get().put(TOTAL, total); + } + + public static Long getTotal() { + return PAGE_CONTEXT_HOLDER.get().getLong(TOTAL); + } + + public static void setSkipQuery(boolean skip) { + if (!isPage()) { + startPage(); + setPageInfo(); + } + PAGE_CONTEXT_HOLDER.get().put(SKIP_QUERY, skip); + } + + public static boolean shouldSkipQuery() { + return PAGE_CONTEXT_HOLDER.get() != null && PAGE_CONTEXT_HOLDER.get().getBooleanValue(SKIP_QUERY); + } + + // === Facade methods for compatibility with PageHelper-like API === + + /** + * 兼容:startPage(pageNum,pageSize) + */ + public static void startPage(Integer pageNum, Integer pageSize) { + // 兼容旧签名,委托到原生 int 版本 + startPage(pageNum == null ? 1 : pageNum.intValue(), pageSize == null ? 10 : pageSize.intValue()); + } + + /** + * 与 PageHelper 对齐的签名:startPage(int pageNum, int pageSize) + */ + public static void startPage(int pageNum, int pageSize) { + startPage(); + setPageInfo(); + PageInfo info = getPageInfo(); + if (info != null) { + info.setPageNumber((long) pageNum); + info.setPageSize((long) pageSize); + } + } + + /** + * 兼容:startPage(pageNum,pageSize,orderBy) + */ + public static void startPage(Integer pageNum, Integer pageSize, String orderBy) { + // 兼容旧签名,委托到原生 int 版本 + startPage(pageNum == null ? 1 : pageNum.intValue(), pageSize == null ? 10 : pageSize.intValue(), orderBy); + } + + /** + * 与 PageHelper 对齐的签名:startPage(int pageNum, int pageSize, String orderBy) + */ + public static void startPage(int pageNum, int pageSize, String orderBy) { + startPage(pageNum, pageSize); + PageInfo info = getPageInfo(); + if (info != null) { + // 直接存入原始 orderBy 字符串;后续由拦截器中的 OrderByUtil 统一校验/构建 + info.setOrderByColumn(orderBy); + } + } + + /** + * 兼容:orderBy("col asc,...") + */ + public static void orderBy(String orderBy) { + if (!isPage()) { + startPage(); + setPageInfo(); + } + PageInfo info = getPageInfo(); + if (info != null) { + info.setOrderByColumn(orderBy); + } + } + + /** + * 设置合理化参数 + */ + public static void setReasonable(Boolean reasonable) { + if (!isPage()) { + startPage(); + setPageInfo(); + } + PageInfo info = getPageInfo(); + if (info != null) { + info.setReasonable(reasonable); + } + } + + /** + * 兼容:clearPage() + */ + public static void clearPage() { + clear(); + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/page/model/PageInfo.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/page/model/PageInfo.java new file mode 100644 index 0000000..14c14a9 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/page/model/PageInfo.java @@ -0,0 +1,157 @@ +package com.ruoyi.mybatisinterceptor.context.page.model; + +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.ServletUtils; + +public class PageInfo { + + private Long pageNumber; + + private Long pageSize; + + /** + * 排序字段(原始请求值,进入 SQL 前需转义/校验) + */ + private String orderByColumn; + + /** + * 排序方向 asc/desc + */ + private String isAsc; + + /** + * 是否进行 count 查询 + */ + private Boolean searchCount; + + /** + * 合理化分页(页码<1按1处理,pageSize<=0 按默认,超过最大按最大) + */ + private Boolean reasonable; + + /** + * pageSize 的最大上限(防止恶意大页),默认 1000 + */ + public static final long DEFAULT_MAX_PAGE_SIZE = 1000L; + + /** + * 当前记录起始索引 + */ + public static final String PAGE_NUM = "pageNum"; + + /** + * 每页显示记录数 + */ + public static final String PAGE_SIZE = "pageSize"; + + /** + * 排序列 + */ + public static final String ORDER_BY_COLUMN = "orderByColumn"; + + /** + * 排序的方向 "desc" 或者 "asc". + */ + public static final String IS_ASC = "isAsc"; + + /** + * 分页参数合理化 + */ + public static final String REASONABLE = "reasonable"; + + /** + * 是否进行 count 查询 + */ + public static final String SEARCH_COUNT = "searchCount"; + + public Long getPageNumber() { + return pageNumber; + } + + public void setPageNumber(Long pageNumber) { + this.pageNumber = pageNumber; + } + + public Long getPageSize() { + return pageSize; + } + + public void setPageSize(Long pageSize) { + this.pageSize = pageSize; + } + + public static PageInfo defaultPageInfo() { + PageInfo pageInfo = new PageInfo(); + long pn = Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1); + long ps = Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10); + // 合理化 + boolean reasonable = Boolean.parseBoolean(ServletUtils.getParameter(REASONABLE)); + if (reasonable && pn < 1) pn = 1; + if (ps <= 0) ps = 10; + if (ps > DEFAULT_MAX_PAGE_SIZE) ps = DEFAULT_MAX_PAGE_SIZE; + + pageInfo.setPageNumber(pn); + pageInfo.setPageSize(ps); + pageInfo.setReasonable(reasonable); + + String ob = ServletUtils.getParameter(ORDER_BY_COLUMN); + if (ob == null || ob.isEmpty()) { + // 兼容 RuoYi 传统参数:orderBy(可含多列及各自方向) + ob = ServletUtils.getParameter("orderBy"); + } + pageInfo.setOrderByColumn(ob); + String asc = ServletUtils.getParameter(IS_ASC); + pageInfo.setIsAsc(asc); + String sc = ServletUtils.getParameter(SEARCH_COUNT); + pageInfo.setSearchCount(sc == null ? Boolean.TRUE : Boolean.parseBoolean(sc)); + return pageInfo; + } + + public Long getOffset() { + long pn = pageNumber == null ? 1L : pageNumber.longValue(); + long ps = pageSize == null ? 10L : pageSize.longValue(); + if (pn < 1L) pn = 1L; + if (ps <= 0L) ps = 10L; + return (pn - 1L) * ps; + } + + /** + * 兼容旧方法拼写 + */ + @Deprecated + public Long getOffeset() { // 保持兼容 + return getOffset(); + } + + public String getOrderByColumn() { + return orderByColumn; + } + + public void setOrderByColumn(String orderByColumn) { + this.orderByColumn = orderByColumn; + } + + public String getIsAsc() { + return isAsc; + } + + public void setIsAsc(String isAsc) { + this.isAsc = isAsc; + } + + public Boolean getSearchCount() { + return searchCount; + } + + public void setSearchCount(Boolean searchCount) { + this.searchCount = searchCount; + } + + public Boolean getReasonable() { + return reasonable; + } + + public void setReasonable(Boolean reasonable) { + this.reasonable = reasonable; + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/page/model/TableInfo.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/page/model/TableInfo.java new file mode 100644 index 0000000..68ee548 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/page/model/TableInfo.java @@ -0,0 +1,26 @@ +package com.ruoyi.mybatisinterceptor.context.page.model; + +import java.util.ArrayList; +import java.util.List; + +public class TableInfo extends ArrayList { + + private Long total; + + public TableInfo() { + super(); + } + + public TableInfo(List list) { + super(list); + } + + public Long getTotal() { + return total; + } + + public void setTotal(Long total) { + this.total = total; + } + +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/sqlContext/SqlContextHolder.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/sqlContext/SqlContextHolder.java new file mode 100644 index 0000000..9367b1b --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/context/sqlContext/SqlContextHolder.java @@ -0,0 +1,47 @@ +package com.ruoyi.mybatisinterceptor.context.sqlContext; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.ruoyi.mybatisinterceptor.enums.ContextKey; + +public class SqlContextHolder { + + + private static final ThreadLocal, Object>>>> SQL_CONTEXT_HOLDER = new ThreadLocal<>(); + + public static void startContext() { + if (SQL_CONTEXT_HOLDER.get() == null) { + SQL_CONTEXT_HOLDER.set(new ConcurrentHashMap<>()); + } + } + + + public static void addData(ContextKey key, String subKey, T value) { + if (value == null) return; + + Map, Object>>> context = SQL_CONTEXT_HOLDER.get(); + if (context == null) { + throw new IllegalStateException("SQL context 未初始化"); + } + Map, Object>> subContext = context.computeIfAbsent(key, k -> new ConcurrentHashMap<>()); + Map, Object> typeMap = subContext.computeIfAbsent(subKey, sk -> new ConcurrentHashMap<>()); + typeMap.put(value.getClass(), value); + } + public static T getData(ContextKey key, String subKey, Class clazz) { + Map, Object>>> context = SQL_CONTEXT_HOLDER.get(); + if (context == null || !context.containsKey(key)) { + return null; + } + Map, Object>> subContext = context.get(key); + if (!subContext.containsKey(subKey)) { + return null; + } + Map, Object> typeMap = subContext.get(subKey); + Object value = typeMap.get(clazz); + return clazz.isInstance(value) ? clazz.cast(value) : null; + } + public static void clearContext() { + SQL_CONTEXT_HOLDER.remove(); + } +} \ No newline at end of file diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/Dialect.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/Dialect.java new file mode 100644 index 0000000..0c35e1d --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/Dialect.java @@ -0,0 +1,45 @@ +package com.ruoyi.mybatisinterceptor.dialect; + +import net.sf.jsqlparser.statement.select.PlainSelect; + +/** + * 方言接口:负责在不同数据库下生成 limit/offset、count SQL。 + */ +public interface Dialect { + + /** + * 对 PlainSelect 注入分页(limit/offset 或 top/row_number)。 + */ + void applyPagination(PlainSelect select, long offset, long limit); + + /** + * 生成 count SQL;对于复杂 SQL,可能需要包裹子查询。 + */ + default String buildCountSql(String originalSql, boolean hasDistinctOrGroupOrUnion) { + String body = originalSql; + if (body != null && body.endsWith(";")) { + body = body.substring(0, body.length() - 1); + } + return "SELECT COUNT(1) FROM (" + body + ") TMP_COUNT"; + } + + /** + * 为原始 SQL 包裹/追加分页(字符串方式)。 + * 默认实现适用于支持 "LIMIT n OFFSET m" 的方言(MySQL/H2/PostgreSQL/openGauss)。 + * 不要求实现检查原 SQL 是否已有 LIMIT,调用方可结合分析结果决定是否使用。 + */ + default String wrapPaginationSql(String originalSql, long offset, long limit) { + String body = originalSql; + if (body.endsWith(";")) + body = body.substring(0, body.length() - 1); + return body + " LIMIT " + limit + " OFFSET " + offset; + } + + /** + * 是否优先使用 wrap 方式分页(而非 AST 改写)。 + * 对于不支持 LIMIT 语法的方言(如 Oracle/SQL Server)应返回 true。 + */ + default boolean preferWrap() { + return false; + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/DialectRouter.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/DialectRouter.java new file mode 100644 index 0000000..8b27eee --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/DialectRouter.java @@ -0,0 +1,39 @@ +package com.ruoyi.mybatisinterceptor.dialect; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.ruoyi.framework.manager.DataSourceManager; + +@Component +public class DialectRouter { + + @Autowired + DataSourceManager dataSourceManager; + + private final Dialect mysql = new MySqlLikeDialect(); + private final Dialect postgres = new PostgresDialect(); + private final Dialect h2 = new H2Dialect(); + private final Dialect oracle = new OracleDialect(); + private final Dialect sqlserver = new SqlServerDialect(); + + public Dialect routeByCurrent() { + String id = dataSourceManager.getCurrentDatabaseId(); + return route(id); + } + + public Dialect route(String databaseId) { + String id = databaseId == null ? null : databaseId.toLowerCase(); + if (id == null) + return mysql; // 默认按 MySQL/PG 类处理 + if (id.contains("postgres") || id.contains("opengauss")) return postgres; + if (id.contains("mysql") || id.contains("mariadb")) return mysql; + if (id.contains("h2")) + return h2; + if (id.contains("oracle")) + return oracle; + if (id.contains("sqlserver") || id.contains("microsoft")) + return sqlserver; + return mysql; + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/H2Dialect.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/H2Dialect.java new file mode 100644 index 0000000..a9d8478 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/H2Dialect.java @@ -0,0 +1,19 @@ +package com.ruoyi.mybatisinterceptor.dialect; + +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.statement.select.Limit; +import net.sf.jsqlparser.statement.select.PlainSelect; + +public class H2Dialect implements Dialect { + // supportsLimit 已移除 + + @Override + public void applyPagination(PlainSelect select, long offset, long limit) { + Limit l = new Limit(); + l.setOffset(new LongValue(offset)); + l.setRowCount(new LongValue(limit)); + select.setLimit(l); + } + + // 使用 Dialect 默认的 buildCountSql +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/MySqlLikeDialect.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/MySqlLikeDialect.java new file mode 100644 index 0000000..77f0d98 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/MySqlLikeDialect.java @@ -0,0 +1,32 @@ +package com.ruoyi.mybatisinterceptor.dialect; + +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.statement.select.Limit; +import net.sf.jsqlparser.statement.select.PlainSelect; + +/** + * 适用于 MySQL/MariaDB/PostgreSQL/openGauss 的方言(均支持 limit offset)。 + */ +public class MySqlLikeDialect implements Dialect { + // supportsLimit 已移除 + + @Override + public void applyPagination(PlainSelect select, long offset, long limit) { + Limit l = new Limit(); + l.setOffset(new LongValue(offset)); + l.setRowCount(new LongValue(limit)); + select.setLimit(l); + } + + @Override + public String buildCountSql(String originalSql, boolean hasDistinctOrGroupOrUnion) { + // 简单场景可直接替换 select ... -> select count(1) + // 兼容起见统一包裹子查询,省去诸多边界处理 + String wrapped = originalSql; + // 移除末尾分号 + if (wrapped.endsWith(";")) { + wrapped = wrapped.substring(0, wrapped.length() - 1); + } + return "SELECT COUNT(1) FROM (" + wrapped + ") TMP_COUNT"; + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/OracleDialect.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/OracleDialect.java new file mode 100644 index 0000000..59e15f0 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/OracleDialect.java @@ -0,0 +1,27 @@ +package com.ruoyi.mybatisinterceptor.dialect; + +import net.sf.jsqlparser.statement.select.PlainSelect; + +/** + * Oracle 分页通过 ROWNUM 包裹。 + */ +public class OracleDialect implements Dialect { + // supportsLimit 已移除 + + @Override + public void applyPagination(PlainSelect select, long offset, long limit) { + // 在 PagePreHandler 中以字符串方式包裹处理 + } + + // 使用 Dialect 默认的 buildCountSql + + public String wrapPaginationSql(String originalSql, long offset, long limit) { + String body = originalSql; + if (body.endsWith(";")) body = body.substring(0, body.length() - 1); + long end = offset + limit; + return "SELECT * FROM (SELECT T1.*, ROWNUM RN FROM (" + body + ") T1 WHERE ROWNUM <= " + end + ") WHERE RN > " + offset; + } + + @Override + public boolean preferWrap() { return true; } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/PostgresDialect.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/PostgresDialect.java new file mode 100644 index 0000000..45487af --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/PostgresDialect.java @@ -0,0 +1,33 @@ +package com.ruoyi.mybatisinterceptor.dialect; + +import net.sf.jsqlparser.statement.select.PlainSelect; + +/** + * PostgreSQL/openGauss 方言: + * - 支持 LIMIT n OFFSET m 语法。 + * - 计数查询统一包裹子查询,避免复杂 SQL 场景下的统计误差。 + */ +public class PostgresDialect implements Dialect { + // supportsLimit 已移除 + + @Override + public void applyPagination(PlainSelect select, long offset, long limit) { + // 为避免 JSqlParser 在某些场景下输出 MySQL 风格的 "LIMIT offset, rowCount", + // PostgreSQL 的分页将优先通过字符串包裹方式处理,见 wrapPaginationSql。 + // 这里不直接改 AST。 + } + + // 使用 Dialect 默认的 buildCountSql + + /** + * 生成 PostgreSQL 的分页 SQL(LIMIT n OFFSET m)。 + */ + public String wrapPaginationSql(String originalSql, long offset, long limit) { + String body = originalSql; + if (body.endsWith(";")) body = body.substring(0, body.length() - 1); + return body + " LIMIT " + limit + " OFFSET " + offset; + } + + @Override + public boolean preferWrap() { return true; } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/SqlServerDialect.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/SqlServerDialect.java new file mode 100644 index 0000000..0542eeb --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/dialect/SqlServerDialect.java @@ -0,0 +1,37 @@ +package com.ruoyi.mybatisinterceptor.dialect; + +import net.sf.jsqlparser.statement.select.PlainSelect; + +/** + * SQL Server 2012+ 支持 OFFSET ... FETCH NEXT,需要 SQL 存在稳定的 ORDER BY。 + * 由于 JSqlParser 对 OFFSET/FETCH 支持有限,这里采用包裹方案: + * select * from (select row_number() over(order by 1) rn, t.* from (orig) t) x where rn between offset+1 and offset+limit + * 但该方式对没有稳定 order by 的结果不确定性较大,生产中建议在 SQL 层显式 order by。 + */ +public class SqlServerDialect implements Dialect { + // supportsLimit 已移除 + + @Override + public void applyPagination(PlainSelect select, long offset, long limit) { + // 在 PagePreHandler 内采用字符串包裹法来处理,避免在此直接改 PlainSelect + // 这里不做操作。 + } + + // 使用 Dialect 默认的 buildCountSql + + /** + * 生成 SQL Server 的分页包装 SQL。 + */ + public String wrapPaginationSql(String originalSql, long offset, long limit) { + String body = originalSql; + if (body.endsWith(";")) body = body.substring(0, body.length() - 1); + long start = offset + 1; + long end = offset + limit; + // 使用常量 order by 1 的行号;对生产建议强制在业务 SQL 中提供 order by,提高确定性 + return "SELECT * FROM (SELECT ROW_NUMBER() OVER(ORDER BY 1) AS RN, T.* FROM (" + body + ") T) X WHERE X.RN BETWEEN " + + start + " AND " + end; + } + + @Override + public boolean preferWrap() { return true; } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/enums/ContextKey.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/enums/ContextKey.java new file mode 100644 index 0000000..b681b62 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/enums/ContextKey.java @@ -0,0 +1,14 @@ +package com.ruoyi.mybatisinterceptor.enums; + +public enum ContextKey { + /** 数据范围权限 (由 DataScopeAspect 生成) */ + DATA_SCOPE, + /** 通用表达式 (由 DataSecurityAspect 生成) */ + GENERAL_EXPRESSIONS, + /** WHERE条件模型 (由 DataSecurityAspect 生成) */ + WHERE_MODELS, + /** JOIN TABLE模型 (由 DataSecurityAspect 生成) */ + JOIN_TABLE_MODELS + + +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/DataScopeInterceptor.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/DataScopeInterceptor.java new file mode 100644 index 0000000..0676b99 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/DataScopeInterceptor.java @@ -0,0 +1,267 @@ +package com.ruoyi.mybatisinterceptor.interceptor; + +import java.sql.Connection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; + +import com.ruoyi.mybatisinterceptor.context.sqlContext.SqlContextHolder; +import com.ruoyi.mybatisinterceptor.enums.ContextKey; + +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.Join; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectItem; + +@Slf4j +@Intercepts({ + @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }), + @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, + RowBounds.class, ResultHandler.class }), + @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, + RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }) +}) +public class DataScopeInterceptor implements Interceptor { + private static final String USER_ID_COLUMN = "user_id"; + private static final String DEPT_ID_COLUMN = "dept_id"; + private static final String USER_TABLE = "sys_user"; + private static final String DEPT_TABLE = "sys_dept"; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + String userAlias = SqlContextHolder.getData(ContextKey.DATA_SCOPE, "userAlias", String.class); + String deptAlias = SqlContextHolder.getData(ContextKey.DATA_SCOPE, "deptAlias", String.class); + ParenthesedExpressionList expressionList = SqlContextHolder.getData( + ContextKey.DATA_SCOPE, + "expression", + ParenthesedExpressionList.class); + + if (expressionList == null || expressionList.isEmpty() || expressionList.get(0) == null) { + return invocation.proceed(); + } + Expression scopeExpression = expressionList.get(0); + + Executor targetExecutor = (Executor) invocation.getTarget(); + Object[] args = invocation.getArgs(); + MappedStatement ms = (MappedStatement) args[0]; + Object parameterObject = args[1]; + RowBounds rowBounds = (RowBounds) args[2]; + ResultHandler resultHandler = (ResultHandler) args[3]; + + BoundSql boundSql; + CacheKey cacheKey; + if (args.length == 4) { + boundSql = ms.getBoundSql(parameterObject); + cacheKey = targetExecutor.createCacheKey(ms, parameterObject, rowBounds, boundSql); + } else { + cacheKey = (CacheKey) args[4]; + boundSql = (BoundSql) args[5]; + } + + try { + String originalSql = boundSql.getSql(); + if (!isMySQLDatabase(invocation)) { + log.warn("数据权限拦截器目前仅支持MySQL数据库,当前数据库类型不支持"); + return invocation.proceed(); + } + Statement statement = CCJSqlParserUtil.parse(originalSql); + if (ms.getSqlCommandType() == SqlCommandType.SELECT) { + if (userAlias != null || deptAlias != null) { + String newSql = parseSelect(statement, scopeExpression, deptAlias, userAlias); + if (!newSql.equals(originalSql)) { + BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql, + boundSql.getParameterMappings(), boundSql.getParameterObject()); + for (ParameterMapping mapping : boundSql.getParameterMappings()) { + String prop = mapping.getProperty(); + if (boundSql.hasAdditionalParameter(prop)) { + newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop)); + } + } + return targetExecutor.query(ms, parameterObject, rowBounds, resultHandler, cacheKey, + newBoundSql); + } + + } + } + } catch (Exception e) { + log.error("数据权限拦截器处理异常", e); + } finally { + SqlContextHolder.clearContext(); + } + + return invocation.proceed(); + } + + private boolean isMySQLDatabase(Invocation invocation) { + try { + Executor executor = (Executor) invocation.getTarget(); + Connection connection = executor.getTransaction().getConnection(); + String databaseProductName = connection.getMetaData().getDatabaseProductName(); + return "MySQL".equalsIgnoreCase(databaseProductName); + } catch (Exception e) { + log.warn("当前数据权限仅支持MySQL数据库", e); + return false; + } + } + + private String parseSelect(Statement statement, Expression expression, String deptAlias, String userAlias) + throws Exception { + PlainSelect plainSelect = ((Select) statement).getPlainSelect(); + + Map tableAliasMap = new HashMap<>(); + Map aliasToTableMap = new HashMap<>(); + Map aliasToJoinMap = new HashMap<>(); + + if (plainSelect.getFromItem() instanceof Table) { + Table mainTable = (Table) plainSelect.getFromItem(); + String tableName = mainTable.getName(); + String alias = mainTable.getAlias() != null ? mainTable.getAlias().getName() : tableName; + tableAliasMap.put(tableName, alias); + } + + List joins = plainSelect.getJoins(); + if (joins != null) { + for (Join join : joins) { + if (join.getRightItem() instanceof Table) { + Table joinTable = (Table) join.getRightItem(); + String tableName = joinTable.getName(); + String alias = joinTable.getAlias() != null ? joinTable.getAlias().getName() : tableName; + tableAliasMap.put(tableName, alias); + aliasToTableMap.put(alias, tableName); + aliasToJoinMap.put(alias, join); + } + } + } + + Set selectColumns = parseSelectColumns(plainSelect); + String targetAlias = null; + String targetType = null; + + if (userAlias != null) { + String userTableAlias = tableAliasMap.get(USER_TABLE); + if (userTableAlias != null && userTableAlias.equals(userAlias)) { + targetAlias = userAlias; + targetType = "用户表"; + log.debug("数据权限拦截器:使用用户表别名 {}", userAlias); + } else if (userTableAlias != null) { + log.warn("数据权限拦截器:用户表别名不匹配,期望 {} 实际 {}", userAlias, userTableAlias); + } else { + if (tableAliasMap.containsValue(userAlias) && + (selectColumns.contains(userAlias + "." + USER_ID_COLUMN) || + selectColumns.contains(USER_ID_COLUMN))) { + targetAlias = userAlias; + String tableName = aliasToTableMap.get(targetAlias); + targetType = String.format("业务表(%s)", tableName); + log.debug("数据权限拦截器:列 {} 在表 {} 存在", DEPT_ID_COLUMN, tableName); + } + } + } + + if (targetAlias == null && deptAlias != null) { + String deptTableAlias = tableAliasMap.get(DEPT_TABLE); + if (deptTableAlias != null && deptTableAlias.equals(deptAlias)) { + targetAlias = deptAlias; + targetType = "部门表"; + log.debug("数据权限拦截器:使用部门表别名 {}", deptAlias); + } else if (deptTableAlias != null) { + log.warn("数据权限拦截器:部门表别名不匹配,期望 {} 实际 {}", deptAlias, deptTableAlias); + } else { + if (tableAliasMap.containsValue(deptAlias) && + (selectColumns.contains(deptAlias + "." + DEPT_ID_COLUMN) || + selectColumns.contains(DEPT_ID_COLUMN))) { + targetAlias = deptAlias; + String tableName = aliasToTableMap.get(targetAlias); + targetType = String.format("业务表(%s)", tableName); + log.debug("数据权限拦截器:列 {} 在表 {} 存在", DEPT_ID_COLUMN, tableName); + } + } + } + + if (userAlias != null || deptAlias != null) { + if (targetAlias != null && aliasToJoinMap.containsKey(targetAlias)) { + Join targetJoin = aliasToJoinMap.get(targetAlias); + if (targetJoin.isInner()) { + Expression currentOn = null; + if (targetJoin.getOnExpressions() != null) { + currentOn = targetJoin.getOnExpressions().stream().reduce(AndExpression::new).orElse(null); + } + Expression newOn = (currentOn == null) ? expression : new AndExpression(currentOn, expression); + List expressions = new ArrayList<>(); + expressions.add(new ParenthesedExpressionList<>(newOn)); + targetJoin.setOnExpressions(expressions); + log.debug("数据权限拦截器:数据权限条件已应用到 INNER JOIN ON 子句(表别名:{})", targetAlias); + } + } + Expression currentWhere = plainSelect.getWhere(); + Expression newWhere = currentWhere == null ? expression : new AndExpression(currentWhere, expression); + plainSelect.setWhere(newWhere); + log.debug("数据权限拦截器:数据权限条件已应用到主 WHERE 子句(表类型:{})", targetType != null ? targetType : "未知"); + } + + return statement.toString(); + } + + private Set parseSelectColumns(PlainSelect plainSelect) { + Set columns = new HashSet<>(); + + if (plainSelect.getSelectItems() != null) { + for (SelectItem selectItem : plainSelect.getSelectItems()) { + handleSingleColumn(selectItem.getExpression(), columns); + } + } + + if (plainSelect.getWhere() != null) { + handleSingleColumn(plainSelect.getWhere(), columns); + } + + if (plainSelect.getJoins() != null) { + for (Join join : plainSelect.getJoins()) { + Collection onExpressions = join.getOnExpressions(); + if (onExpressions != null && !onExpressions.isEmpty()) { + for (Expression onExpression : onExpressions) { + handleSingleColumn(onExpression, columns); + } + } + } + } + + return columns; + } + + private void handleSingleColumn(Expression expression, Set columns) { + if (expression instanceof Column) { + Column column = (Column) expression; + String columnName = column.getColumnName(); + if (column.getTable() != null) { + columns.add(column.getTable().getName() + "." + columnName); + } else { + columns.add(columnName); + } + } + } +} \ No newline at end of file diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/MybatisInterceptor.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/MybatisInterceptor.java new file mode 100644 index 0000000..02f4e65 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/MybatisInterceptor.java @@ -0,0 +1,58 @@ +package com.ruoyi.mybatisinterceptor.interceptor; + +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; + +public abstract class MybatisInterceptor implements Interceptor { + + @Override + public Object intercept(Invocation invocation) throws Throwable { + Executor targetExecutor = (Executor) invocation.getTarget(); + Object[] args = invocation.getArgs(); + MappedStatement ms = (MappedStatement) args[0]; + Object parameterObject = args[1]; + RowBounds rowBounds = (RowBounds) args[2]; + ResultHandler resultHandler = (ResultHandler) args[3]; + Object ret = null; + QueryFetcher fetcher = null; + if (args.length < 6) { + BoundSql boundSql = ms.getBoundSql(parameterObject); + CacheKey cacheKey = targetExecutor.createCacheKey(ms, parameterObject, rowBounds, boundSql); + ret = runPreHandlers(targetExecutor, ms, parameterObject, rowBounds, resultHandler, cacheKey, boundSql); + fetcher = () -> targetExecutor.query(ms, parameterObject, rowBounds, resultHandler, cacheKey, boundSql); + } else { + CacheKey cacheKey = (CacheKey) args[4]; + BoundSql boundSql = (BoundSql) args[5]; + ret = runPreHandlers(targetExecutor, ms, parameterObject, rowBounds, resultHandler, cacheKey, boundSql); + fetcher = () -> invocation.proceed(); + } + try { + if (ret != null) { + return applyAfterHandlers(ret); + } else { + return applyAfterHandlers(fetcher.get()); + } + } finally { + finish(); + } + } + + public abstract Object runPreHandlers(Executor executor, MappedStatement ms, Object parameterObject, + RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws Throwable; + + public abstract Object applyAfterHandlers(Object result) throws Throwable; + + public abstract void finish(); + + @FunctionalInterface + private interface QueryFetcher { + Object get() throws Throwable; + } + +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/PageInercetor.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/PageInercetor.java new file mode 100644 index 0000000..1157b6a --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/interceptor/PageInercetor.java @@ -0,0 +1,268 @@ +package com.ruoyi.mybatisinterceptor.interceptor; + +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.ResultMapping; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.springframework.util.ReflectionUtils; + +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.common.utils.sql.SqlUtil; +import com.ruoyi.mybatisinterceptor.context.page.PageContextHolder; +import com.ruoyi.mybatisinterceptor.context.page.model.PageInfo; +import com.ruoyi.mybatisinterceptor.context.page.model.TableInfo; +import com.ruoyi.mybatisinterceptor.dialect.Dialect; +import com.ruoyi.mybatisinterceptor.dialect.DialectRouter; +// no dialect-specific imports needed; rely on Dialect API +import com.ruoyi.mybatisinterceptor.util.OrderByUtil; +import com.ruoyi.mybatisinterceptor.util.SqlAnalysisCache; + +// +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statement; +// +import net.sf.jsqlparser.statement.select.OrderByElement; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; + +@Intercepts({ + @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, + RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }), + @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, + RowBounds.class, ResultHandler.class }) +}) +public class PageInercetor extends MybatisInterceptor { + + private static final List EMPTY_RESULTMAPPING = new ArrayList(0); + + private static final String SELECT_COUNT_SUFIX = "_SELECT_COUNT"; + private static final Field sqlFiled = ReflectionUtils.findField(BoundSql.class, "sql"); + static { + sqlFiled.setAccessible(true); + } + + private DialectRouter dialectRouter = SpringUtils.getBean(DialectRouter.class); + + @Override + public Object runPreHandlers(Executor executor, MappedStatement mappedStatement, Object params, RowBounds rowBounds, + ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws Throwable { + // 避免对内部 count 查询再次分页/递归 + if (mappedStatement.getId() != null && mappedStatement.getId().endsWith(SELECT_COUNT_SUFIX)) { + return null; + } + if (PageContextHolder.isPage()) { + String originSql = boundSql.getSql(); + SqlAnalysisCache.Analysis analysis = SqlAnalysisCache.analyze(originSql); + // 惰性解析:仅在确需 AST(注入排序或复杂分页)时再 parse + Statement sql = null; + Select selectAst = null; + { + PageInfo pageInfo = PageContextHolder.getPageInfo(); + String orderExpr = OrderByUtil.build(pageInfo.getOrderByColumn(), pageInfo.getIsAsc()); + + // 路由方言 + Dialect dialect = dialectRouter.routeByCurrent(); + + // 生成 count(可关闭) + Long total = null; + boolean doCount = pageInfo.getSearchCount() == null || pageInfo.getSearchCount().booleanValue(); + if (doCount) { + String base = analysis.noOrderSql != null ? analysis.noOrderSql : originSql; // 优化:去掉 order by + String countSql = dialect.buildCountSql(base, analysis.complex); + total = getCount(executor, mappedStatement, params, boundSql, rowBounds, resultHandler, countSql); + PageContextHolder.setTotal(total); + // 若 total=0,标记跳过主查询,由拦截器统一执行短路 + if (total != null && total.longValue() == 0L) { + PageContextHolder.setSkipQuery(true); + } + } + + // PageHelper 合理化语义对齐:在拿到 total 后修正页码 + if (Boolean.TRUE.equals(pageInfo.getReasonable())) { + // 仅在已执行 count 的情况下才能根据最大页修正 + if (doCount) { + long ps = pageInfo.getPageSize() == null ? 10L + : Math.max(1L, pageInfo.getPageSize()); + long pages = (total == null || total <= 0L) ? 0L : ((total + ps - 1L) / ps); + long pn = pageInfo.getPageNumber() == null ? 1L : pageInfo.getPageNumber(); + if (pn < 1L) + pn = 1L; + if (pages == 0L) { + // 无数据时,PageHelper 将页码校正为 1 + pn = 1L; + } else if (pn > pages) { + pn = pages; + } + pageInfo.setPageNumber(pn); + } else { + // 未进行 count 时,仅做下限校正 + long pn = pageInfo.getPageNumber() == null ? 1L : pageInfo.getPageNumber(); + if (pn < 1L) + pageInfo.setPageNumber(1L); + } + } + + // 注入排序(仅当需要注入且原 SQL 无排序时,才解析 AST 注入) + boolean needInjectOrder = (orderExpr != null && !orderExpr.isEmpty() && analysis.noOrderSql == null); + if (needInjectOrder || analysis.hasLimit || (!dialect.preferWrap() && analysis.complex)) { + // 需要 AST:解析一次 + if (sql == null) + sql = SqlUtil.parseSql(originSql); + if (sql instanceof Select) { + selectAst = (Select) sql; + if (needInjectOrder) { + applyOrderByIfPresent(selectAst, pageInfo); + } + } + } + + // 注入分页 + String baseSqlForPage = (selectAst != null) ? selectAst.toString() : originSql; + String pagedSqlStr; + if (selectAst != null) { + // 用 AST 注入分页 + pagedSqlStr = applyPagination(selectAst, baseSqlForPage, pageInfo, dialect, analysis); + } else { + // 不解析,走包装 + long limitSize = pageInfo.getPageSize(); + long offset = pageInfo.getOffset(); + pagedSqlStr = dialect.wrapPaginationSql(baseSqlForPage, offset, limitSize); + } + sqlFiled.set(boundSql, pagedSqlStr); + cacheKey.update(pagedSqlStr); + } + } + if (PageContextHolder.shouldSkipQuery()) { + return Collections.emptyList(); + } else { + return null; + } + } + + @Override + public Object applyAfterHandlers(Object object) throws Throwable { + if (PageContextHolder.isPage()) { + if (object instanceof List) { + TableInfo tableInfo = new TableInfo((List) object); + tableInfo.setTotal(PageContextHolder.getTotal()); + return tableInfo; + } + return object; + } + return object; + } + + @Override + public void finish() { + PageContextHolder.clear(); + } + + private static MappedStatement createCountMappedStatement(MappedStatement ms, String newMsId) { + MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), newMsId, ms.getSqlSource(), + ms.getSqlCommandType()); + builder.resource(ms.getResource()); + builder.fetchSize(ms.getFetchSize()); + builder.statementType(ms.getStatementType()); + builder.keyGenerator(ms.getKeyGenerator()); + if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) { + StringBuilder keyProperties = new StringBuilder(); + for (String keyProperty : ms.getKeyProperties()) { + keyProperties.append(keyProperty).append(","); + } + keyProperties.delete(keyProperties.length() - 1, keyProperties.length()); + builder.keyProperty(keyProperties.toString()); + } + builder.timeout(ms.getTimeout()); + builder.parameterMap(ms.getParameterMap()); + // count查询返回值int + List resultMaps = new ArrayList(); + ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), Long.class, EMPTY_RESULTMAPPING) + .build(); + resultMaps.add(resultMap); + builder.resultMaps(resultMaps); + builder.resultSetType(ms.getResultSetType()); + builder.cache(ms.getCache()); + builder.flushCacheRequired(ms.isFlushCacheRequired()); + builder.useCache(ms.isUseCache()); + return builder.build(); + } + + public static Long getCount(Executor executor, MappedStatement mappedStatement, Object parameter, + BoundSql boundSql, RowBounds rowBounds, ResultHandler resultHandler, String countSql) + throws SQLException { + Map additionalParameters = boundSql.getAdditionalParameters(); + BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, + boundSql.getParameterMappings(), parameter); + for (String key : additionalParameters.keySet()) { + countBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); + } + CacheKey countKey = executor.createCacheKey(mappedStatement, parameter, RowBounds.DEFAULT, countBoundSql); + List query = executor.query( + createCountMappedStatement(mappedStatement, getCountMSId(mappedStatement)), + parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql); + return (Long) query.get(0); + } + + private static String getCountMSId(MappedStatement mappedStatement) { + return mappedStatement.getId() + SELECT_COUNT_SUFIX; + } + + // 已简化:count 直接通过 dialect.buildCountSql(originSql, analysis.complex) + private String applyPagination(Select select, String originSql, PageInfo pageInfo, Dialect dialect, + SqlAnalysisCache.Analysis analysis) { + long limitSize = pageInfo.getPageSize(); + long offset = pageInfo.getOffset(); + if (dialect.preferWrap()) { + return dialect.wrapPaginationSql(originSql, offset, limitSize); + } + // 支持 LIMIT 的方言:对于非复杂并且无现有 LIMIT 的 SQL,走字符串快路径(委托方言包装) + if (!analysis.complex && !analysis.hasLimit) { + return dialect.wrapPaginationSql(originSql, offset, limitSize); + } + // 其余情况使用 AST 添加 LIMIT(委托方言以保持一致性) + dialect.applyPagination(select.getPlainSelect(), offset, limitSize); + return select.toString(); + } + + private void applyOrderByIfPresent(Select select, PageInfo pageInfo) { + String orderExpr = OrderByUtil.build(pageInfo.getOrderByColumn(), pageInfo.getIsAsc()); + if (orderExpr == null || orderExpr.isEmpty()) + return; + PlainSelect plain = select.getPlainSelect(); + if (plain.getOrderByElements() != null && !plain.getOrderByElements().isEmpty()) { + return; // 已有排序,按原 SQL 为准 + } + String[] items = orderExpr.split(","); + List list = new ArrayList<>(); + for (String item : items) { + String[] parts = item.trim().split("\\s+"); + if (parts.length >= 1) { + OrderByElement e = new OrderByElement(); + e.setExpression(new Column(parts[0])); + if (parts.length >= 2) { + e.setAsc("asc".equalsIgnoreCase(parts[1])); + } else { + e.setAsc(true); + } + list.add(e); + } + } + if (!list.isEmpty()) { + plain.setOrderByElements(list); + } + } + +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/OrderByUtil.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/OrderByUtil.java new file mode 100644 index 0000000..aad1893 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/OrderByUtil.java @@ -0,0 +1,44 @@ +package com.ruoyi.mybatisinterceptor.util; + +import java.util.Arrays; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.springframework.util.StringUtils; + +/** + * 安全构建 order by 子句,简单白名单/字符校验,避免注入。 + */ +public class OrderByUtil { + private static final Pattern COL_OK = Pattern.compile("^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)?$"); + private static final Set DIR = Set.of("asc", "desc"); + + public static String build(String orderByColumn, String isAsc) { + if (!StringUtils.hasText(orderByColumn)) return null; + String globalDirTmp = (isAsc == null ? "" : isAsc.trim().toLowerCase()); + if (!DIR.contains(globalDirTmp)) globalDirTmp = null; + final String globalDir = globalDirTmp; + + // 分解项目:允许 item 形如 "col" 或 "schema.col" 或 "col asc/desc" + String[] items = Arrays.stream(orderByColumn.split(",")) + .map(String::trim) + .filter(s -> s.length() > 0) + .toArray(String[]::new); + if (items.length == 0) return null; + + return Arrays.stream(items).map(item -> { + String[] parts = item.split("\\s+"); + if (parts.length == 0) return null; + String col = parts[0]; + if (!COL_OK.matcher(col).matches()) return null; + String dir = null; + if (parts.length >= 2) { + String d = parts[1].toLowerCase(); + if (DIR.contains(d)) dir = d; + } + if (dir == null) dir = (globalDir == null ? "asc" : globalDir); + return col + " " + dir; + }).filter(s -> s != null).collect(Collectors.joining(", ")); + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/SqlAnalysisCache.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/SqlAnalysisCache.java new file mode 100644 index 0000000..b2d8f89 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/SqlAnalysisCache.java @@ -0,0 +1,83 @@ +package com.ruoyi.mybatisinterceptor.util; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.ruoyi.common.utils.sql.SqlUtil; + +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.Limit; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; + +/** + * 轻量 SQL 分析缓存:缓存无 OrderBy 的 SQL 与特征位,减少重复解析负担。 + */ +public final class SqlAnalysisCache { + private SqlAnalysisCache() {} + + public static final class Analysis { + public final boolean isSelect; + public final boolean complex; + public final boolean hasOrderBy; + public final boolean hasLimit; + public final String noOrderSql; // 去除 order by 后的 SQL(仅 PlainSelect 有值) + + Analysis(boolean isSelect, boolean complex, boolean hasOrderBy, boolean hasLimit, String noOrderSql) { + this.isSelect = isSelect; + this.complex = complex; + this.hasOrderBy = hasOrderBy; + this.hasLimit = hasLimit; + this.noOrderSql = noOrderSql; + } + } + + private static final int MAX = 1024; + private static final Map CACHE = Collections.synchronizedMap(new LinkedHashMap(128, 0.75f, true) { + private static final long serialVersionUID = 1L; + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > MAX; + } + }); + + public static Analysis analyze(String originSql) { + if (originSql == null) return new Analysis(false, false, false, false, null); + Analysis a = CACHE.get(originSql); + if (a != null) return a; + boolean isSelect = false; + boolean complex = false; + boolean hasOrderBy = false; + boolean hasLimit = false; + String noOrderSql = null; + try { + Statement st = SqlUtil.parseSql(originSql); + if (st instanceof Select) { + isSelect = true; + Select sel = (Select) st; + PlainSelect plain = sel.getPlainSelect(); + if (plain != null) { + hasOrderBy = plain.getOrderByElements() != null && !plain.getOrderByElements().isEmpty(); + Limit limit = plain.getLimit(); + hasLimit = limit != null; + complex = plain.getDistinct() != null || plain.getGroupBy() != null || plain.getIntoTables() != null + || plain.getHaving() != null || (plain.getJoins() != null && !plain.getJoins().isEmpty()); + if (hasOrderBy) { + // 暂存后移除 order by 生成字符串,再不复用该 AST + plain.setOrderByElements(null); + noOrderSql = sel.toString(); + } + } else { + // 非 PlainSelect(如 UNION)按复杂处理 + complex = true; + } + } + } catch (Throwable ignore) { + // 解析失败,视为非 select + } + Analysis res = new Analysis(isSelect, complex, hasOrderBy, hasLimit, noOrderSql); + CACHE.put(originSql, res); + return res; + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/SqlUtil.java b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/SqlUtil.java new file mode 100644 index 0000000..98e1f54 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-interceptor/src/main/java/com/ruoyi/mybatisinterceptor/util/SqlUtil.java @@ -0,0 +1,64 @@ +package com.ruoyi.mybatisinterceptor.util; + +import com.ruoyi.common.exception.UtilException; +import com.ruoyi.common.utils.StringUtils; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.statement.Statement; + +import java.io.StringReader; + +/** + * sql操作工具类 + * + * @author ruoyi + */ +public class SqlUtil { + /** + * 定义常用的 sql关键字 + */ + public static String SQL_REGEX = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |+|user()"; + + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; + + private static final CCJSqlParserManager parserManager = new CCJSqlParserManager(); + + /** + * 检查字符,防止注入绕过 + */ + public static String escapeOrderBySql(String value) { + if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) { + throw new UtilException("参数不符合规范,不能进行查询"); + } + return value; + } + + /** + * 验证 order by 语法是否符合规范 + */ + public static boolean isValidOrderBySql(String value) { + return value.matches(SQL_PATTERN); + } + + /** + * SQL关键字检查 + */ + public static void filterKeyword(String value) { + if (StringUtils.isEmpty(value)) { + return; + } + String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); + for (String sqlKeyword : sqlKeywords) { + if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) { + throw new UtilException("参数存在SQL注入风险"); + } + } + } + + public static Statement parseSql(String sql) throws JSQLParserException { + return parserManager.parse(new StringReader(sql)); + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/pom.xml b/ruoyi-plugins/ruoyi-mybatis-jpa/pom.xml new file mode 100644 index 0000000..3545ec5 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/pom.xml @@ -0,0 +1,33 @@ + + + + ruoyi-plugins + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-mybatis-jpa + + + ruoyi-mybatis-jpa + + + + + + + com.ruoyi.geekxd + ruoyi-common + + + + com.baomidou + mybatis-plus-spring-boot3-starter + true + + + + + diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/Column.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/Column.java new file mode 100644 index 0000000..e977634 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/Column.java @@ -0,0 +1,18 @@ +package com.ruoyi.mybatis.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 标注数据库字段 + * + * @author Dftre + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Column { + String name(); // 对应数据库字段名 + boolean primaryKey() default false; // 是否是主键 +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/ColumnMap.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/ColumnMap.java new file mode 100644 index 0000000..5e1128f --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/ColumnMap.java @@ -0,0 +1,25 @@ +package com.ruoyi.mybatis.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 标注数据库映射字段 + * + * @author Dftre + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ColumnMap { + String name(); // 对应数据库字段 + + String target(); // 映射表来源 + + String on() default ""; // 映射表字段 + + String onLeft() default ""; // 映射表左字段 + + String onRight() default ""; // 映射表右字段 +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/EnableTableMap.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/EnableTableMap.java new file mode 100644 index 0000000..2e6a887 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/EnableTableMap.java @@ -0,0 +1,35 @@ +package com.ruoyi.mybatis.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 开启数据库关联 + * + * @author Dftre + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface EnableTableMap { + String name() default "t"; + + String dept() default ""; + + String user() default ""; + + String userOn() default "user_id"; + + String userOnLeft() default ""; + + String userOnRight() default ""; + + String deptOn() default "dept_id"; + + String deptOnLeft() default ""; + + String deptOnRight() default ""; + + String deptFrom() default ""; +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/Query.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/Query.java new file mode 100644 index 0000000..458c211 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/Query.java @@ -0,0 +1,27 @@ +package com.ruoyi.mybatis.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.ruoyi.mybatis.enums.QueryEnum; + +/** + * 标注查询条件 + * + * @author Dftre + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Query { + QueryEnum operation() default QueryEnum.eq; // 操作符,如 eq, like, gt 等 + + String[] sections() default { "begin", "end" }; + + String section() default "section"; + + boolean params() default false; + + String sql() default ""; +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/Table.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/Table.java new file mode 100644 index 0000000..fb3619e --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/annotation/Table.java @@ -0,0 +1,14 @@ +package com.ruoyi.mybatis.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Table { + String name(); + String[] columns() default {}; + String[] orderBy() default {}; +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/domain/BaseColumnInfo.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/domain/BaseColumnInfo.java new file mode 100644 index 0000000..4335d08 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/domain/BaseColumnInfo.java @@ -0,0 +1,54 @@ +package com.ruoyi.mybatis.domain; + +import java.lang.reflect.Field; + +import com.ruoyi.mybatis.annotation.Query; +import com.ruoyi.mybatis.utils.QueryUtil; + +public class BaseColumnInfo { + protected String columnName; + protected String fieldName; + protected Field field; + protected Query query; + protected String querySql; + + public Field getField() { + return field; + } + + public Query getQuery() { + return query; + } + + public String getTemplate() { + return getTemplate(false); + } + + public String getColumnName() { + return columnName; + } + + public String getUnqualifiedColumnName() { + return columnName; + } + + public String getTemplate(boolean params) { + if (params) { + return "#{params." + fieldName + "}"; + } else { + return "#{" + fieldName + "}"; + } + } + + public boolean fieldQueryIsNotNull(Object entity) { + return QueryUtil.fieldQueryIsNotNull(entity, field, query); + } + + public boolean fieldIsNotNull(Object entity) { + try { + return this.field.get(entity) != null; + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to access field for building query conditions.", e); + } + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/domain/ColumnInfo.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/domain/ColumnInfo.java new file mode 100644 index 0000000..489ac13 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/domain/ColumnInfo.java @@ -0,0 +1,39 @@ +package com.ruoyi.mybatis.domain; + +import java.lang.reflect.Field; + +import org.springframework.core.annotation.AnnotationUtils; + +import com.ruoyi.mybatis.annotation.Column; +import com.ruoyi.mybatis.annotation.Query; +import com.ruoyi.mybatis.utils.QueryUtil; + +public class ColumnInfo extends BaseColumnInfo { + private Column column; + + public ColumnInfo(Field field) { + this.field = field; + this.column = AnnotationUtils.findAnnotation(this.field, Column.class); + this.query = AnnotationUtils.findAnnotation(this.field, Query.class); + this.columnName = this.column.name(); + this.fieldName = this.field.getName(); + this.field.setAccessible(true); + this.querySql = this.getQuerySql(this.query); + } + + public boolean isPrimaryKey() { + return this.column.primaryKey(); + } + + public String getQuerySql(Query query) { + return QueryUtil.getQuerySql(this.getColumnName(), getTemplate(), query); + } + + public String getQuerySql() { + return this.querySql; + } + + public String getFullyQualifiedColumnName(String tableName) { + return tableName + "." + this.getColumnName(); + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/domain/MapColumnInfo.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/domain/MapColumnInfo.java new file mode 100644 index 0000000..f83eb60 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/domain/MapColumnInfo.java @@ -0,0 +1,45 @@ +package com.ruoyi.mybatis.domain; + +import java.lang.reflect.Field; + +import org.springframework.core.annotation.AnnotationUtils; + +import com.ruoyi.mybatis.annotation.ColumnMap; +import com.ruoyi.mybatis.annotation.Query; +import com.ruoyi.mybatis.utils.QueryUtil; + +/** + * 数据库关联字段信息 + * + * @author Dftre + */ +public class MapColumnInfo extends BaseColumnInfo { + private ColumnMap columnMap; + + public MapColumnInfo(Field field) { + this.field = field; + this.columnMap = AnnotationUtils.findAnnotation(this.field, ColumnMap.class); + this.columnName = this.columnMap.name(); + this.query = AnnotationUtils.findAnnotation(this.field, Query.class); + this.fieldName = this.field.getName(); + this.field.setAccessible(true); + this.querySql = this.getQuerySql(this.query); + } + + public ColumnMap getJoin() { + return this.columnMap; + } + + public String getQuerySql(Query query) { + return QueryUtil.getQuerySql(this.getColumnName(), getTemplate(), query); + } + + public String getQuerySql() { + return this.columnMap.target() + "." + this.querySql; + } + + public String getFullyQualifiedColumnName() { + return this.columnMap.target() + "." + this.getColumnName(); + } + +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/domain/TableInfo.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/domain/TableInfo.java new file mode 100644 index 0000000..0ebb5df --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/domain/TableInfo.java @@ -0,0 +1,206 @@ +package com.ruoyi.mybatis.domain; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.core.annotation.AnnotationUtils; + +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.mybatis.annotation.Column; +import com.ruoyi.mybatis.annotation.ColumnMap; +import com.ruoyi.mybatis.annotation.EnableTableMap; +import com.ruoyi.mybatis.annotation.Table; +import com.ruoyi.mybatis.utils.QueryUtil; + +/** + * 数据库表信息 + * + * @author Dftre + */ +public class TableInfo { + private String tableName; + private EnableTableMap enableTableMap; + private Table table; + private List columns = new ArrayList<>(); // 所有标记column注解的字段 + private List primaryKeys = new ArrayList<>(); + private List mapColumns = new ArrayList<>(); + private Set joinSql = new HashSet<>(); + boolean hasDataScopeValue = false; + + public TableInfo(Class cls) { + this.table = AnnotationUtils.findAnnotation(cls, Table.class); + if (this.table == null) { + throw new RuntimeException("error , not find tableName"); + } + this.tableName = this.table.name(); + this.enableTableMap = AnnotationUtils.findAnnotation(cls, EnableTableMap.class); + + Arrays.stream(cls.getDeclaredFields()) + .filter(field -> AnnotationUtils.findAnnotation(field, Column.class) != null) + .map(ColumnInfo::new) + .forEach(this.columns::add); + + Arrays.stream(cls.getDeclaredFields()) + .filter(field -> AnnotationUtils.findAnnotation(field, ColumnMap.class) != null) + .map(MapColumnInfo::new) + .forEach(this.mapColumns::add); + + this.getColumns().stream() + .filter(ColumnInfo::isPrimaryKey) + .forEach(this.primaryKeys::add); + + this.getMapColumns().stream() + .map(MapColumnInfo::getJoin) + .map(join -> { + String lf = join.onLeft(); + String rf = join.onRight(); + String lt = this.getTableNameT(); + String rt = join.target(); + if (StringUtils.isEmpty(lf) || StringUtils.isEmpty(rf)) { + lf = join.on(); + rf = join.on(); + } + return QueryUtil.getJoinSql(lt, rt, lf, rf); + }) + .forEach(joinSql::add); + + if (this.enableTableMap != null) { + if (StringUtils.isNotEmpty(this.enableTableMap.user())) { + String lf = this.enableTableMap.userOnLeft(); + String rf = this.enableTableMap.userOnRight(); + String lt = this.getTableNameT(); + String rt = this.enableTableMap.user(); + if (StringUtils.isEmpty(lf) || StringUtils.isEmpty(rf)) { + lf = this.enableTableMap.userOn(); + rf = this.enableTableMap.userOn(); + } + this.joinSql.add("sys_user " + QueryUtil.getJoinSql(lt, rt, lf, rf)); + this.hasDataScopeValue = true; + } + + if (StringUtils.isNotEmpty(this.enableTableMap.dept())) { + String lf = this.enableTableMap.deptOnLeft(); + String rf = this.enableTableMap.deptOnRight(); + String lt = StringUtils.isNotEmpty(this.enableTableMap.deptFrom()) ? this.enableTableMap.deptFrom() + : this.getTableNameT(); + String rt = this.enableTableMap.dept(); + if (StringUtils.isEmpty(lf) || StringUtils.isEmpty(rf)) { + lf = this.enableTableMap.deptOn(); + rf = this.enableTableMap.deptOn(); + } + this.joinSql.add("sys_dept " + QueryUtil.getJoinSql(lt, rt, lf, rf)); + this.hasDataScopeValue = true; + } + } + } + + public String[] getOrderBy() { + return this.table.orderBy(); + } + + public boolean hasDataScope() { + return this.hasDataScopeValue; + } + + public Set getJoinSql() { + return this.joinSql; + } + + public Boolean isEnbleMap() { + return this.enableTableMap != null; + } + + public String getTableNameT() { + return this.enableTableMap.name(); + } + + public String getTableNameFrom() { + if (this.isEnbleMap()){ + return this.tableName + " " + this.enableTableMap.name(); + }else{ + return this.tableName; + } + } + + public List getQueryColumnNames() { + List columns = Arrays.asList(this.table.columns()); + if (columns.size() <= 0) { + columns = this.columns.stream() + .map(ColumnInfo::getColumnName) + .collect(Collectors.toList()); + } + if (this.isEnbleMap()) { + columns = columns.stream() + .map(column -> this.getTableNameT() + "." + column) + .collect(Collectors.toList()); + this.mapColumns.stream() + .map(MapColumnInfo::getFullyQualifiedColumnName) + .forEach(columns::add); + } + + return columns; + + } + + public List getColumnNames() { + List columns = this.columns.stream() + .map(ColumnInfo::getColumnName) + .collect(Collectors.toList()); + + if (this.isEnbleMap()) { + columns = columns.stream() + .map(column -> this.getTableNameT() + "." + column) + .collect(Collectors.toList()); + this.mapColumns.stream() + .map(MapColumnInfo::getFullyQualifiedColumnName) + .forEach(columns::add); + } + + return columns; + } + + public List getColumns() { + return columns; + } + + public List getMapColumns() { + return mapColumns; + } + + public List getNotNullColumnsForQuery(T entity) { + return this.columns.stream() + .filter(column -> column.fieldQueryIsNotNull(entity)) + .collect(Collectors.toList()); + } + + public List getNotNullColumns(T entity) { + return this.columns.stream() + .filter(column -> column.fieldIsNotNull(entity)) + .collect(Collectors.toList()); + } + + public List getNotNullMapColumnsForQuery(T entity) { + return this.mapColumns.stream() + .filter(column -> column.fieldQueryIsNotNull(entity)) + .collect(Collectors.toList()); + } + + public List getNotNullMapColumns(T entity) { + return this.mapColumns.stream() + .filter(column -> column.fieldIsNotNull(entity)) + .collect(Collectors.toList()); + } + + public List getPrimaryKeys() { + return primaryKeys; + } + + public String getTableName() { + return tableName; + } + +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/enums/QueryEnum.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/enums/QueryEnum.java new file mode 100644 index 0000000..bd9c625 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/enums/QueryEnum.java @@ -0,0 +1,29 @@ +package com.ruoyi.mybatis.enums; + +/** + * 数据库查询枚举 + * + * @author Dftre + */ +public enum QueryEnum { + eq, // 等于 = + ne, // 不等于 <> + gt, // 大于 > + ge, // 大于等于 >= + lt, // 小于 < + le, // 小于等于 <= + like, // 模糊查询 + notLike, + likeLeft, // 左模糊查询 + likeRight, // 右模糊查询 + notLikeLeft, + notLikeRight, + in, // in + notIn, // not in + between, // between and + notBetween, // not between and + isNull, //is null + isNotNull, + inSql, + notInSql; +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/mapper/JPAMapper.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/mapper/JPAMapper.java new file mode 100644 index 0000000..2478e65 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/mapper/JPAMapper.java @@ -0,0 +1,30 @@ +package com.ruoyi.mybatis.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.DeleteProvider; +import org.apache.ibatis.annotations.InsertProvider; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.SelectProvider; +import org.apache.ibatis.annotations.UpdateProvider; + +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.mybatis.utils.SQLGenerator; + +public interface JPAMapper { + @SelectProvider(value = SQLGenerator.class, method = SQLGenerator.Method.SELECT_BY_ID) + public T selectById(T entity); + + @SelectProvider(value = SQLGenerator.class, method = SQLGenerator.Method.LIST) + public List selectList(T entity); + + @InsertProvider(value = SQLGenerator.class, method = SQLGenerator.Method.INSERT) + @Options(useGeneratedKeys = true) + public int insert(T entity); + + @UpdateProvider(value = SQLGenerator.class, method = SQLGenerator.Method.UPDATE) + public int update(T entity); + + @DeleteProvider(value = SQLGenerator.class, method = SQLGenerator.Method.DELETE_BY_ID) + public int deleteById(T entity); +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/service/JPAService.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/service/JPAService.java new file mode 100644 index 0000000..30309a7 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/service/JPAService.java @@ -0,0 +1,18 @@ +package com.ruoyi.mybatis.service; + +import java.util.List; + +import com.ruoyi.common.core.domain.BaseEntity; + +public interface JPAService { + + public T get(T entity); + + public List list(T entity); + + public int add(T entity); + + public int update(T entity); + + public int del(T entity); +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/service/impl/JPAServiceImpl.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/service/impl/JPAServiceImpl.java new file mode 100644 index 0000000..f9ce746 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/service/impl/JPAServiceImpl.java @@ -0,0 +1,40 @@ +package com.ruoyi.mybatis.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.mybatis.mapper.JPAMapper; +import com.ruoyi.mybatis.service.JPAService; + +public class JPAServiceImpl implements JPAService { + + @Autowired + private JPAMapper mapper; + + @Override + public T get(T entity) { + return mapper.selectById(entity); + }; + + @Override + public List list(T entity) { + return mapper.selectList(entity); + } + + @Override + public int add(T entity) { + return mapper.insert(entity); + } + + @Override + public int update(T entity) { + return mapper.update(entity); + } + + @Override + public int del(T entity) { + return mapper.deleteById(entity); + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/utils/QueryUtil.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/utils/QueryUtil.java new file mode 100644 index 0000000..8b55478 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/utils/QueryUtil.java @@ -0,0 +1,105 @@ +package com.ruoyi.mybatis.utils; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.mybatis.annotation.Query; + +public class QueryUtil { + + /** + * 判断该字段是否可以被列为查询条件 + * @param entity 参与判断的对象 + * @param field 参与判断的字段 + * @param query 参与判断的查询注解 + * @return + */ + public static boolean fieldQueryIsNotNull(Object entity, Field field, Query query) { + try { + if (query == null)return false; + BaseEntity baseEntity = (BaseEntity) entity; + Map map = baseEntity.getParams(); + return switch(query.operation()){ + case between -> map.get(query.sections()[0]) != null && map.get(query.sections()[1]) != null; + case in -> { + Object section = map.get(query.section()); + if (section == null) yield false; + if (section instanceof String) { + List list = new ArrayList<>(); + for (String split : ((String) section).split(",")) { + list.add("\"" + split + "\""); + } + map.put(query.section() + "_operation", String.join(",", list)); + yield true; + } else if (section instanceof Collection) { + List list = new ArrayList<>(); + for (Object split : ((Collection) section)) { + list.add("\"" + split.toString() + "\""); + } + map.put(query.section() + "_operation", String.join(",", list)); + yield true; + } else { + yield false; + } + } + default ->field.get(entity) != null; + }; + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to access field for building query conditions.", e); + } + } + + public static String getJoinSql(String leftTable, String rightTable, String leftField, String rightField) { + return rightTable + " on " + + leftTable + "." + leftField + " = " + + rightTable + "." + rightField; + } + + public static String getQuerySql(String column, String iField, Query query) { + if (query == null) + return ""; + String begin = "#{params." + query.sections()[0] + "}"; + String end = "#{params." + query.sections()[1] + "}"; + String inParams = "${params." + query.section() + "_operation" + "}"; + return switch (query.operation()) { + case eq -> column + " = " + iField; + case ne -> column + " <> " + iField; + case gt -> column + " > " + iField; + case ge -> column + " >= " + iField; + case le -> column + " < " + iField; + case lt -> column + " <= " + iField; + case between -> column + " between " + begin + " and " + end; + case notBetween -> column + " not between " + begin + " and " + end; + case like -> column + " like concat('%'," + iField + ",'%')"; + case notLike -> column + "not like concat('%'," + iField + ",'%')"; + case likeLeft -> column + "like concat('%'," + iField + ")"; + case likeRight -> column + "like concat(" + iField + ",'%')"; + case notLikeLeft -> column + "not like concat('%'," + iField + ")"; + case notLikeRight -> column + "not like concat(" + iField + ",'%')"; + case isNull -> column + " is null"; + case isNotNull -> column + " is not null"; + case in -> column + " in (" + inParams + ")"; + case notIn -> column + " not in (" + inParams + ")"; + case inSql -> column + " in (" + query.sql() + ")"; + case notInSql -> column + " not in (" + query.sql() + ")"; + default -> throw new IllegalArgumentException( + "Unsupported operation: " + query.operation()); + }; + } + + public static String listToInSQL(Collection oList) { + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (Serializable o : oList) { + sb.append(o).append(","); + } + sb.deleteCharAt(sb.length() - 1); + sb.append(")"); + return sb.toString(); + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/utils/QueryWrapperUtil.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/utils/QueryWrapperUtil.java new file mode 100644 index 0000000..f3c395c --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/utils/QueryWrapperUtil.java @@ -0,0 +1,84 @@ +package com.ruoyi.mybatis.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.mybatis.annotation.Query; +import com.ruoyi.mybatis.domain.TableInfo; + +public class QueryWrapperUtil { + public static List getArrayFromParam(Object obj) { + List list = new ArrayList<>(); + if (obj instanceof String) { + for (String split : ((String) obj).split(",")) { + list.add(split); + } + } else if (obj instanceof Collection) { + for (Object split : ((Collection) obj)) { + list.add(split.toString()); + } + } + return list; + } + + public static QueryWrapper initQueryWrapper(T entity) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + TableInfo tableInfo = TableContainer.getTableInfo(entity); + Map params = entity.getParams(); + + tableInfo.getNotNullColumnsForQuery(entity).stream() + .forEach(field -> { + Object fieldValue = null; + try { + fieldValue = field.getField().get(entity); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + Query queryAnnotation = field.getQuery(); + switch (queryAnnotation.operation()) { + case eq -> queryWrapper.eq(field.getUnqualifiedColumnName(), fieldValue); + case ne -> queryWrapper.ne(field.getUnqualifiedColumnName(), fieldValue); + case gt -> queryWrapper.gt(field.getUnqualifiedColumnName(), fieldValue); + case ge -> queryWrapper.ge(field.getUnqualifiedColumnName(), fieldValue); + case le -> queryWrapper.le(field.getUnqualifiedColumnName(), fieldValue); + case lt -> queryWrapper.lt(field.getUnqualifiedColumnName(), fieldValue); + case between -> { + String begin = queryAnnotation.sections()[0]; + String end = queryAnnotation.sections()[1]; + queryWrapper.between(field.getUnqualifiedColumnName(), params.get(begin), params.get(end)); + } + case notBetween -> { + String begin = queryAnnotation.sections()[0]; + String end = queryAnnotation.sections()[1]; + queryWrapper.notBetween(field.getUnqualifiedColumnName(), params.get(begin), + params.get(end)); + } + case like -> queryWrapper.like(field.getUnqualifiedColumnName(), fieldValue); + case notLike -> queryWrapper.notLike(field.getUnqualifiedColumnName(), fieldValue); + case likeLeft -> queryWrapper.likeLeft(field.getUnqualifiedColumnName(), fieldValue); + case likeRight -> queryWrapper.likeRight(field.getUnqualifiedColumnName(), fieldValue); + case notLikeLeft -> queryWrapper.notLikeLeft(field.getUnqualifiedColumnName(), fieldValue); + case notLikeRight -> queryWrapper.notLikeRight(field.getUnqualifiedColumnName(), fieldValue); + case isNull -> queryWrapper.isNull(field.getUnqualifiedColumnName()); + case isNotNull -> queryWrapper.isNotNull(field.getUnqualifiedColumnName()); + case in -> queryWrapper.in(field.getUnqualifiedColumnName(), + getArrayFromParam(params.get(queryAnnotation.section()))); + case notIn -> queryWrapper.notIn(field.getUnqualifiedColumnName(), + getArrayFromParam(params.get(queryAnnotation.section()))); + case inSql -> queryWrapper.inSql(field.getUnqualifiedColumnName(), queryAnnotation.sql()); + case notInSql -> queryWrapper.notInSql(field.getUnqualifiedColumnName(), queryAnnotation.sql()); + default -> + throw new IllegalArgumentException( + "Unsupported operation: " + queryAnnotation.operation()); + } + }); + + return queryWrapper; + } +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/utils/SQLGenerator.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/utils/SQLGenerator.java new file mode 100644 index 0000000..3873dc8 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/utils/SQLGenerator.java @@ -0,0 +1,196 @@ +package com.ruoyi.mybatis.utils; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; + +import org.apache.ibatis.jdbc.SQL; + +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.mybatis.domain.ColumnInfo; +import com.ruoyi.mybatis.domain.MapColumnInfo; +import com.ruoyi.mybatis.domain.TableInfo; + +public class SQLGenerator { + + public static class Method { + public final static String LIST = "list"; + public final static String INSERT = "insert"; + public final static String UPDATE = "update"; + public final static String SELECT_BY_ID = "selectById"; + public final static String DELETE_BY_ID = "deleteById"; + public final static String SELECT_BY_IDS = "selectByIds"; + public final static String DELETE_BY_IDS = "deleteByIds"; + public final static String SELECT_BY_ID_WITH_ENTITY = "selectByIdWithEntity"; + public final static String DELETE_BY_ID_WITH_ENTITY = "deleteByIdWithEntity"; + } + + public static String list(T entity) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(entity); + sql.SELECT(String.join(",", tableInfo.getQueryColumnNames())) + .FROM(tableInfo.getTableNameFrom()); + + if (tableInfo.isEnbleMap()) { + tableInfo.getJoinSql().stream() + .filter(StringUtils::isNotEmpty) + .forEach(sql::LEFT_OUTER_JOIN); + tableInfo.getNotNullMapColumnsForQuery(entity).stream() + .map(MapColumnInfo::getQuerySql) + .forEach(sql::WHERE); + tableInfo.getNotNullColumnsForQuery(entity).stream() + .map(ColumnInfo::getQuerySql) + .map(query -> tableInfo.getTableNameT() + "." + query) + .forEach(sql::WHERE); + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + + Arrays.stream(tableInfo.getOrderBy()) + .filter(StringUtils::isNotEmpty) + .map(order -> tableInfo.getTableNameT() + "." + order) + .forEach(sql::ORDER_BY); + } else { + tableInfo.getNotNullColumnsForQuery(entity).stream() + .map(ColumnInfo::getQuerySql) + .filter(StringUtils::isNotEmpty) + .forEach(sql::WHERE); + Arrays.stream(tableInfo.getOrderBy()) + .filter(StringUtils::isNotEmpty) + .forEach(sql::ORDER_BY); + } + return sql.toString(); + } + + public static String insert(T entity) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(entity); + sql.INSERT_INTO(tableInfo.getTableName()); + tableInfo.getNotNullColumns(entity).stream() + .forEach(column -> sql.VALUES(column.getColumnName(), column.getTemplate())); + return sql.toString(); + } + + public static String update(T entity) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(entity); + sql.UPDATE(tableInfo.getTableName()); + tableInfo.getPrimaryKeys().stream() + .map(column -> column.getColumnName() + " = " + column.getTemplate()) + .forEach(sql::WHERE); + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + tableInfo.getNotNullColumns(entity).stream() + .filter(column -> !column.isPrimaryKey()) + .map(column -> column.getColumnName() + " = " + column.getTemplate()) + .forEach(sql::SET); + return sql.toString(); + } + + public static String deleteByIdWithEntity(T entity) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(entity); + sql.DELETE_FROM(tableInfo.getTableName()); + tableInfo.getPrimaryKeys().stream() + .map(column -> column.getColumnName() + " = " + column.getTemplate()) + .forEach(sql::WHERE); + + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + return sql.toString(); + } + + public static String selectByIdWithEntity(T entity) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(entity); + sql.SELECT(String.join(",", tableInfo.getColumnNames())) + .FROM(tableInfo.getTableNameFrom()); + if (tableInfo.isEnbleMap()) { + tableInfo.getJoinSql().stream() + .forEach(sql::LEFT_OUTER_JOIN); + + tableInfo.getPrimaryKeys().stream() + .map(column -> tableInfo.getTableNameT() + "." + column.getColumnName() + " = " + + column.getTemplate()) + .forEach(sql::WHERE); + } else { + tableInfo.getPrimaryKeys().stream() + .map(column -> column.getColumnName() + " = " + column.getTemplate()) + .forEach(sql::WHERE); + } + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + return sql.toString(); + } + + public static String selectById(Serializable id, Class clz) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(clz); + sql.SELECT(String.join(",", tableInfo.getColumnNames())) + .FROM(tableInfo.getTableNameFrom()); + if (tableInfo.isEnbleMap()) { + tableInfo.getJoinSql().stream().forEach(sql::LEFT_OUTER_JOIN); + ColumnInfo columnInfo = tableInfo.getPrimaryKeys().get(0); + String columnName = tableInfo.getTableNameT() + "." + columnInfo.getColumnName(); + sql.WHERE(columnName + " = " + id); + } else { + ColumnInfo columnInfo = tableInfo.getPrimaryKeys().get(0); + sql.WHERE(columnInfo.getColumnName() + " = " + id); + } + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + return sql.toString(); + } + + public static String selectByIds(Collection ids, Class clz) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(clz); + sql.SELECT(String.join(",", tableInfo.getColumnNames())) + .FROM(tableInfo.getTableNameFrom()); + if (tableInfo.isEnbleMap()) { + tableInfo.getJoinSql().stream() + .forEach(sql::LEFT_OUTER_JOIN); + + ColumnInfo columnInfo = tableInfo.getPrimaryKeys().get(0); + String columnName = tableInfo.getTableNameT() + "." + columnInfo.getColumnName(); + sql.WHERE(columnName + " in " + QueryUtil.listToInSQL(ids)); + } else { + ColumnInfo columnInfo = tableInfo.getPrimaryKeys().get(0); + sql.WHERE(columnInfo.getColumnName() + " in " + QueryUtil.listToInSQL(ids)); + } + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + return sql.toString(); + } + + public static String deleteById(Serializable id, Class clz) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(clz); + sql.DELETE_FROM(tableInfo.getTableName()); + ColumnInfo columnInfo = tableInfo.getPrimaryKeys().get(0); + sql.WHERE(columnInfo.getColumnName() + " = " + id); + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + return sql.toString(); + } + + public static String deleteByIds(Collection ids, Class clz) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(clz); + sql.DELETE_FROM(tableInfo.getTableName()); + ColumnInfo columnInfo = tableInfo.getPrimaryKeys().get(0); + sql.WHERE(columnInfo.getColumnName() + " in " + QueryUtil.listToInSQL(ids)); + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + return sql.toString(); + } + +} diff --git a/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/utils/TableContainer.java b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/utils/TableContainer.java new file mode 100644 index 0000000..6e490ec --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-jpa/src/main/java/com/ruoyi/mybatis/utils/TableContainer.java @@ -0,0 +1,31 @@ +package com.ruoyi.mybatis.utils; + +import java.util.HashMap; +import java.util.Map; + +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.mybatis.domain.TableInfo; + +/** + * sql构建工具 + * + * @author Dftre + */ +public class TableContainer { + + private static final Map, TableInfo> tableInfoMap = new HashMap<>(); + + public static TableInfo getTableInfo(T entity) { + Class clazz = entity.getClass(); + return getTableInfo(clazz); + } + + public static TableInfo getTableInfo(Class clazz) { + TableInfo tableInfo = tableInfoMap.get(clazz); + if (tableInfo == null) { + tableInfo = new TableInfo(clazz); + } + return tableInfo; + } + +} diff --git a/ruoyi-plugins/ruoyi-mybatis-plus/pom.xml b/ruoyi-plugins/ruoyi-mybatis-plus/pom.xml new file mode 100644 index 0000000..93e03c4 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-plus/pom.xml @@ -0,0 +1,37 @@ + + + + ruoyi-plugins + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-mybatis-plus + + + ruoyi-mybatis-plus模块 + + + + + + + com.ruoyi.geekxd + ruoyi-common + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + + com.ruoyi.geekxd + ruoyi-mybatis-interceptor + + + + diff --git a/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/config/MybatisPlusConfig.java b/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/config/MybatisPlusConfig.java new file mode 100644 index 0000000..8d047b2 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/config/MybatisPlusConfig.java @@ -0,0 +1,109 @@ +package com.ruoyi.mybatisplus.config; + +import javax.sql.DataSource; + +import org.apache.ibatis.io.VFS; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.io.DefaultResourceLoader; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import com.github.pagehelper.PageInterceptor; +import com.github.pagehelper.autoconfigure.PageHelperStandardProperties; +import com.ruoyi.common.service.mybatis.CreateSqlSessionFactory; +import com.ruoyi.common.utils.MybatisUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.DataSourceManager; +import com.ruoyi.framework.mybatis.CustomDatabaseIdProvider; +import com.ruoyi.mybatisinterceptor.interceptor.DataScopeInterceptor; +/** + * Mybatis Plus 配置 + * + * @author ruoyi + */ +@Configuration +public class MybatisPlusConfig { + + @Autowired + private DataSourceManager dataSourceManager; + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 分页插件 + interceptor.addInnerInterceptor(paginationInnerInterceptor()); + // 乐观锁插件 + interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor()); + // 阻断插件 + interceptor.addInnerInterceptor(blockAttackInnerInterceptor()); + return interceptor; + } + + /** + * 分页插件,自动识别数据库类型 https://baomidou.com/guide/interceptor-pagination.html + */ + public PaginationInnerInterceptor paginationInnerInterceptor() { + PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); + // 设置数据库类型为mysql + String databaseId = dataSourceManager.getCurrentDatabaseId(); + paginationInnerInterceptor.setDbType(DbType.getDbType(databaseId)); + // 设置最大单页限制数量,默认 500 条,-1 不受限制 + paginationInnerInterceptor.setMaxLimit(-1L); + return paginationInnerInterceptor; + } + + /** + * 乐观锁插件 https://baomidou.com/guide/interceptor-optimistic-locker.html + */ + public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() { + return new OptimisticLockerInnerInterceptor(); + } + + /** + * 如果是对全表的删除或更新操作,就会终止该操作 + * https://baomidou.com/guide/interceptor-block-attack.html + */ + public BlockAttackInnerInterceptor blockAttackInnerInterceptor() { + return new BlockAttackInnerInterceptor(); + } + + @Bean + @ConditionalOnProperty(prefix = "createSqlSessionFactory", name = "use", havingValue = "mybatis-plus") + public CreateSqlSessionFactory createSqlSessionFactory(PageHelperStandardProperties packageHelperStandardProperties) { + return new CreateSqlSessionFactory() { + public SqlSessionFactory createSqlSessionFactory(Environment env, DataSource dataSource) throws Exception { + String typeAliasesPackage = env.getProperty("mybatis-plus.typeAliasesPackage"); + String mapperLocations = env.getProperty("mybatis-plus.mapperLocations"); + String configLocation = env.getProperty("mybatis-plus.configLocation"); + typeAliasesPackage = MybatisUtils.setTypeAliasesPackage(typeAliasesPackage); + VFS.addImplClass(SpringBootVFS.class); + + final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean(); + sessionFactory.setPlugins(new Interceptor[] { mybatisPlusInterceptor() }); + sessionFactory.setDataSource(dataSource); + // 设置自定义 DatabaseIdProvider:区分 openGauss 与 PostgreSQL + sessionFactory.setDatabaseIdProvider(new CustomDatabaseIdProvider()); + sessionFactory.setTypeAliasesPackage(typeAliasesPackage); + sessionFactory.setMapperLocations( + MybatisUtils.resolveMapperLocations(StringUtils.split(mapperLocations, ","))); + sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); + PageInterceptor interceptor = new PageInterceptor(); + interceptor.setProperties(packageHelperStandardProperties.getProperties()); + sessionFactory.addPlugins(interceptor,new DataScopeInterceptor()); + return sessionFactory.getObject(); + } + }; + } + +} diff --git a/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/controller/SysStudentController.java b/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/controller/SysStudentController.java new file mode 100644 index 0000000..7196c98 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/controller/SysStudentController.java @@ -0,0 +1,107 @@ +package com.ruoyi.mybatisplus.controller; + + +import java.util.Arrays; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.mybatisplus.domain.SysStudent; +import com.ruoyi.mybatisplus.service.ISysStudentService; + +/** + * 学生信息Controller + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/student") +public class SysStudentController extends BaseController +{ + @Autowired + private ISysStudentService sysStudentService; + + /** + * 查询学生信息列表 + */ + @PreAuthorize("@ss.hasPermi('system:student:list')") + @GetMapping("/list") + public TableDataInfo list(SysStudent sysStudent) + { + Page page = new Page(1,2); + List list = sysStudentService.list(page); + return getDataTable(list); + } + + /** + * 导出学生信息列表 + */ + @PreAuthorize("@ss.hasPermi('system:student:export')") + @Log(title = "学生信息", businessType = BusinessType.EXPORT) + @GetMapping("/export") + public AjaxResult export(SysStudent sysStudent) + { + List list = sysStudentService.queryList(sysStudent); + ExcelUtil util = new ExcelUtil(SysStudent.class); + return util.exportExcel(list, "student"); + } + + /** + * 获取学生信息详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:student:query')") + @GetMapping(value = "/{studentId}") + public AjaxResult getInfo(@PathVariable("studentId") Long studentId) + { + return AjaxResult.success(sysStudentService.getById(studentId)); + } + + /** + * 新增学生信息 + */ + @PreAuthorize("@ss.hasPermi('system:student:add')") + @Log(title = "学生信息", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysStudent sysStudent) + { + return toAjax(sysStudentService.save(sysStudent)); + } + + /** + * 修改学生信息 + */ + @PreAuthorize("@ss.hasPermi('system:student:edit')") + @Log(title = "学生信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysStudent sysStudent) + { + return toAjax(sysStudentService.updateById(sysStudent)); + } + + /** + * 删除学生信息 + */ + @PreAuthorize("@ss.hasPermi('system:student:remove')") + @Log(title = "学生信息", businessType = BusinessType.DELETE) + @DeleteMapping("/{studentIds}") + public AjaxResult remove(@PathVariable Long[] studentIds) + { + return toAjax(sysStudentService.removeByIds(Arrays.asList(studentIds))); + } +} \ No newline at end of file diff --git a/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/domain/SysStudent.java b/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/domain/SysStudent.java new file mode 100644 index 0000000..0fb8e2b --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/domain/SysStudent.java @@ -0,0 +1,132 @@ +package com.ruoyi.mybatisplus.domain; + +import java.io.Serializable; +import java.util.Date; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; + +/** + * 学生信息对象 sys_student + * + * @author ruoyi + */ +@TableName(value = "sys_student") +public class SysStudent implements Serializable +{ + @TableField(exist = false) + private static final long serialVersionUID = 1L; + + /** 编号 */ + @TableId(type = IdType.AUTO) + private Long studentId; + + /** 学生名称 */ + @Excel(name = "学生名称") + private String studentName; + + /** 年龄 */ + @Excel(name = "年龄") + private Integer studentAge; + + /** 爱好(0代码 1音乐 2电影) */ + @Excel(name = "爱好", readConverterExp = "0=代码,1=音乐,2=电影") + private String studentHobby; + + /** 性别(0男 1女 2未知) */ + @Excel(name = "性别", readConverterExp = "0=男,1=女,2=未知") + private String studentSex; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String studentStatus; + + /** 生日 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "生日", width = 30, dateFormat = "yyyy-MM-dd") + private Date studentBirthday; + + public void setStudentId(Long studentId) + { + this.studentId = studentId; + } + + public Long getStudentId() + { + return studentId; + } + public void setStudentName(String studentName) + { + this.studentName = studentName; + } + + public String getStudentName() + { + return studentName; + } + public void setStudentAge(Integer studentAge) + { + this.studentAge = studentAge; + } + + public Integer getStudentAge() + { + return studentAge; + } + public void setStudentHobby(String studentHobby) + { + this.studentHobby = studentHobby; + } + + public String getStudentHobby() + { + return studentHobby; + } + public void setStudentSex(String studentSex) + { + this.studentSex = studentSex; + } + + public String getStudentSex() + { + return studentSex; + } + public void setStudentStatus(String studentStatus) + { + this.studentStatus = studentStatus; + } + + public String getStudentStatus() + { + return studentStatus; + } + public void setStudentBirthday(Date studentBirthday) + { + this.studentBirthday = studentBirthday; + } + + public Date getStudentBirthday() + { + return studentBirthday; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("studentId", getStudentId()) + .append("studentName", getStudentName()) + .append("studentAge", getStudentAge()) + .append("studentHobby", getStudentHobby()) + .append("studentSex", getStudentSex()) + .append("studentStatus", getStudentStatus()) + .append("studentBirthday", getStudentBirthday()) + .toString(); + } +} \ No newline at end of file diff --git a/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/mapper/SysStudentMapper.java b/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/mapper/SysStudentMapper.java new file mode 100644 index 0000000..fdc2b1f --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/mapper/SysStudentMapper.java @@ -0,0 +1,14 @@ +package com.ruoyi.mybatisplus.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.mybatisplus.domain.SysStudent; + +/** + * 学生信息Mapper接口 + * + * @author ruoyi + */ +public interface SysStudentMapper extends BaseMapper +{ + +} \ No newline at end of file diff --git a/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/service/ISysStudentService.java b/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/service/ISysStudentService.java new file mode 100644 index 0000000..60a2625 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/service/ISysStudentService.java @@ -0,0 +1,21 @@ +package com.ruoyi.mybatisplus.service; +import java.util.List; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.ruoyi.mybatisplus.domain.SysStudent; + +/** + * 学生信息Service接口 + * + * @author ruoyi + */ +public interface ISysStudentService extends IService +{ + /** + * 查询学生信息列表 + * + * @param sysStudent 学生信息 + * @return 学生信息集合 + */ + public List queryList(SysStudent sysStudent); +} \ No newline at end of file diff --git a/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/service/impl/SysStudentServiceImpl.java b/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/service/impl/SysStudentServiceImpl.java new file mode 100644 index 0000000..f7f4c74 --- /dev/null +++ b/ruoyi-plugins/ruoyi-mybatis-plus/src/main/java/com/ruoyi/mybatisplus/service/impl/SysStudentServiceImpl.java @@ -0,0 +1,43 @@ +package com.ruoyi.mybatisplus.service.impl; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.mybatisplus.domain.SysStudent; +import com.ruoyi.mybatisplus.mapper.SysStudentMapper; +import com.ruoyi.mybatisplus.service.ISysStudentService; + +/** + * 学生信息Service业务层处理 + * + * @author ruoyi + */ +@Service +public class SysStudentServiceImpl extends ServiceImpl implements ISysStudentService +{ + @Override + public List queryList(SysStudent sysStudent) + { + // 注意:mybatis-plus lambda 模式不支持 eclipse 的编译器 + // LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + // queryWrapper.eq(SysStudent::getStudentName, sysStudent.getStudentName()); + QueryWrapper queryWrapper = Wrappers.query(); + if (StringUtils.isNotEmpty(sysStudent.getStudentName())) + { + queryWrapper.eq("student_name", sysStudent.getStudentName()); + } + if (StringUtils.isNotNull(sysStudent.getStudentAge())) + { + queryWrapper.eq("student_age", sysStudent.getStudentAge()); + } + if (StringUtils.isNotEmpty(sysStudent.getStudentHobby())) + { + queryWrapper.eq("student_hobby", sysStudent.getStudentHobby()); + } + return this.list(queryWrapper); + } +} \ No newline at end of file diff --git a/ruoyi-plugins/ruoyi-netty/pom.xml b/ruoyi-plugins/ruoyi-netty/pom.xml new file mode 100644 index 0000000..fae3937 --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/pom.xml @@ -0,0 +1,30 @@ + + + + ruoyi-plugins + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-netty + + + 19 + 19 + UTF-8 + + + + + io.netty + netty-all + + + com.ruoyi.geekxd + ruoyi-common + + + diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/NettyServerRunner.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/NettyServerRunner.java new file mode 100644 index 0000000..626850e --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/NettyServerRunner.java @@ -0,0 +1,28 @@ +package com.ruoyi.netty.websocket; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +import com.ruoyi.netty.websocket.nettyServer.NettyWebSocketServer; + +import jakarta.annotation.PreDestroy; + +@Component +public class NettyServerRunner implements ApplicationRunner { + + @Autowired + private NettyWebSocketServer server; + + @Override + public void run(ApplicationArguments args) throws Exception { + server.start(); + } + + @PreDestroy + public void destroy() { + server.shutdown(); + } + +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/annotations/NettyWebSocketEndpoint.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/annotations/NettyWebSocketEndpoint.java new file mode 100644 index 0000000..15f64ae --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/annotations/NettyWebSocketEndpoint.java @@ -0,0 +1,12 @@ +package com.ruoyi.netty.websocket.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface NettyWebSocketEndpoint { + String path(); +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/endpoints/TcpLongConnectionServer.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/endpoints/TcpLongConnectionServer.java new file mode 100644 index 0000000..46488f4 --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/endpoints/TcpLongConnectionServer.java @@ -0,0 +1,229 @@ +package com.ruoyi.netty.websocket.endpoints; + +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.DelimiterBasedFrameDecoder; +import io.netty.handler.codec.Delimiters; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import io.netty.handler.timeout.IdleStateHandler; +import io.netty.util.CharsetUtil; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; + +/** + * 一个简单的 TCP 长连接服务示例:\n + * 协议约定:\n + * 1. 客户端连接后首先发送认证报文:AUTH:clientId (以换行结尾);认证成功后可正常收发。\n + * 2. 心跳:客户端定期发送 PING,服务端回复 PONG。\n + * 3. 其它普通文本消息,服务端原样回显(ECHO)。\n + * 4. 服务器可通过静态方法 sendToClient / broadcast 发送消息。\n + * 行分隔符使用标准的换行符(\n 或 \r\n),通过 DelimiterBasedFrameDecoder 解析。\n + */ +@Component +public class TcpLongConnectionServer { + + private static final Logger log = LoggerFactory.getLogger(TcpLongConnectionServer.class); + + /** 是否启用 */ + @Value("${netty.tcp.enable:true}") + private boolean enable; + + /** 监听端口 */ + @Value("${netty.tcp.port:18888}") + private int port; + + /** boss 线程数 */ + @Value("${netty.tcp.bossThreads:1}") + private int bossThreads; + + /** worker 线程数 */ + @Value("${netty.tcp.workerThreads:4}") + private int workerThreads; + + /** 读空闲(秒)。超过则主动关闭 */ + @Value("${netty.tcp.readerIdleSeconds:120}") + private int readerIdleSeconds; + + private EventLoopGroup bossGroup; + private EventLoopGroup workerGroup; + private Channel serverChannel; + + /** clientId -> Channel */ + private static final Map CLIENTS = new ConcurrentHashMap<>(); + + /** ChannelId -> clientId */ + private static final Map CHANNEL_BINDINGS = new ConcurrentHashMap<>(); + + @PostConstruct + public void start() throws InterruptedException { + if (!enable) { + log.info("TCP LongConnection disabled (netty.tcp.enable=false)"); + return; + } + bossGroup = new NioEventLoopGroup(bossThreads); + workerGroup = new NioEventLoopGroup(workerThreads); + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childOption(ChannelOption.SO_KEEPALIVE, true) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ChannelPipeline p = ch.pipeline(); + // 基于换行分割帧,防止拆包/粘包(最大 8K 一行) + p.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); + p.addLast(new StringDecoder(CharsetUtil.UTF_8)); + p.addLast(new StringEncoder(CharsetUtil.UTF_8)); + p.addLast(new IdleStateHandler(readerIdleSeconds, 0, 0)); + p.addLast(new TcpLongConnectionHandler()); + } + }); + ChannelFuture future = bootstrap.bind(new InetSocketAddress(port)).sync(); + serverChannel = future.channel(); + log.info("TCP LongConnection Server started at port {}", port); + } + + @PreDestroy + public void stop() { + try { + if (serverChannel != null) { + serverChannel.close().syncUninterruptibly(); + } + } finally { + if (bossGroup != null) { + bossGroup.shutdownGracefully(); + } + if (workerGroup != null) { + workerGroup.shutdownGracefully(); + } + log.info("TCP LongConnection Server stopped"); + } + } + + /** 向指定 client 发送 */ + public static boolean sendToClient(String clientId, String msg) { + Channel ch = CLIENTS.get(clientId); + if (ch == null || !ch.isActive()) { + return false; + } + ch.writeAndFlush(msg + "\n"); + return true; + } + + /** 广播 */ + public static int broadcast(String msg) { + int count = 0; + for (Channel ch : CLIENTS.values()) { + if (ch.isActive()) { + ch.writeAndFlush(msg + "\n"); + count++; + } + } + return count; + } + + /** 获取当前在线数量 */ + public static int onlineSize() { + return CLIENTS.size(); + } + + /** Netty 处理器 */ + private static class TcpLongConnectionHandler extends SimpleChannelInboundHandler { + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + unbind(ctx); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, String msg) { + String trim = msg.trim(); + if (trim.isEmpty()) { + return; + } + // 认证 + if (trim.startsWith("AUTH:")) { + String clientId = trim.substring(5).trim(); + if (clientId.isEmpty()) { + ctx.writeAndFlush("ERR:EMPTY_CLIENT_ID\n"); + return; + } + bind(clientId, ctx); + ctx.writeAndFlush("AUTH_OK\n"); + return; + } + // 心跳 + if ("PING".equalsIgnoreCase(trim)) { + ctx.writeAndFlush("PONG\n"); + return; + } + // 未认证直接断开 + if (!CHANNEL_BINDINGS.containsKey(ctx.channel().id().asShortText())) { + ctx.writeAndFlush("ERR:UNAUTH\n").addListener(ChannelFutureListener.CLOSE); + return; + } + // 普通消息:回显 + ctx.writeAndFlush("ECHO:" + trim + "\n"); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof IdleStateEvent idle) { + if (idle.state() == IdleState.READER_IDLE) { + log.debug("Close idle connection: {}", ctx.channel().remoteAddress()); + ctx.writeAndFlush("ERR:IDLE_TIMEOUT\n").addListener(ChannelFutureListener.CLOSE); + } + } else { + super.userEventTriggered(ctx, evt); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + log.debug("TCP connection error: {}", cause.getMessage()); + ctx.close(); + } + + private void bind(String clientId, ChannelHandlerContext ctx) { + // 如果已存在旧连接,关闭旧连接 + Channel old = CLIENTS.put(clientId, ctx.channel()); + if (old != null && old != ctx.channel()) { + old.writeAndFlush("ERR:KICKED\n").addListener(ChannelFutureListener.CLOSE); + } + CHANNEL_BINDINGS.put(ctx.channel().id().asShortText(), clientId); + log.info("Client authenticated, clientId={}, remote={}", clientId, ctx.channel().remoteAddress()); + } + + private void unbind(ChannelHandlerContext ctx) { + String channelId = ctx.channel().id().asShortText(); + String clientId = CHANNEL_BINDINGS.remove(channelId); + if (clientId != null) { + CLIENTS.remove(clientId, ctx.channel()); + log.info("Client disconnected, clientId={}, remote={}", clientId, ctx.channel().remoteAddress()); + } + } + } +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/endpoints/TestNettyWebSocket.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/endpoints/TestNettyWebSocket.java new file mode 100644 index 0000000..3e827f6 --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/endpoints/TestNettyWebSocket.java @@ -0,0 +1,44 @@ +package com.ruoyi.netty.websocket.endpoints; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.stereotype.Component; + +import com.ruoyi.netty.websocket.annotations.NettyWebSocketEndpoint; +import com.ruoyi.netty.websocket.nettyServer.NettyWebSocketEndpointHandler; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpMessage; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; + +@Component +@NettyWebSocketEndpoint(path = "/test/{seqNumber}") +public class TestNettyWebSocket extends NettyWebSocketEndpointHandler { + + private static final Map map = new ConcurrentHashMap<>(); + + @Override + public void onMessage(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) { + System.out.println(textWebSocketFrame.text()); + } + + @Override + public void onOpen(ChannelHandlerContext channelHandlerContext, FullHttpMessage fullHttpMessage) { + map.put(getPathParam("seqNumber"), channelHandlerContext); + } + + @Override + public void onClose(ChannelHandlerContext channelHandlerContext) { + map.remove(getPathParam("seqNumber")); + } + + @Override + public void onError(ChannelHandlerContext channelHandlerContext, Throwable throwable) { + + } + + public static void send(String seqNumber, String msg) { + sendMsg(map.get(seqNumber), msg); + } +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/NettyWebSocketEndpointHandler.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/NettyWebSocketEndpointHandler.java new file mode 100644 index 0000000..0c26469 --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/NettyWebSocketEndpointHandler.java @@ -0,0 +1,40 @@ +package com.ruoyi.netty.websocket.nettyServer; + +import java.util.Map; + +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpMessage; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public abstract class NettyWebSocketEndpointHandler { + + private Map pathParam; + + private Map urlParam; + + public static void sendMsg(ChannelHandlerContext context, String msg) { + TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame(msg); + context.channel().writeAndFlush(textWebSocketFrame); + } + + public abstract void onMessage(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame); + + public abstract void onOpen(ChannelHandlerContext channelHandlerContext, FullHttpMessage fullHttpMessage); + + public abstract void onClose(ChannelHandlerContext channelHandlerContext); + + public abstract void onError(ChannelHandlerContext channelHandlerContext, Throwable throwable); + + public String getPathParam(String key) { + return pathParam.get(key); + } + + public void closeChannel(ChannelHandlerContext channelHandlerContext) { + channelHandlerContext.close().addListener(ChannelFutureListener.CLOSE); + } +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/NettyWebSocketServer.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/NettyWebSocketServer.java new file mode 100644 index 0000000..db276fd --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/NettyWebSocketServer.java @@ -0,0 +1,89 @@ +package com.ruoyi.netty.websocket.nettyServer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.ruoyi.netty.websocket.nettyServer.handler.WebSocketHandler; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; + +@Component +public class NettyWebSocketServer { + + private static final Logger log = LoggerFactory.getLogger(NettyWebSocketServer.class); + private ServerBootstrap serverBootstrap; + private Channel serverChannel; + + @Value("${netty.websocket.maxMessageSize}") + private Long messageSize; + + @Value("${netty.websocket.bossThreads}") + private Long bossThreads; + + @Value("${netty.websocket.workerThreads}") + private Long workerThreads; + + @Value("${netty.websocket.port}") + private Long port; + + @Value("${netty.websocket.enable}") + private Boolean enable; + + public ServerBootstrap start() throws InterruptedException { + if (!enable) { + return null; + } + ServerBootstrap serverBootstrap = new ServerBootstrap(); + NioEventLoopGroup boss = new NioEventLoopGroup(4); + NioEventLoopGroup worker = new NioEventLoopGroup(workerThreads.intValue()); + serverBootstrap.group(boss, worker); + serverBootstrap.channel(NioServerSocketChannel.class); + serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); + serverBootstrap.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel channel) throws Exception { + ChannelPipeline pipeline = channel.pipeline(); + pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new HttpObjectAggregator(messageSize.intValue())); + pipeline.addLast(new WebSocketHandler()); + pipeline.addLast(new WebSocketServerProtocolHandler("/", true)); + } + }); + ChannelFuture future = serverBootstrap.bind(port.intValue()).sync(); + serverChannel = future.channel(); + log.info("netty for websocket start success, running in port: {}", this.port); + this.serverBootstrap = serverBootstrap; + return this.serverBootstrap; + } + + public void shutdown() { + if (serverChannel != null) { + serverChannel.close().syncUninterruptibly(); + } + if (serverBootstrap != null) { + EventLoopGroup bossGroup = serverBootstrap.config().group(); + EventLoopGroup workerGroup = serverBootstrap.config().childGroup(); + if (bossGroup != null) { + bossGroup.shutdownGracefully().syncUninterruptibly(); + } + if (workerGroup != null) { + workerGroup.shutdownGracefully().syncUninterruptibly(); + } + } + log.info("netty for websocket shudown success"); + } +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/handler/WebSocketHandler.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/handler/WebSocketHandler.java new file mode 100644 index 0000000..d52701a --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/nettyServer/handler/WebSocketHandler.java @@ -0,0 +1,169 @@ +package com.ruoyi.netty.websocket.nettyServer.handler; + +import java.lang.reflect.Constructor; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.netty.websocket.annotations.NettyWebSocketEndpoint; +import com.ruoyi.netty.websocket.nettyServer.NettyWebSocketEndpointHandler; +import com.ruoyi.netty.websocket.utils.CommonUtil; + +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelId; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.netty.util.concurrent.GlobalEventExecutor; +import jakarta.annotation.PostConstruct; + +@Component +public class WebSocketHandler extends SimpleChannelInboundHandler { + + @Autowired + private List handlers; + + private static final Map uriHandlerMapper = new ConcurrentHashMap<>(); + + public static final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + public static final Map channelHandlerMap = new ConcurrentHashMap<>(); + + @PostConstruct + private void init() throws URISyntaxException, NoSuchMethodException, SecurityException { + for (NettyWebSocketEndpointHandler handler : handlers) { + Class handlerClass = handler.getClass(); + NettyWebSocketEndpoint annotation = handlerClass.getAnnotation(NettyWebSocketEndpoint.class); + if (annotation == null || StringUtils.isEmpty(annotation.path())) { + throw new RuntimeException("未配置路径的 netty websocket endpoint "); + } + PathMatchModel pathMachModel = parseHandler(annotation.path(), handlerClass); + uriHandlerMapper.put(pathMachModel.path, pathMachModel); + } + } + + @Override + protected void channelRead0(ChannelHandlerContext context, TextWebSocketFrame webSocketFrame) throws Exception { + NettyWebSocketEndpointHandler handler = channelHandlerMap.get(context.channel().id()); + handler.onMessage(context, webSocketFrame); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + channelGroup.add(ctx.channel()); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + NettyWebSocketEndpointHandler handler = channelHandlerMap.get(ctx.channel().id()); + if (handler != null) { + handler.onClose(ctx); + } + channelHandlerMap.remove(ctx.channel().id()); + channelGroup.remove(ctx.channel()); + } + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + channelHandlerMap.get(ctx.channel().id()).onError(ctx, cause); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof FullHttpRequest) { + FullHttpRequest fullHttpRequest = (FullHttpRequest) msg; + if (channelHandlerMap.get(ctx.channel().id()) != null) { + super.channelRead(ctx, fullHttpRequest); + return; + } + URI uri = new URI(fullHttpRequest.uri()); + PathMatchModel mathPathMachModel = mathPathMachModel(uri.getPath()); + if (mathPathMachModel == null) { + ctx.channel() + .writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND)); + ctx.close().addListener(ChannelFutureListener.CLOSE); + return; + } + Constructor constructor = mathPathMachModel.handlerConstructor; + NettyWebSocketEndpointHandler newInstance = (NettyWebSocketEndpointHandler) constructor.newInstance(); + if (!(mathPathMachModel.pathParams == null || mathPathMachModel.pathParams.isEmpty())) { + newInstance.setPathParam( + CommonUtil.parsePathParam(uri.getPath(), mathPathMachModel.pathParams, mathPathMachModel.path)); + super.channelRead(ctx, msg); + } + newInstance.setUrlParam(CommonUtil.parseQueryParameters(uri.getQuery())); + channelHandlerMap.put(ctx.channel().id(), newInstance); + newInstance.onOpen(ctx, fullHttpRequest); + } else if (msg instanceof TextWebSocketFrame) { + super.channelRead(ctx, msg); + } + + } + + private static PathMatchModel parseHandler(String path, Class handlerClass) + throws NoSuchMethodException, SecurityException { + List paramName = new ArrayList<>(); + String[] split = path.split("/"); + for (int index = 1; index < split.length; index++) { + String item = split[index]; + if (item.startsWith("{") && item.endsWith("}")) { + paramName.add(item.substring(1, item.length() - 1).trim()); + split[index] = "?"; + } + } + StringBuilder finalPath = new StringBuilder(""); + for (int index = 1; index < split.length; index++) { + finalPath.append("/").append(split[index]); + } + return new PathMatchModel(paramName, finalPath.toString(), handlerClass.getDeclaredConstructor()); + } + + private static PathMatchModel mathPathMachModel(String uri) { + Map map = new HashMap<>(); + for (String key : uriHandlerMapper.keySet()) { + int mathUri = CommonUtil.mathUri(uri, key); + if (mathUri > 0) { + map.put(mathUri, uriHandlerMapper.get(key)); + } + } + if (map.keySet() == null || map.keySet().isEmpty()) { + return null; + } + Integer max = CommonUtil.getMax(map.keySet()); + return map.get(max); + } + + private static final class PathMatchModel { + private final List pathParams; + + private final String path; + + private final Constructor handlerConstructor; + + public PathMatchModel(List pathParams, String path, Constructor handlerConstructor) { + this.pathParams = pathParams; + this.path = path; + this.handlerConstructor = handlerConstructor; + } + + } +} diff --git a/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/utils/CommonUtil.java b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/utils/CommonUtil.java new file mode 100644 index 0000000..9c029db --- /dev/null +++ b/ruoyi-plugins/ruoyi-netty/src/main/java/com/ruoyi/netty/websocket/utils/CommonUtil.java @@ -0,0 +1,78 @@ +package com.ruoyi.netty.websocket.utils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class CommonUtil { + + /** + * @param uri + * @param uriTemplates + * @return + */ + public static int mathUri(String uri, String uriTemplate) { + String[] uriSplit = uri.split("/"); + String[] tempalteSplit = uriTemplate.split("/"); + if (uriSplit.length != tempalteSplit.length) { + return -1; + } + int mathLevel = 0; + for (int index = 1; index < tempalteSplit.length; index++) { + if (tempalteSplit[index].equals("?")) { + mathLevel = mathLevel + index; + continue; + } + if (!tempalteSplit[index].equals(uriSplit[index])) { + return -1; + } else { + mathLevel = mathLevel + tempalteSplit.length + 1; + } + } + return mathLevel; + } + + public static Map parseQueryParameters(String query) { + if (query == null || query.isEmpty()) { + return Map.of(); + } + + Map params = new HashMap<>(); + String[] pairs = query.split("&"); + for (String pair : pairs) { + String[] keyValue = pair.split("="); + if (keyValue.length > 1) { + params.put(keyValue[0], keyValue[1]); + } else { + params.put(keyValue[0], ""); + } + } + return params; + } + + public static Map parsePathParam(String uri, List pathParams, String uriTemplate) { + int index = 0; + String[] split = uriTemplate.split("/"); + String[] split2 = uri.split("/"); + Map map = new HashMap<>(); + for (int i = 1; i < split.length; i++) { + if (split[i].equals("?")) { + map.put(pathParams.get(index), split2[i]); + index++; + } + } + return map; + } + + public static Integer getMax(Set set) { + Optional maxNumber = set.stream().max(Integer::compare); + if (maxNumber.isPresent()) { + System.out.println("Max number: " + maxNumber.get()); + } else { + System.out.println("The list is empty"); + } + return maxNumber.get(); + } +} diff --git a/ruoyi-plugins/ruoyi-plugins-starter/pom.xml b/ruoyi-plugins/ruoyi-plugins-starter/pom.xml new file mode 100644 index 0000000..00f4ed9 --- /dev/null +++ b/ruoyi-plugins/ruoyi-plugins-starter/pom.xml @@ -0,0 +1,70 @@ + + + + ruoyi-plugins + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-plugins-starter + + + 中间件 + + + + + + com.ruoyi.geekxd + ruoyi-cache-ehcache + + + + + com.ruoyi.geekxd + ruoyi-cache-redis + + + + + com.ruoyi.geekxd + ruoyi-websocket + + + + + com.ruoyi.geekxd + ruoyi-mybatis-jpa + + + + + com.ruoyi.geekxd + ruoyi-mybatis-plus + + + + com.ruoyi.geekxd + ruoyi-mybatis-interceptor + + + + com.ruoyi.geekxd + ruoyi-netty + + + + com.ruoyi.geekxd + ruoyi-rabbitmq + + + + + + + diff --git a/ruoyi-plugins/ruoyi-rabbitmq/pom.xml b/ruoyi-plugins/ruoyi-rabbitmq/pom.xml new file mode 100644 index 0000000..8a5b2f8 --- /dev/null +++ b/ruoyi-plugins/ruoyi-rabbitmq/pom.xml @@ -0,0 +1,33 @@ + + + + ruoyi-plugins + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-rabbitmq + + + 中间件 + + + + + + + com.ruoyi.geekxd + ruoyi-common + + + + + org.springframework.boot + spring-boot-starter-amqp + + + + + diff --git a/ruoyi-plugins/ruoyi-rabbitmq/src/main/java/com/ruoyi/middleware/rabbitmq/config/DirectRabbitConfig.java b/ruoyi-plugins/ruoyi-rabbitmq/src/main/java/com/ruoyi/middleware/rabbitmq/config/DirectRabbitConfig.java new file mode 100644 index 0000000..ae47451 --- /dev/null +++ b/ruoyi-plugins/ruoyi-rabbitmq/src/main/java/com/ruoyi/middleware/rabbitmq/config/DirectRabbitConfig.java @@ -0,0 +1,65 @@ +package com.ruoyi.middleware.rabbitmq.config; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.DirectExchange; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar; +import org.springframework.amqp.support.converter.DefaultClassMapper; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; +import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory; + +/** + * @Author : JCccc + * @CreateTime : 2019/9/3 + * @Description : + **/ +@Configuration +public class DirectRabbitConfig implements RabbitListenerConfigurer { + + @Autowired + public ConnectionFactory connectionFactory; + + @Bean + public Queue TestDirectQueue() { + return new Queue("TestDirectQueue", true); + } + + @Bean + DirectExchange TestDirectExchange() { + return new DirectExchange("TestDirectExchange", true, false); + } + + @Bean + Binding bindingDirect() { + return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting"); + } + + @Bean + public MessageConverter jsonToMapMessageConverter() { + DefaultClassMapper defaultClassMapper = new DefaultClassMapper(); + defaultClassMapper.setTrustedPackages("*"); + Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter(); + jackson2JsonMessageConverter.setClassMapper(defaultClassMapper); + return jackson2JsonMessageConverter; + } + + @Bean + public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() { + DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); + factory.setMessageConverter(new MappingJackson2MessageConverter()); + return factory; + } + + @Override + public void configureRabbitListeners(RabbitListenerEndpointRegistrar rabbitListenerEndpointRegistrar) { + rabbitListenerEndpointRegistrar.setMessageHandlerMethodFactory(myHandlerMethodFactory()); + } +} \ No newline at end of file diff --git a/ruoyi-plugins/ruoyi-rabbitmq/src/main/java/com/ruoyi/middleware/rabbitmq/controller/SendMessageController.java b/ruoyi-plugins/ruoyi-rabbitmq/src/main/java/com/ruoyi/middleware/rabbitmq/controller/SendMessageController.java new file mode 100644 index 0000000..f099828 --- /dev/null +++ b/ruoyi-plugins/ruoyi-rabbitmq/src/main/java/com/ruoyi/middleware/rabbitmq/controller/SendMessageController.java @@ -0,0 +1,38 @@ +package com.ruoyi.middleware.rabbitmq.controller; + +import java.util.Map; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.core.domain.Message; + +/** + * @Author : JCccc + * @CreateTime : 2019/9/3 + * @Description : + **/ +@RestController +@RequestMapping("/rabbitmq") +public class SendMessageController { + + @Autowired + RabbitTemplate rabbitTemplate; // 使用RabbitTemplate,这提供了接收/发送等等方法 + + @Anonymous + @GetMapping("/sendDirectMessage") + public String sendDirectMessage() { + // 将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange + rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", + Message.create() + .setPayload(Map.of("message", "你好")) + .setReceiver("接收者") + .setSender("发送者")); + return "ok"; + } + +} \ No newline at end of file diff --git a/ruoyi-plugins/ruoyi-rabbitmq/src/main/java/com/ruoyi/middleware/rabbitmq/listener/RabbitMessageListener.java b/ruoyi-plugins/ruoyi-rabbitmq/src/main/java/com/ruoyi/middleware/rabbitmq/listener/RabbitMessageListener.java new file mode 100644 index 0000000..894ff6d --- /dev/null +++ b/ruoyi-plugins/ruoyi-rabbitmq/src/main/java/com/ruoyi/middleware/rabbitmq/listener/RabbitMessageListener.java @@ -0,0 +1,19 @@ +package com.ruoyi.middleware.rabbitmq.listener; + +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.core.domain.Message; + +@Component +@RabbitListener(queues = "TestDirectQueue") // 监听的队列名称 TestDirectQueue +@ConditionalOnProperty(prefix = "spring.rabbitmq", name = { "enable" }, havingValue = "true", matchIfMissing = false) +public class RabbitMessageListener { + + @RabbitHandler + public void process(Message map) { + System.out.println("DirectReceiver m消费者收到消息 : " + map.toString()); + } +} \ No newline at end of file diff --git a/ruoyi-plugins/ruoyi-websocket/pom.xml b/ruoyi-plugins/ruoyi-websocket/pom.xml new file mode 100644 index 0000000..610d740 --- /dev/null +++ b/ruoyi-plugins/ruoyi-websocket/pom.xml @@ -0,0 +1,31 @@ + + + + ruoyi-plugins + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-websocket + + + websocket系统模块 + + + + + com.ruoyi.geekxd + ruoyi-framework + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + diff --git a/ruoyi-plugins/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketConfig.java b/ruoyi-plugins/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketConfig.java new file mode 100644 index 0000000..9324417 --- /dev/null +++ b/ruoyi-plugins/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketConfig.java @@ -0,0 +1,29 @@ +package com.ruoyi.websocket; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +/** + * websocket 配置 + * + * @author ruoyi + */ +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + @Autowired + private WebSocketServer webSocketServer; + + @Autowired + private WebSocketInterceptor webSocketInterceptor; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(webSocketServer, "/websocket/message") + .addInterceptors(webSocketInterceptor) + .setAllowedOrigins("*"); // 允许跨域,生产环境建议配置具体域名 + } +} diff --git a/ruoyi-plugins/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketInterceptor.java b/ruoyi-plugins/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketInterceptor.java new file mode 100644 index 0000000..06cf624 --- /dev/null +++ b/ruoyi-plugins/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketInterceptor.java @@ -0,0 +1,95 @@ +package com.ruoyi.websocket; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.TokenService; + +/** + * WebSocket 握手拦截器 + * 用于处理连接时的 authorization 参数 + * + * @author ruoyi + */ +@Component +public class WebSocketInterceptor implements HandshakeInterceptor { + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketInterceptor.class); + + @Value("${token.header}") + private String header; + + @Autowired + private TokenService tokenService; + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler wsHandler, Map attributes) throws Exception { + // 获取请求参数 + String query = request.getURI().getQuery(); + if (query != null) { + String[] params = query.split("&"); + for (String param : params) { + String[] keyValue = param.split("="); + if (keyValue.length == 2) { + attributes.put(keyValue[0], keyValue[1]); + } + } + } + + String token = (String) attributes.get(header); + // 获取 Sec-WebSocket-Protocol 头部信息(前端通过第二个参数传递的 authorization) + List protocols = request.getHeaders().get("Sec-WebSocket-Protocol"); + if (token == null && protocols != null && !protocols.isEmpty()) { + token = protocols.get(0); + LOGGER.info("WebSocket 连接携带 authorization: {}", token); + } + + attributes.put(header, token); + if (!validateToken(token)) { + LOGGER.warn("WebSocket 连接认证失败: {}", token); + return false; + } else { + LOGGER.info("WebSocket 握手成功,远程地址: {}", request.getRemoteAddress()); + return true; + } + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler wsHandler, Exception exception) { + if (exception != null) { + LOGGER.error("WebSocket 握手后异常", exception); + } + } + + /** + * 验证token(示例方法,可根据实际需求实现) + * + * @param token 认证token + * @return 验证结果 + */ + private boolean validateToken(String token) { + // 这里可以添加实际的token验证逻辑 + // 例如:解析JWT、查询数据库等 + LoginUser loginUser = tokenService.getLoginUser(token); + if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) { + tokenService.verifyToken(loginUser); + return true; + } else { + return false; + } + } +} diff --git a/ruoyi-plugins/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketServer.java b/ruoyi-plugins/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketServer.java new file mode 100644 index 0000000..3b5adbf --- /dev/null +++ b/ruoyi-plugins/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketServer.java @@ -0,0 +1,124 @@ +package com.ruoyi.websocket; + +import java.util.concurrent.Semaphore; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.core.domain.Message; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.MessageType; +import com.ruoyi.framework.web.service.TokenService; + +/** + * websocket 消息处理 + * + * @author ruoyi + */ +@Component +public class WebSocketServer extends TextWebSocketHandler { + + @Autowired + private TokenService tokenService; + + @Value("${token.header}") + private String header; + /** + * WebSocketServer 日志控制器 + */ + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class); + + /** + * 默认最多允许同时在线人数100 + */ + public static int socketMaxOnlineCount = 100; + + private static Semaphore socketSemaphore = new Semaphore(socketMaxOnlineCount); + + /** + * 连接建立成功调用的方法 + */ + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + boolean semaphoreFlag = false; + // 尝试获取信号量 + semaphoreFlag = socketSemaphore.tryAcquire(); + Message message = new Message(); + message.setSender("system"); + if (!semaphoreFlag) { + // 未获取到信号量 + LOGGER.error("\n 当前在线人数超过限制数- {}", socketMaxOnlineCount); + message.setContent("当前在线人数超过限制数:" + socketMaxOnlineCount); + WebSocketUsers.sendMessageToUser(session, message); + session.close(); + } else { + // 获取 authorization 信息 + String authorization = (String) session.getAttributes().get(header); + LoginUser loginUser = tokenService.getLoginUser(authorization); + session.getAttributes().put("USER", loginUser); + // 添加用户 + WebSocketUsers.put(session.getId(), session, loginUser); + LOGGER.info("\n 建立连接 - {}", session.getId()); + LOGGER.info("\n 当前人数 - {}", WebSocketUsers.getUsers().size()); + message.setContent("连接成功,你好" + loginUser.getUsername()); + WebSocketUsers.sendMessageToUser(session, message); + } + } + + /** + * 连接关闭时处理 + */ + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + LOGGER.info("\n 关闭连接 - {}, 状态: {}", session.getId(), status); + // 移除用户 + WebSocketUsers.remove(session.getId()); + // 获取到信号量则需释放 + socketSemaphore.release(); + } + + /** + * 抛出异常时处理 + */ + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { + if (session.isOpen()) { + // 关闭连接 + session.close(); + } + String sessionId = session.getId(); + LOGGER.info("\n 连接异常 - {}", sessionId); + LOGGER.info("\n 异常信息 - {}", exception); + // 移出用户 + WebSocketUsers.remove(sessionId); + // 获取到信号量则需释放 + socketSemaphore.release(); + } + + /** + * 服务器接收到客户端消息时调用的方法 + */ + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + String payload = message.getPayload(); + Message msg = JSONObject.parseObject(payload, Message.class); + WebSocketSession receiver = WebSocketUsers.USERNAME.get(msg.getReceiver()); + if (msg.getType().equals(MessageType.ASYNC_MESSAGE)) { + WebSocketUsers.sendMessageToUser(session, msg); + } else { + if (receiver == null) { + LOGGER.error("\n 无法找到接收者 - {}", msg.getReceiver()); + return; + } + WebSocketUsers.sendMessageToUser(receiver, msg); + } + } +} diff --git a/ruoyi-plugins/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketUsers.java b/ruoyi-plugins/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketUsers.java new file mode 100644 index 0000000..e7a5e48 --- /dev/null +++ b/ruoyi-plugins/ruoyi-websocket/src/main/java/com/ruoyi/websocket/WebSocketUsers.java @@ -0,0 +1,124 @@ +package com.ruoyi.websocket; + +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.core.domain.Message; +import com.ruoyi.common.core.domain.model.LoginUser; + +/** + * websocket 客户端用户集 + * + * @author ruoyi + */ +public class WebSocketUsers { + /** + * WebSocketUsers 日志控制器 + */ + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class); + + /** + * session集 + */ + public static final Map SESSIONS = new ConcurrentHashMap<>(); + public static final Map USERNAME = new ConcurrentHashMap<>(); + public static final Map LOGINUSER = new ConcurrentHashMap<>(); + + /** + * 存储用户 + * + * @param key 唯一键 + * @param session 用户信息 + */ + public static void put(String key, WebSocketSession session, LoginUser loginUser) { + SESSIONS.put(key, session); + if (loginUser != null) { + LOGINUSER.put(key, loginUser); + USERNAME.put(loginUser.getUsername(), session); + } + } + + /** + * 移出用户 + * + * @param key 键 + */ + public static boolean remove(String key) { + LOGGER.info("\n 正在移出用户 - {}", key); + SESSIONS.remove(key); + LoginUser loginUser = LOGINUSER.remove(key); + if (loginUser != null) { + USERNAME.remove(loginUser.getUsername()); + } + return true; + } + + /** + * 获取在线用户列表 + * + * @return 返回用户集合 + */ + public static Collection getUsers() { + return LOGINUSER.values(); + } + + public static Map getSessions() { + return SESSIONS; + } + + /** + * 群发消息文本消息 + * + * @param message 消息内容 + */ + public static void sendMessageToUsersByText(String message) { + Collection values = SESSIONS.values(); + for (WebSocketSession value : values) { + sendMessageToUserByText(value, message); + } + } + + /** + * 发送文本消息 + * + * @param session WebSocket会话 + * @param message 消息内容 + */ + public static void sendMessageToUserByText(WebSocketSession session, String message) { + if (session != null && session.isOpen()) { + try { + session.sendMessage(new TextMessage(message)); + } catch (IOException e) { + LOGGER.error("\n[发送消息异常]", e); + } + } else { + LOGGER.info("\n[连接已断开或不存在]"); + } + } + + /** + * 发送消息 + * + * @param session WebSocket会话 + * @param message 消息内容 + */ + public static void sendMessageToUser(WebSocketSession session, Message message) { + if (session != null && session.isOpen()) { + try { + session.sendMessage(new TextMessage(JSONObject.toJSONString(message))); + } catch (IOException e) { + LOGGER.error("\n[发送消息异常]", e); + } + } else { + LOGGER.info("\n[连接已断开或不存在]"); + } + } +} diff --git a/ruoyi-scene-auth/pom.xml b/ruoyi-scene-auth/pom.xml new file mode 100644 index 0000000..aa889cd --- /dev/null +++ b/ruoyi-scene-auth/pom.xml @@ -0,0 +1,118 @@ + + + + ruoyi + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-scene-auth + + + 1.16.7 + 3.7.4.ALL + 3.1.1 + 2.0.1 + + + + 第三方认证模块 + + + + + + com.ruoyi.geekxd + ruoyi-auth-common + ${ruoyi.version} + + + + jakarta.mail + jakarta.mail-api + ${mail.version} + + + + com.sun.mail + jakarta.mail + ${mail.version} + + + + + me.zhyd.oauth + JustAuth + ${justauth.version} + + + + + com.alipay.sdk + alipay-sdk-java + ${alipay.version} + + + commons-logging + commons-logging + + + + + + + com.aliyun + dysmsapi20170525 + ${dysmsapi.version} + + + + + com.ruoyi.geekxd + ruoyi-oauth-justauth + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-oauth-wx + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-tfa-phone + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-tfa-email + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-auth-starter + ${ruoyi.version} + + + + + + + ruoyi-auth-common + ruoyi-oauth-justauth + ruoyi-oauth-wx + ruoyi-tfa-phone + ruoyi-tfa-email + ruoyi-auth-starter + + pom + \ No newline at end of file diff --git a/ruoyi-scene-auth/ruoyi-auth-common/pom.xml b/ruoyi-scene-auth/ruoyi-auth-common/pom.xml new file mode 100644 index 0000000..b2f6c9c --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-common/pom.xml @@ -0,0 +1,27 @@ + + + + ruoyi-scene-auth + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-auth-common + + + system系统模块 + + + + + + + com.ruoyi.geekxd + ruoyi-framework + + + + + \ No newline at end of file diff --git a/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/domain/OauthUser.java b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/domain/OauthUser.java new file mode 100644 index 0000000..8cb19f7 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/domain/OauthUser.java @@ -0,0 +1,317 @@ +package com.ruoyi.auth.common.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 第三方认证对象 oauth_user + * + * @author Dftre + * @date 2024-01-18 + */ +@Schema(description = "第三方认证对象") +public class OauthUser extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 主键 */ + @Schema(title = "主键") + private Long id; + + /** 第三方系统的唯一ID,详细解释请参考:名词解释 */ + @Schema(title = "第三方系统的唯一ID,详细解释请参考:名词解释") + @Excel(name = "第三方系统的唯一ID,详细解释请参考:名词解释") + private String uuid; + + /** 用户ID */ + @Schema(title = "用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** 第三方用户来源 */ + @Schema(title = "第三方用户来源") + @Excel(name = "第三方用户来源") + private String source; + + /** 用户的授权令牌 */ + @Schema(title = "用户的授权令牌") + @Excel(name = "用户的授权令牌") + private String accessToken; + + /** 第三方用户的授权令牌的有效期,部分平台可能没有 */ + @Schema(title = "第三方用户的授权令牌的有效期,部分平台可能没有") + @Excel(name = "第三方用户的授权令牌的有效期,部分平台可能没有") + private Long expireIn; + + /** 刷新令牌,部分平台可能没有 */ + @Schema(title = "刷新令牌,部分平台可能没有") + @Excel(name = "刷新令牌,部分平台可能没有") + private String refreshToken; + + /** 第三方用户的 open id,部分平台可能没有 */ + @Schema(title = "第三方用户的 open id,部分平台可能没有") + @Excel(name = "第三方用户的 open id,部分平台可能没有") + private String openId; + + /** 第三方用户的 ID,部分平台可能没有 */ + @Schema(title = "第三方用户的 ID,部分平台可能没有") + @Excel(name = "第三方用户的 ID,部分平台可能没有") + private String uid; + + /** 个别平台的授权信息,部分平台可能没有 */ + @Schema(title = "个别平台的授权信息,部分平台可能没有") + @Excel(name = "个别平台的授权信息,部分平台可能没有") + private String accessCode; + + /** 第三方用户的 union id,部分平台可能没有 */ + @Schema(title = "第三方用户的 union id,部分平台可能没有") + @Excel(name = "第三方用户的 union id,部分平台可能没有") + private String unionId; + + /** 第三方用户授予的权限,部分平台可能没有 */ + @Schema(title = "第三方用户授予的权限,部分平台可能没有") + @Excel(name = "第三方用户授予的权限,部分平台可能没有") + private String scope; + + /** 个别平台的授权信息,部分平台可能没有 */ + @Schema(title = "个别平台的授权信息,部分平台可能没有") + @Excel(name = "个别平台的授权信息,部分平台可能没有") + private String tokenType; + + /** id token,部分平台可能没有 */ + @Schema(title = "id token,部分平台可能没有") + @Excel(name = "id token,部分平台可能没有") + private String idToken; + + /** 小米平台用户的附带属性,部分平台可能没有 */ + @Schema(title = "小米平台用户的附带属性,部分平台可能没有") + @Excel(name = "小米平台用户的附带属性,部分平台可能没有") + private String macAlgorithm; + + /** 小米平台用户的附带属性,部分平台可能没有 */ + @Schema(title = "小米平台用户的附带属性,部分平台可能没有") + @Excel(name = "小米平台用户的附带属性,部分平台可能没有") + private String macKey; + + /** 用户的授权code,部分平台可能没有 */ + @Schema(title = "用户的授权code,部分平台可能没有") + @Excel(name = "用户的授权code,部分平台可能没有") + private String code; + + /** Twitter平台用户的附带属性,部分平台可能没有 */ + @Schema(title = "Twitter平台用户的附带属性,部分平台可能没有") + @Excel(name = "Twitter平台用户的附带属性,部分平台可能没有") + private String oauthToken; + + /** Twitter平台用户的附带属性,部分平台可能没有 */ + @Schema(title = "Twitter平台用户的附带属性,部分平台可能没有") + @Excel(name = "Twitter平台用户的附带属性,部分平台可能没有") + private String oauthTokenSecret; + + /** 用户名称 */ + @Schema(title = "用户名称") + @Excel(name = "用户名称") + private String userName; + + /** 部门名称 */ + @Schema(title = "部门名称") + @Excel(name = "部门名称") + private String deptName; + + public String getDeptName() { + return deptName; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getUuid() { + return uuid; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getUserId() { + return userId; + } + + public void setSource(String source) { + this.source = source; + } + + public String getSource() { + return source; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getAccessToken() { + return accessToken; + } + + public void setExpireIn(Long expireIn) { + this.expireIn = expireIn; + } + + public Long getExpireIn() { + return expireIn; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setOpenId(String openId) { + this.openId = openId; + } + + public String getOpenId() { + return openId; + } + + public void setUid(String uid) { + this.uid = uid; + } + + public String getUid() { + return uid; + } + + public void setAccessCode(String accessCode) { + this.accessCode = accessCode; + } + + public String getAccessCode() { + return accessCode; + } + + public void setUnionId(String unionId) { + this.unionId = unionId; + } + + public String getUnionId() { + return unionId; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getScope() { + return scope; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + public String getTokenType() { + return tokenType; + } + + public void setIdToken(String idToken) { + this.idToken = idToken; + } + + public String getIdToken() { + return idToken; + } + + public void setMacAlgorithm(String macAlgorithm) { + this.macAlgorithm = macAlgorithm; + } + + public String getMacAlgorithm() { + return macAlgorithm; + } + + public void setMacKey(String macKey) { + this.macKey = macKey; + } + + public String getMacKey() { + return macKey; + } + + public void setCode(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public void setOauthToken(String oauthToken) { + this.oauthToken = oauthToken; + } + + public String getOauthToken() { + return oauthToken; + } + + public void setOauthTokenSecret(String oauthTokenSecret) { + this.oauthTokenSecret = oauthTokenSecret; + } + + public String getOauthTokenSecret() { + return oauthTokenSecret; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("uuid", getUuid()) + .append("userId", getUserId()) + .append("source", getSource()) + .append("accessToken", getAccessToken()) + .append("expireIn", getExpireIn()) + .append("refreshToken", getRefreshToken()) + .append("openId", getOpenId()) + .append("uid", getUid()) + .append("accessCode", getAccessCode()) + .append("unionId", getUnionId()) + .append("scope", getScope()) + .append("tokenType", getTokenType()) + .append("idToken", getIdToken()) + .append("macAlgorithm", getMacAlgorithm()) + .append("macKey", getMacKey()) + .append("code", getCode()) + .append("oauthToken", getOauthToken()) + .append("oauthTokenSecret", getOauthTokenSecret()) + .toString(); + } +} diff --git a/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/enums/OauthVerificationUse.java b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/enums/OauthVerificationUse.java new file mode 100644 index 0000000..6b44b6c --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/enums/OauthVerificationUse.java @@ -0,0 +1,59 @@ +package com.ruoyi.auth.common.enums; + +public enum OauthVerificationUse { + + /** 用于登录 */ + LOGIN("登录", "login"), + /** 用于注册 */ + REGISTER("注册", "register"), + /** 用于禁用 */ + DISABLE("禁用", "disable"), + /** 用于重置信息 */ + RESET("重置", "reset"), + /** 用于绑定信息 */ + BIND("绑定", "bind"), + /** 其他用途 */ + OTHER("其他", "other"); + + private String name; + private String value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public static OauthVerificationUse getByValue(String value) { + for (OauthVerificationUse use : OauthVerificationUse.values()) { + if (use.getValue().equals(value)) { + return use; + } + } + return null; + } + + public static OauthVerificationUse getByName(String name) { + for (OauthVerificationUse use : OauthVerificationUse.values()) { + if (use.getName().equals(name)) { + return use; + } + } + return null; + } + + private OauthVerificationUse(String name, String value) { + this.name = name; + this.value = value; + } +} diff --git a/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/mapper/OauthUserMapper.java b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/mapper/OauthUserMapper.java new file mode 100644 index 0000000..656630e --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/mapper/OauthUserMapper.java @@ -0,0 +1,112 @@ +package com.ruoyi.auth.common.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Param; + +import com.ruoyi.auth.common.domain.OauthUser; + +/** + * 第三方认证Mapper接口 + * + * @author Dftre + * @date 2024-01-18 + */ +public interface OauthUserMapper { + /** + * 查询第三方认证 + * + * @param id 第三方认证主键 + * @return 第三方认证 + */ + public OauthUser selectOauthUserById(Long id); + + public OauthUser selectOauthUserByUserId(Long userId); + + /** + * 查询第三方认证 + * 钉钉、抖音:uuid 为用户的 unionid + * 微信公众平台登录、京东、酷家乐、美团:uuid 为用户的 openId + * 微信开放平台登录、QQ:uuid 为用户的 openId,平台支持获取unionid, unionid 在 AuthToken + * 中(如果支持),在登录完成后,可以通过 response.getData().getToken().getUnionId() 获取 + * Google:uuid 为用户的 sub,sub为Google的所有账户体系中用户唯一的身份标识符,详见:OpenID Connect + * + * @param uuid + * @return + */ + public OauthUser selectOauthUserByUUID(String uuid); + + /** + * 查询第三方认证列表 + * + * @param oauthUser 第三方认证 + * @return 第三方认证集合 + */ + public List selectOauthUserList(OauthUser oauthUser); + + /** + * 新增第三方认证 + * + * @param oauthUser 第三方认证 + * @return 结果 + */ + public int insertOauthUser(OauthUser oauthUser); + + /** + * 修改第三方认证 + * + * @param oauthUser 第三方认证 + * @return 结果 + */ + public int updateOauthUser(OauthUser oauthUser); + + /** + * 删除第三方认证 + * + * @param id 第三方认证主键 + * @return 结果 + */ + public int deleteOauthUserById(Long id); + + /** + * 批量删除第三方认证 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteOauthUserByIds(Long[] ids); + + /** + * 校验source平台是否绑定 + * + * @param userId 用户编号 + * @param source 绑定平台 + * @return 结果 + */ + public int checkAuthUser(@Param("userId") Long userId, @Param("source") String source); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public int checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public int checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public int checkEmailUnique(String email); + +} diff --git a/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/service/IOauthUserService.java b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/service/IOauthUserService.java new file mode 100644 index 0000000..bc01edf --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/service/IOauthUserService.java @@ -0,0 +1,102 @@ +package com.ruoyi.auth.common.service; + +import java.util.List; + +import com.ruoyi.auth.common.domain.OauthUser; +import com.ruoyi.common.core.domain.entity.SysUser; + +/** + * 第三方认证Service接口 + * + * @author Dftre + * @date 2024-01-18 + */ +public interface IOauthUserService { + /** + * 查询第三方认证 + * + * @param id 第三方认证主键 + * @return 第三方认证 + */ + public OauthUser selectOauthUserById(Long id); + + public OauthUser selectOauthUserByUUID(String uuid); + + public OauthUser selectOauthUserByUserId(Long userId); + + public SysUser selectSysUserByUUID(String uuid); + + /** + * 查询第三方认证列表 + * + * @param oauthUser 第三方认证 + * @return 第三方认证集合 + */ + public List selectOauthUserList(OauthUser oauthUser); + + /** + * 新增第三方认证 + * + * @param oauthUser 第三方认证 + * @return 结果 + */ + public int insertOauthUser(OauthUser oauthUser); + + /** + * 修改第三方认证 + * + * @param oauthUser 第三方认证 + * @return 结果 + */ + public int updateOauthUser(OauthUser oauthUser); + + /** + * 批量删除第三方认证 + * + * @param ids 需要删除的第三方认证主键集合 + * @return 结果 + */ + public int deleteOauthUserByIds(Long[] ids); + + /** + * 删除第三方认证信息 + * + * @param id 第三方认证主键 + * @return 结果 + */ + public int deleteOauthUserById(Long id); + + /** + * 校验source平台是否绑定 + * + * @param userId 用户编号 + * @param source 绑定平台 + * @return 结果 + */ + public boolean checkAuthUser(Long userId, String source); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public boolean checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public boolean checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public boolean checkEmailUnique(String email); + +} diff --git a/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/service/OauthVerificationCodeService.java b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/service/OauthVerificationCodeService.java new file mode 100644 index 0000000..8fa22f0 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/service/OauthVerificationCodeService.java @@ -0,0 +1,15 @@ +package com.ruoyi.auth.common.service; + +import com.ruoyi.auth.common.enums.OauthVerificationUse; + +/** + * code认证方式接口 + * + * @author zlh + * @date 2024-04-16 + */ +public interface OauthVerificationCodeService { + public boolean sendCode(String o, String code,OauthVerificationUse use) throws Exception; + public boolean checkCode(String o, String code,OauthVerificationUse use) throws Exception; + +} diff --git a/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/service/TfaService.java b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/service/TfaService.java new file mode 100644 index 0000000..92d92dd --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/service/TfaService.java @@ -0,0 +1,73 @@ +package com.ruoyi.auth.common.service; + +import com.ruoyi.common.core.domain.model.LoginBody; +import com.ruoyi.common.core.domain.model.RegisterBody; + +/** + * 双因素认证(TFA)操作的服务接口。 + * 该接口提供处理TFA绑定、注册和登录流程的方法, + * 包括它们的验证步骤。 + * + *

+ * 双因素认证通过要求用户提供两种不同的认证因素, + * 为认证过程增加了额外的安全层。 + *

+ */ +public interface TfaService { + /** + * 启动将TFA方法绑定到用户账户的流程。 + * + * @param loginBody 包含TFA绑定所需数据的登录信息 + */ + public void doBind(LoginBody loginBody); + + /** + * 使用验证码或其他确认方式验证TFA绑定流程。 + * + * @param loginBody 包含验证数据的登录信息 + */ + public void doBindVerify(LoginBody loginBody); + + /** + * 处理包含TFA设置的注册流程。 + * + * @param registerBody 包含用户详情和TFA设置的注册信息 + */ + public void doRegister(RegisterBody registerBody); + + /** + * 验证包含TFA设置的注册流程。 + * + * @param registerBody 包含验证数据的注册信息 + */ + public void doRegisterVerify(RegisterBody registerBody); + + /** + * 启动TFA登录流程的第一步。 + * + * @param loginBody 包含用户凭证的登录信息 + */ + public void doLogin(LoginBody loginBody, boolean autoRegister); + + /** + * 验证TFA登录流程的第二步并完成认证。 + * + * @param loginBody 包含TFA验证码的登录信息 + * @return 已认证会话的字符串令牌或会话标识符 + */ + public String doLoginVerify(LoginBody loginBody, boolean autoRegister); + + /** + * 启动TFA重置流程的第一步。 + * + * @param registerBody 包含用户凭证的注册信息 + */ + public void doReset(RegisterBody registerBody); + + /** + * 验证TFA重置流程的第二步并完成重置。 + * + * @param registerBody 包含TFA验证码的注册信息 + */ + public void doResetVerify(RegisterBody registerBody); +} diff --git a/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/service/impl/OauthUserServiceImpl.java b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/service/impl/OauthUserServiceImpl.java new file mode 100644 index 0000000..5fdb695 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/service/impl/OauthUserServiceImpl.java @@ -0,0 +1,154 @@ +package com.ruoyi.auth.common.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.auth.common.domain.OauthUser; +import com.ruoyi.auth.common.mapper.OauthUserMapper; +import com.ruoyi.auth.common.service.IOauthUserService; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.mapper.SysUserMapper; + +/** + * 第三方认证Service业务层处理 + * + * @author Dftre + * @date 2024-01-18 + */ +@Service +public class OauthUserServiceImpl implements IOauthUserService { + @Autowired + private OauthUserMapper oauthUserMapper; + + @Autowired + private SysUserMapper sysUserMapper; + + /** + * 查询第三方认证 + * + * @param id 第三方认证主键 + * @return 第三方认证 + */ + @Override + public OauthUser selectOauthUserById(Long id) { + return oauthUserMapper.selectOauthUserById(id); + } + + @Override + public OauthUser selectOauthUserByUUID(String uuid) { + return oauthUserMapper.selectOauthUserByUUID(uuid); + } + + @Override + public OauthUser selectOauthUserByUserId(Long userId) { + return oauthUserMapper.selectOauthUserByUserId(userId); + } + + /** + * 查询第三方认证列表 + * + * @param oauthUser 第三方认证 + * @return 第三方认证 + */ + @Override + public List selectOauthUserList(OauthUser oauthUser) { + return oauthUserMapper.selectOauthUserList(oauthUser); + } + + /** + * 新增第三方认证 + * + * @param oauthUser 第三方认证 + * @return 结果 + */ + @Override + public int insertOauthUser(OauthUser oauthUser) { + return oauthUserMapper.insertOauthUser(oauthUser); + } + + /** + * 修改第三方认证 + * + * @param oauthUser 第三方认证 + * @return 结果 + */ + @Override + public int updateOauthUser(OauthUser oauthUser) { + return oauthUserMapper.updateOauthUser(oauthUser); + } + + /** + * 批量删除第三方认证 + * + * @param ids 需要删除的第三方认证主键 + * @return 结果 + */ + @Override + public int deleteOauthUserByIds(Long[] ids) { + return oauthUserMapper.deleteOauthUserByIds(ids); + } + + /** + * 删除第三方认证信息 + * + * @param id 第三方认证主键 + * @return 结果 + */ + @Override + public int deleteOauthUserById(Long id) { + return oauthUserMapper.deleteOauthUserById(id); + } + + public SysUser selectSysUserByUUID(String uuid) { + OauthUser oauthUser = oauthUserMapper.selectOauthUserByUUID(uuid); + if (StringUtils.isNotNull(oauthUser)) { + return sysUserMapper.selectUserById(oauthUser.getUserId()); + } else { + return null; + } + } + + /** + * 校验source平台是否绑定 + * + * @param userId 用户编号 + * @param source 绑定平台 + * @return 结果 + */ + public boolean checkAuthUser(Long userId, String source) { + return oauthUserMapper.checkAuthUser(userId, source) > 0; + }; + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public boolean checkUserNameUnique(String userName) { + return oauthUserMapper.checkUserNameUnique(userName) > 0; + }; + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public boolean checkPhoneUnique(String phonenumber) { + return oauthUserMapper.checkPhoneUnique(phonenumber) > 0; + }; + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public boolean checkEmailUnique(String email) { + return oauthUserMapper.checkEmailUnique(email) > 0; + }; +} diff --git a/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/utils/RandomCodeUtil.java b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/utils/RandomCodeUtil.java new file mode 100644 index 0000000..098e9c4 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-common/src/main/java/com/ruoyi/auth/common/utils/RandomCodeUtil.java @@ -0,0 +1,28 @@ +package com.ruoyi.auth.common.utils; + +import java.security.SecureRandom; + +public class RandomCodeUtil { + + public static String randomString(String characters, int length) { + StringBuilder result = new StringBuilder(); + SecureRandom random = new SecureRandom(); + + for (int i = 0; i < length; i++) { + int index = random.nextInt(characters.length()); + result.append(characters.charAt(index)); + } + + return result.toString(); + } + + public static String numberCode(int length) { + String characters = "0123456789"; + return randomString(characters, length); + } + + public static String code(int length) { + String characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + return randomString(characters, length); + } +} diff --git a/ruoyi-scene-auth/ruoyi-auth-common/src/main/resources/mapper/common/OauthUserMapper.xml b/ruoyi-scene-auth/ruoyi-auth-common/src/main/resources/mapper/common/OauthUserMapper.xml new file mode 100644 index 0000000..8966a12 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-common/src/main/resources/mapper/common/OauthUserMapper.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, uuid, source, access_token, expire_in, refresh_token, open_id, uid, access_code, union_id, scope, token_type, id_token, mac_algorithm, mac_key, code, oauth_token, oauth_token_secret, sys_user.user_name, sys_dept.dept_name from oauth_user + left join sys_user on oauth_user.user_id = sys_user.user_id + left join sys_dept on sys_user.dept_id = sys_dept.dept_id + + + + + + + + + + + + + + + + + + + + insert into oauth_user + + id, + uuid, + user_id, + source, + access_token, + expire_in, + refresh_token, + open_id, + uid, + access_code, + union_id, + scope, + token_type, + id_token, + mac_algorithm, + mac_key, + code, + oauth_token, + oauth_token_secret, + + + #{id}, + #{uuid}, + #{userId}, + #{source}, + #{accessToken}, + #{expireIn}, + #{refreshToken}, + #{openId}, + #{uid}, + #{accessCode}, + #{unionId}, + #{scope}, + #{tokenType}, + #{idToken}, + #{macAlgorithm}, + #{macKey}, + #{code}, + #{oauthToken}, + #{oauthTokenSecret}, + + + + + update oauth_user + + uuid = #{uuid}, + user_id = #{userId}, + source = #{source}, + access_token = #{accessToken}, + expire_in = #{expireIn}, + refresh_token = #{refreshToken}, + open_id = #{openId}, + uid = #{uid}, + access_code = #{accessCode}, + union_id = #{unionId}, + scope = #{scope}, + token_type = #{tokenType}, + id_token = #{idToken}, + mac_algorithm = #{macAlgorithm}, + mac_key = #{macKey}, + code = #{code}, + oauth_token = #{oauthToken}, + oauth_token_secret = #{oauthTokenSecret}, + + where oauth_user.id = #{id} + + + + delete from oauth_user where id = #{id} + + + + delete from oauth_user where id in + + #{id} + + + \ No newline at end of file diff --git a/ruoyi-scene-auth/ruoyi-auth-starter/pom.xml b/ruoyi-scene-auth/ruoyi-auth-starter/pom.xml new file mode 100644 index 0000000..0fbab6f --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-starter/pom.xml @@ -0,0 +1,50 @@ + + + + ruoyi-scene-auth + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-auth-starter + + + 第三方认证模块 + + + + + + com.ruoyi.geekxd + ruoyi-auth-common + + + + + com.ruoyi.geekxd + ruoyi-oauth-justauth + + + + + com.ruoyi.geekxd + ruoyi-oauth-wx + + + + + com.ruoyi.geekxd + ruoyi-tfa-phone + + + + + com.ruoyi.geekxd + ruoyi-tfa-email + + + + \ No newline at end of file diff --git a/ruoyi-scene-auth/ruoyi-auth-starter/src/main/java/com/ruoyi/auth/controller/OauthUserController.java b/ruoyi-scene-auth/ruoyi-auth-starter/src/main/java/com/ruoyi/auth/controller/OauthUserController.java new file mode 100644 index 0000000..cefe56e --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-starter/src/main/java/com/ruoyi/auth/controller/OauthUserController.java @@ -0,0 +1,109 @@ +package com.ruoyi.auth.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.auth.common.domain.OauthUser; +import com.ruoyi.auth.common.service.IOauthUserService; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 第三方认证Controller + * + * @author Dftre + * @date 2024-01-18 + */ +@RestController +@RequestMapping("/system/oauth") +@Tag(name = "【第三方认证】管理") +public class OauthUserController extends BaseController { + @Autowired + private IOauthUserService oauthUserService; + + /** + * 查询第三方认证列表 + */ + @Operation(summary = "查询第三方认证列表") + @PreAuthorize("@ss.hasPermi('system:oauth:list')") + @GetMapping("/list") + public TableDataInfo list(OauthUser oauthUser) { + startPage(); + List list = oauthUserService.selectOauthUserList(oauthUser); + return getDataTable(list); + } + + /** + * 导出第三方认证列表 + */ + @Operation(summary = "导出第三方认证列表") + @PreAuthorize("@ss.hasPermi('system:oauth:export')") + @Log(title = "第三方认证", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, OauthUser oauthUser) { + List list = oauthUserService.selectOauthUserList(oauthUser); + ExcelUtil util = new ExcelUtil(OauthUser.class); + util.exportExcel(response, list, "第三方认证数据"); + } + + /** + * 获取第三方认证详细信息 + */ + @Operation(summary = "获取第三方认证详细信息") + @PreAuthorize("@ss.hasPermi('system:oauth:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) { + return success(oauthUserService.selectOauthUserById(id)); + } + + /** + * 新增第三方认证 + */ + @Operation(summary = "新增第三方认证") + @PreAuthorize("@ss.hasPermi('system:oauth:add')") + @Log(title = "第三方认证", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody OauthUser oauthUser) { + return toAjax(oauthUserService.insertOauthUser(oauthUser)); + } + + /** + * 修改第三方认证 + */ + @Operation(summary = "修改第三方认证") + @PreAuthorize("@ss.hasPermi('system:oauth:edit')") + @Log(title = "第三方认证", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody OauthUser oauthUser) { + return toAjax(oauthUserService.updateOauthUser(oauthUser)); + } + + /** + * 删除第三方认证 + */ + @Operation(summary = "删除第三方认证") + @PreAuthorize("@ss.hasPermi('system:oauth:remove')") + @Log(title = "第三方认证", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable(name = "ids") Long[] ids) { + return toAjax(oauthUserService.deleteOauthUserByIds(ids)); + } +} diff --git a/ruoyi-scene-auth/ruoyi-auth-starter/src/main/java/com/ruoyi/auth/controller/TfaController.java b/ruoyi-scene-auth/ruoyi-auth-starter/src/main/java/com/ruoyi/auth/controller/TfaController.java new file mode 100644 index 0000000..78904c9 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-auth-starter/src/main/java/com/ruoyi/auth/controller/TfaController.java @@ -0,0 +1,113 @@ +package com.ruoyi.auth.controller; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.auth.common.service.TfaService; +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginBody; +import com.ruoyi.common.core.domain.model.RegisterBody; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.PostConstruct; + +@RestController +@RequestMapping("/auth/{channel}") // dySms mail +@Tag(name = "【第三方认证】双因素认证") +public class TfaController extends BaseController { + + @Autowired(required = false) + Map tfaServiceMap; + + @PostConstruct + public void init() { + if (tfaServiceMap == null) { + tfaServiceMap = new HashMap<>(); + logger.warn("请注意,没有加载任何双认证服务"); + } else { + tfaServiceMap.forEach((k, v) -> { + logger.info("已加载双认证服务 {}", k); + }); + } + } + + @Operation(summary = "发送注册验证码") + @PostMapping("/send/register") + @Anonymous + public AjaxResult sendRegister(@PathVariable String channel, @RequestBody RegisterBody registerBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doRegister(registerBody); + return success(); + } + + @Operation(summary = "验证注册验证码") + @PostMapping("/verify/register") + @Anonymous + public AjaxResult verifyRegister(@PathVariable String channel, @RequestBody RegisterBody registerBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doRegisterVerify(registerBody); + return success(); + } + + @Operation(summary = "发送登录验证码") + @PostMapping("/send/login") + @Anonymous + public AjaxResult sendLogin(@PathVariable String channel, @RequestBody LoginBody loginBody, + @RequestParam(defaultValue = "false") boolean autoRegister) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doLogin(loginBody, autoRegister); + return success(); + } + + @Operation(summary = "验证登录验证码") + @PostMapping("/verify/login") + @Anonymous + public AjaxResult verifyLogin(@PathVariable String channel, @RequestBody LoginBody loginBody, + @RequestParam(defaultValue = "false") boolean autoRegister) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + return success(tfaService.doLoginVerify(loginBody, autoRegister)); + } + + @Operation(summary = "发送绑定验证码") + @PostMapping("/send/bind") + public AjaxResult send(@PathVariable String channel, @RequestBody LoginBody loginBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doBind(loginBody); + return success(); + } + + @Operation(summary = "验证绑定验证码") + @PostMapping("/verify/bind") // 发送验证码 + public AjaxResult verify(@PathVariable String channel, @RequestBody LoginBody loginBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doBindVerify(loginBody); + return success(); + } + + @Operation(summary = "发送重置验证码") + @PostMapping("/send/reset") + public AjaxResult sendReset(@PathVariable String channel, @RequestBody RegisterBody registerBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doReset(registerBody); + return success(); + } + + @Operation(summary = "验证重置验证码") + @PostMapping("/verify/reset") + public AjaxResult verifyReset(@PathVariable String channel, @RequestBody RegisterBody registerBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doReset(registerBody); + return success(); + } +} diff --git a/ruoyi-scene-auth/ruoyi-oauth-justauth/pom.xml b/ruoyi-scene-auth/ruoyi-oauth-justauth/pom.xml new file mode 100644 index 0000000..9eaaaeb --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-justauth/pom.xml @@ -0,0 +1,37 @@ + + + + ruoyi-scene-auth + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-oauth-justauth + + + justauth框架认证模块 + + + + + + + com.ruoyi.geekxd + ruoyi-auth-common + + + + me.zhyd.oauth + JustAuth + + + + com.alipay.sdk + alipay-sdk-java + + + + + \ No newline at end of file diff --git a/ruoyi-scene-auth/ruoyi-oauth-justauth/src/main/java/com/ruoyi/oauth/justauth/config/JustAuthConfig.java b/ruoyi-scene-auth/ruoyi-oauth-justauth/src/main/java/com/ruoyi/oauth/justauth/config/JustAuthConfig.java new file mode 100644 index 0000000..7d46a73 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-justauth/src/main/java/com/ruoyi/oauth/justauth/config/JustAuthConfig.java @@ -0,0 +1,46 @@ +package com.ruoyi.oauth.justauth.config; + +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import com.ruoyi.oauth.justauth.utils.JustAuthUtils; + +import jakarta.annotation.PostConstruct; +import me.zhyd.oauth.cache.AuthDefaultStateCache; +import me.zhyd.oauth.cache.AuthStateCache; +import me.zhyd.oauth.request.AuthRequest; + +@Configuration +@ConfigurationProperties("justauth") +public class JustAuthConfig { + private Map sources; + private AuthStateCache authStateCache; + + @PostConstruct + public void init() { + authStateCache = AuthDefaultStateCache.INSTANCE; + } + + public AuthRequest getAuthRequest(String source) { + JustAuthProperties properties = sources.get(source); + if (properties == null) { + throw new IllegalArgumentException("No JustAuthProperties found for source: " + source); + } + return JustAuthUtils.getAuthRequest(source, properties.getClientId(), properties.getClientSecret(), + properties.getRedirectUri(), authStateCache); + } + + public boolean isEnabled(String source) { + return sources.containsKey(source); + } + + public Map getSources() { + return sources; + } + + public void setSources(Map sources) { + this.sources = sources; + } +} diff --git a/ruoyi-scene-auth/ruoyi-oauth-justauth/src/main/java/com/ruoyi/oauth/justauth/config/JustAuthProperties.java b/ruoyi-scene-auth/ruoyi-oauth-justauth/src/main/java/com/ruoyi/oauth/justauth/config/JustAuthProperties.java new file mode 100644 index 0000000..c59da46 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-justauth/src/main/java/com/ruoyi/oauth/justauth/config/JustAuthProperties.java @@ -0,0 +1,10 @@ +package com.ruoyi.oauth.justauth.config; + +import lombok.Data; + +@Data +public class JustAuthProperties { + private String clientId; + private String clientSecret; + private String redirectUri; +} diff --git a/ruoyi-scene-auth/ruoyi-oauth-justauth/src/main/java/com/ruoyi/oauth/justauth/controller/SysAuthController.java b/ruoyi-scene-auth/ruoyi-oauth-justauth/src/main/java/com/ruoyi/oauth/justauth/controller/SysAuthController.java new file mode 100644 index 0000000..04ab08c --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-justauth/src/main/java/com/ruoyi/oauth/justauth/controller/SysAuthController.java @@ -0,0 +1,171 @@ +package com.ruoyi.oauth.justauth.controller; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.auth.common.domain.OauthUser; +import com.ruoyi.auth.common.service.IOauthUserService; +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.UserStatus; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.SysPermissionService; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.oauth.justauth.config.JustAuthConfig; +import com.ruoyi.system.service.ISysUserService; + +import jakarta.servlet.http.HttpServletRequest; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthRequest; +import me.zhyd.oauth.utils.AuthStateUtils; + +/** + * 第三方认证授权处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/auth") +public class SysAuthController extends BaseController { + + @Autowired + private ISysUserService userService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private TokenService tokenService; + + @Autowired + private IOauthUserService oauthUserService; + + @Autowired + private JustAuthConfig justAuthConfig; + + /** + * 认证授权 + * + * @param source + * @throws IOException + */ + @Anonymous + @GetMapping("/binding/{source}") + @ResponseBody + public AjaxResult authBinding(@PathVariable("source") String source, HttpServletRequest request) + throws IOException { + LoginUser tokenUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(tokenUser) && oauthUserService.checkAuthUser(tokenUser.getUserId(), source)) { + return error(source + "平台账号已经绑定"); + } + if (!justAuthConfig.isEnabled(source)) { + return error(source + "平台账号暂不支持"); + } + AuthRequest authRequest = justAuthConfig.getAuthRequest(source); + String authorizeUrl = authRequest.authorize(AuthStateUtils.createState()); + return success(authorizeUrl); + } + + /** + * 认证授权 + * + * @param source + * @throws IOException + */ + @Anonymous + @GetMapping("/login/{source}") + @ResponseBody + public AjaxResult login(@PathVariable("source") String source, HttpServletRequest request) + throws IOException { + if (!justAuthConfig.isEnabled(source)) { + return error(source + "平台账号暂不支持"); + } + AuthRequest authRequest = justAuthConfig.getAuthRequest(source); + String authorizeUrl = authRequest.authorize(AuthStateUtils.createState()); + return success(authorizeUrl); + } + + /** + * 第三方登录回调 + * + * @param source + * @param callback + * @param request + * @return + */ + @Anonymous + @GetMapping("/social-login/{source}") + public AjaxResult socialLogin(@PathVariable("source") String source, AuthCallback callback, + HttpServletRequest request) { + if (!justAuthConfig.isEnabled(source)) { + return AjaxResult.error(10002, "第三方平台系统不支持或未提供来源"); + } + AuthRequest authRequest = justAuthConfig.getAuthRequest(source); + AuthResponse response = authRequest.login(callback); + if (response.ok()) { + LoginUser tokenUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(tokenUser)) { + SysUser user = oauthUserService.selectSysUserByUUID(source + response.getData().getUuid()); + if (StringUtils.isNotNull(user)) { + String token = tokenService.createToken(SecurityUtils.getLoginUser()); + return success().put(Constants.TOKEN, token); + } + // 若已经登录则直接绑定系统账号 + OauthUser authUser = new OauthUser(); + // SysUser sysUser = new SysUser(); + // sysUser.setAvatar(response.getData().getAvatar()); + authUser.setUuid(source + response.getData().getUuid()); + authUser.setUserId(SecurityUtils.getUserId()); + // sysUser.setUserName(response.getData().getUsername()); + // sysUser.setNickName(response.getData().getNickname()); + // sysUser.setEmail(response.getData().getEmail()); + authUser.setSource(source); + oauthUserService.insertOauthUser(authUser); + // userService.insertUser(sysUser); + String token = tokenService.createToken(SecurityUtils.getLoginUser()); + return success().put(Constants.TOKEN, token); + } + SysUser authUser = oauthUserService.selectSysUserByUUID(source + response.getData().getUuid()); + if (StringUtils.isNotNull(authUser)) { + SysUser user = userService.selectUserByUserName(authUser.getUserName()); + if (StringUtils.isNull(user)) { + throw new ServiceException("登录用户:" + user.getUserName() + " 不存在"); + } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { + throw new ServiceException("对不起,您的账号:" + user.getUserName() + " 已被删除"); + } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { + throw new ServiceException("对不起,您的账号:" + user.getUserName() + " 已停用"); + } + LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, + permissionService.getMenuPermission(user)); + String token = tokenService.createToken(loginUser); + return success().put(Constants.TOKEN, token); + } else { + return AjaxResult.error(10002, "对不起,您没有绑定注册用户,请先注册后在个人中心绑定第三方授权信息!"); + } + } + return AjaxResult.error(10002, "对不起,授权信息验证不通过,请联系管理员"); + } + + /** + * 取消授权 + */ + @DeleteMapping(value = "/unlock/{authId}") + public AjaxResult unlockAuth(@PathVariable Long authId) { + return toAjax(oauthUserService.deleteOauthUserById(authId)); + } +} diff --git a/ruoyi-scene-auth/ruoyi-oauth-justauth/src/main/java/com/ruoyi/oauth/justauth/utils/JustAuthUtils.java b/ruoyi-scene-auth/ruoyi-oauth-justauth/src/main/java/com/ruoyi/oauth/justauth/utils/JustAuthUtils.java new file mode 100644 index 0000000..a421ad6 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-justauth/src/main/java/com/ruoyi/oauth/justauth/utils/JustAuthUtils.java @@ -0,0 +1,177 @@ +package com.ruoyi.oauth.justauth.utils; + +import me.zhyd.oauth.cache.AuthStateCache; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.request.AuthAlipayRequest; +import me.zhyd.oauth.request.AuthAliyunRequest; +import me.zhyd.oauth.request.AuthBaiduRequest; +import me.zhyd.oauth.request.AuthCodingRequest; +// import me.zhyd.oauth.request.AuthCsdnRequest; +import me.zhyd.oauth.request.AuthDingTalkRequest; +import me.zhyd.oauth.request.AuthDouyinRequest; +import me.zhyd.oauth.request.AuthElemeRequest; +import me.zhyd.oauth.request.AuthGiteeRequest; +import me.zhyd.oauth.request.AuthGithubRequest; +import me.zhyd.oauth.request.AuthGitlabRequest; +import me.zhyd.oauth.request.AuthHuaweiRequest; +import me.zhyd.oauth.request.AuthKujialeRequest; +import me.zhyd.oauth.request.AuthLinkedinRequest; +import me.zhyd.oauth.request.AuthMeituanRequest; +import me.zhyd.oauth.request.AuthMiRequest; +import me.zhyd.oauth.request.AuthMicrosoftRequest; +import me.zhyd.oauth.request.AuthOschinaRequest; +import me.zhyd.oauth.request.AuthPinterestRequest; +import me.zhyd.oauth.request.AuthQqRequest; +import me.zhyd.oauth.request.AuthRenrenRequest; +import me.zhyd.oauth.request.AuthRequest; +import me.zhyd.oauth.request.AuthStackOverflowRequest; +import me.zhyd.oauth.request.AuthTaobaoRequest; +import me.zhyd.oauth.request.AuthTeambitionRequest; +import me.zhyd.oauth.request.AuthToutiaoRequest; +import me.zhyd.oauth.request.AuthWeChatMpRequest; +import me.zhyd.oauth.request.AuthWeChatOpenRequest; +import me.zhyd.oauth.request.AuthWeiboRequest; + +/** + * 认证授权工具类 + * + * @author ruoyi + */ +public class JustAuthUtils +{ + @SuppressWarnings("deprecation") + public static AuthRequest getAuthRequest(String source, String clientId, String clientSecret, String redirectUri, + AuthStateCache authStateCache) + { + AuthRequest authRequest = null; + switch (source.toLowerCase()) + { + case "dingtalk": + authRequest = new AuthDingTalkRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "baidu": + authRequest = new AuthBaiduRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "github": + authRequest = new AuthGithubRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "gitee": + authRequest = new AuthGiteeRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "weibo": + authRequest = new AuthWeiboRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "coding": + authRequest = new AuthCodingRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "oschina": + authRequest = new AuthOschinaRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "alipay": + // 支付宝在创建回调地址时,不允许使用localhost或者127.0.0.1,所以这儿的回调地址使用的局域网内的ip + authRequest = new AuthAlipayRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .alipayPublicKey("").redirectUri(redirectUri).build(), authStateCache); + break; + case "qq": + authRequest = new AuthQqRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "wechat_open": + authRequest = new AuthWeChatOpenRequest(AuthConfig.builder().clientId(clientId) + .clientSecret(clientSecret).redirectUri(redirectUri).build(), authStateCache); + break; + // case "csdn": + // authRequest = new AuthCsdnRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + // .redirectUri(redirectUri).build(), authStateCache); + // break; + case "taobao": + authRequest = new AuthTaobaoRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "douyin": + authRequest = new AuthDouyinRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "linkedin": + authRequest = new AuthLinkedinRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "microsoft": + authRequest = new AuthMicrosoftRequest(AuthConfig.builder().clientId(clientId) + .clientSecret(clientSecret).redirectUri(redirectUri).build(), authStateCache); + break; + case "mi": + authRequest = new AuthMiRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "toutiao": + authRequest = new AuthToutiaoRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "teambition": + authRequest = new AuthTeambitionRequest(AuthConfig.builder().clientId(clientId) + .clientSecret(clientSecret).redirectUri(redirectUri).build(), authStateCache); + break; + case "pinterest": + authRequest = new AuthPinterestRequest(AuthConfig.builder().clientId(clientId) + .clientSecret(clientSecret).redirectUri(redirectUri).build(), authStateCache); + break; + case "renren": + authRequest = new AuthRenrenRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "stack_overflow": + authRequest = new AuthStackOverflowRequest(AuthConfig.builder().clientId(clientId) + .clientSecret(clientSecret).redirectUri(redirectUri).stackOverflowKey("").build(), + authStateCache); + break; + case "huawei": + authRequest = new AuthHuaweiRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + // case "wechat_enterprise": + // authRequest = new AuthWeChatEnterpriseRequest(AuthConfig.builder().clientId(clientId) + // .clientSecret(clientSecret).redirectUri(redirectUri).agentId("").build(), authStateCache); + // break; + case "kujiale": + authRequest = new AuthKujialeRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "gitlab": + authRequest = new AuthGitlabRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "meituan": + authRequest = new AuthMeituanRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "eleme": + authRequest = new AuthElemeRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build()); + break; + case "wechat_mp": + authRequest = new AuthWeChatMpRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "aliyun": + authRequest = new AuthAliyunRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + default: + break; + } + if (null == authRequest) + { + throw new AuthException("未获取到有效的Auth配置"); + } + return authRequest; + } +} diff --git a/ruoyi-scene-auth/ruoyi-oauth-wx/pom.xml b/ruoyi-scene-auth/ruoyi-oauth-wx/pom.xml new file mode 100644 index 0000000..0bf38f1 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-wx/pom.xml @@ -0,0 +1,29 @@ + + + + ruoyi-scene-auth + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-oauth-wx + + + 微信认证模块 + + + + + + + com.ruoyi.geekxd + ruoyi-auth-common + + + + + + \ No newline at end of file diff --git a/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/constant/WxMiniAppConstant.java b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/constant/WxMiniAppConstant.java new file mode 100644 index 0000000..7dc9433 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/constant/WxMiniAppConstant.java @@ -0,0 +1,42 @@ +package com.ruoyi.oauth.wx.constant; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class WxMiniAppConstant { + @Value("${oauth.wx.miniapp.appId}") + private String appId; + + @Value("${oauth.wx.miniapp.appSecret}") + private String appSecret; + + @Value("${oauth.wx.miniapp.url}") + private String url; + + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getAppSecret() { + return appSecret; + } + + public void setAppSecret(String appSecret) { + this.appSecret = appSecret; + } + +} diff --git a/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/constant/WxPubConstant.java b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/constant/WxPubConstant.java new file mode 100644 index 0000000..ae7cda4 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/constant/WxPubConstant.java @@ -0,0 +1,41 @@ +package com.ruoyi.oauth.wx.constant; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class WxPubConstant { + @Value("${oauth.wx.pub.appId}") + private String appId; + + @Value("${oauth.wx.pub.appSecret}") + private String appSecret; + + @Value("${oauth.wx.pub.url}") + private String url; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getAppSecret() { + return appSecret; + } + + public void setAppSecret(String appSecret) { + this.appSecret = appSecret; + } + +} diff --git a/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/controller/WxLoginController.java b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/controller/WxLoginController.java new file mode 100644 index 0000000..3707263 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/controller/WxLoginController.java @@ -0,0 +1,69 @@ +package com.ruoyi.oauth.wx.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.auth.common.domain.OauthUser; +import com.ruoyi.auth.common.service.IOauthUserService; +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.oauth.wx.service.Impl.WxMiniAppLoginServiceImpl; +import com.ruoyi.oauth.wx.service.Impl.WxPubLoginServiceImpl; + +@RestController +@RequestMapping("/oauth/wx") +public class WxLoginController extends BaseController { + + @Autowired + private IOauthUserService oauthUserService; + + @Autowired + private WxMiniAppLoginServiceImpl wxMiniAppLoginServiceImpl; + + @Autowired + private WxPubLoginServiceImpl wxPubLoginServiceImpl; + + @Anonymous + @PostMapping("/login/{source}/{code}") + public AjaxResult loginMiniApp(@PathVariable("source") String source, @PathVariable("code") String code) { + String token = null; + AjaxResult ajax = AjaxResult.success(); + if ("miniapp".equals(source)) { + token = wxMiniAppLoginServiceImpl.doLogin(code, true); + } else if ("pub".equals(source)) { + token = wxPubLoginServiceImpl.doLogin(code, false); + } else { + return error("错误的登录方式"); + } + ajax.put(Constants.TOKEN, token); + return ajax; + } + + @PostMapping("/register/{source}/{code}") + public AjaxResult register(@PathVariable("source") String source, @PathVariable("code") String code) { + OauthUser oauthUser = oauthUserService.selectOauthUserByUserId(getUserId()); + if (oauthUser != null) { + return error("不可以重复绑定"); + } else { + String msg = ""; + oauthUser = new OauthUser(); + oauthUser.setUserId(getUserId()); + oauthUser.setCode(code); + if ("miniapp".equals(source)) { + msg = wxMiniAppLoginServiceImpl.doRegister(oauthUser); + } else if ("pub".equals(source)) { + msg = wxPubLoginServiceImpl.doRegister(oauthUser); + } else { + return error("错误的注册方式"); + } + return StringUtils.isEmpty(msg) ? success() : error(msg); + } + } + +} diff --git a/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/service/Impl/WxMiniAppLoginServiceImpl.java b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/service/Impl/WxMiniAppLoginServiceImpl.java new file mode 100644 index 0000000..648a34b --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/service/Impl/WxMiniAppLoginServiceImpl.java @@ -0,0 +1,104 @@ +package com.ruoyi.oauth.wx.service.Impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.auth.common.domain.OauthUser; +import com.ruoyi.auth.common.service.IOauthUserService; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.web.service.SysLoginService; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.framework.web.service.UserDetailsServiceImpl; +import com.ruoyi.oauth.wx.constant.WxMiniAppConstant; +import com.ruoyi.oauth.wx.service.WxLoginService; +import com.ruoyi.system.service.ISysUserService; + +@Service +public class WxMiniAppLoginServiceImpl implements WxLoginService { + @Autowired + private WxMiniAppConstant wxAppConstant; + + @Autowired + private TokenService tokenService; + + @Autowired + private UserDetailsServiceImpl userDetailsServiceImpl; + + @Autowired + private ISysUserService userService; + + @Autowired + private IOauthUserService oauthUserService; + + @Autowired + private SysLoginService sysLoginService; + + @Override + public String doLogin(String code, boolean autoRegister) { + JSONObject doAuth = doAuth( + wxAppConstant.getUrl(), + wxAppConstant.getAppId(), + wxAppConstant.getAppSecret(), + code); + String openid = doAuth.getString("openid"); + OauthUser selectOauthUser = oauthUserService.selectOauthUserByUUID(openid); + SysUser sysUser = null; + if (selectOauthUser == null) { + if (autoRegister) { + sysUser = new SysUser(); + sysUser.setUserName(openid); + sysUser.setNickName(openid); + sysUser.setPassword(SecurityUtils.encryptPassword(code)); + userService.registerUser(sysUser); + OauthUser oauthUser = new OauthUser(); + oauthUser.setUserId(sysUser.getUserId()); + oauthUser.setOpenId(doAuth.getString("openid")); + oauthUser.setUuid(doAuth.getString("openid")); + oauthUser.setSource("WXMiniApp"); + oauthUser.setAccessToken(doAuth.getString("session_key")); + oauthUserService.insertOauthUser(oauthUser); + } + } else { + sysUser = userService.selectUserById(selectOauthUser.getUserId()); + } + if (sysUser == null) { + throw new ServiceException("该微信未绑定用户"); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, + MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) userDetailsServiceImpl.createLoginUser(sysUser); + sysLoginService.recordLoginInfo(loginUser.getUserId()); + return tokenService.createToken(loginUser); + } + + @Override + public String doRegister(OauthUser oauthUser) { + if (StringUtils.isEmpty(oauthUser.getCode())) { + return "没有凭证"; + } + if (oauthUser.getUserId() == null) { + return "请先注册账号"; + } + JSONObject doAuth = doAuth( + wxAppConstant.getUrl(), + wxAppConstant.getAppId(), + wxAppConstant.getAppSecret(), + oauthUser.getCode()); + oauthUser.setOpenId(doAuth.getString("openid")); + oauthUser.setUuid(doAuth.getString("openid")); + oauthUser.setSource("WXMiniApp"); + oauthUser.setAccessToken(doAuth.getString("session_key")); + oauthUserService.insertOauthUser(oauthUser); + return ""; + } + +} diff --git a/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/service/Impl/WxPubLoginServiceImpl.java b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/service/Impl/WxPubLoginServiceImpl.java new file mode 100644 index 0000000..495b30f --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/service/Impl/WxPubLoginServiceImpl.java @@ -0,0 +1,105 @@ +package com.ruoyi.oauth.wx.service.Impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.auth.common.domain.OauthUser; +import com.ruoyi.auth.common.service.IOauthUserService; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.web.service.SysLoginService; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.framework.web.service.UserDetailsServiceImpl; +import com.ruoyi.oauth.wx.constant.WxPubConstant; +import com.ruoyi.oauth.wx.service.WxLoginService; +import com.ruoyi.system.service.ISysUserService; + +@Service +public class WxPubLoginServiceImpl implements WxLoginService { + + @Autowired + private WxPubConstant wxH5Constant; + + @Autowired + private TokenService tokenService; + + @Autowired + private UserDetailsServiceImpl userDetailsServiceImpl; + + @Autowired + private ISysUserService userService; + + @Autowired + private IOauthUserService oauthUserService; + + @Autowired + private SysLoginService sysLoginService; + + @Override + public String doLogin(String code, boolean autoRegister) { + JSONObject doAuth = doAuth( + wxH5Constant.getUrl(), + wxH5Constant.getAppId(), + wxH5Constant.getAppSecret(), + code); + String openid = doAuth.getString("openid"); + OauthUser selectOauthUser = oauthUserService.selectOauthUserByUUID(openid); + SysUser sysUser = null; + if (selectOauthUser == null) { + if (autoRegister) { + sysUser = new SysUser(); + sysUser.setUserName(openid); + sysUser.setNickName(openid); + sysUser.setPassword(SecurityUtils.encryptPassword(code)); + userService.registerUser(sysUser); + OauthUser oauthUser = new OauthUser(); + oauthUser.setUserId(sysUser.getUserId()); + oauthUser.setOpenId(doAuth.getString("openid")); + oauthUser.setUuid(doAuth.getString("openid")); + oauthUser.setSource("WXMiniApp"); + oauthUser.setAccessToken(doAuth.getString("session_key")); + oauthUserService.insertOauthUser(oauthUser); + } + } else { + sysUser = userService.selectUserById(selectOauthUser.getUserId()); + } + if (sysUser == null) { + throw new ServiceException("该微信未绑定用户"); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, + MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) userDetailsServiceImpl.createLoginUser(sysUser); + sysLoginService.recordLoginInfo(loginUser.getUserId()); + return tokenService.createToken(loginUser); + } + + @Override + public String doRegister(OauthUser oauthUser) { + if (StringUtils.isEmpty(oauthUser.getCode())) { + return "没有凭证"; + } + if (oauthUser.getUserId() == null) { + return "请先注册账号"; + } + JSONObject doAuth = doAuth( + wxH5Constant.getUrl(), + wxH5Constant.getAppId(), + wxH5Constant.getAppSecret(), + oauthUser.getCode()); + oauthUser.setOpenId(doAuth.getString("openid")); + oauthUser.setUuid(doAuth.getString("openid")); + oauthUser.setSource("WXPub"); + oauthUser.setAccessToken(doAuth.getString("session_key")); + oauthUserService.insertOauthUser(oauthUser); + return ""; + } + +} diff --git a/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/service/WxLoginService.java b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/service/WxLoginService.java new file mode 100644 index 0000000..31b4047 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/java/com/ruoyi/oauth/wx/service/WxLoginService.java @@ -0,0 +1,34 @@ +package com.ruoyi.oauth.wx.service; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.auth.common.domain.OauthUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.http.HttpUtils; + +public interface WxLoginService { + + public String doLogin(String code, boolean autoRegister); + + public String doRegister(OauthUser oauthUser); + + public default JSONObject doAuth(String url, String appid, String secret, String code) { + StringBuilder builder = new StringBuilder(url); + builder.append("?appid=").append(appid) + .append("&secret=").append(secret) + .append("&js_code=").append(code) + .append("&grant_type=").append("authorization_code"); + String getMessageUrl = builder.toString(); + String result = HttpUtils.get(getMessageUrl); + JSONObject jsonObject = JSON.parseObject(result); + if (jsonObject.containsKey("openid")) { + String openid = jsonObject.getString("openid"); + String sessionKey = jsonObject.getString("session_key"); + System.out.println("openid:" + openid); + System.out.println("sessionKey:" + sessionKey); + return jsonObject; + } else { + throw new ServiceException(jsonObject.getString("errmsg"), jsonObject.getIntValue("errcode")); + } + } +} diff --git a/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..8af98a8 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-oauth-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,34 @@ +{ + "properties": [ + { + "name": "oauth.wx.miniapp.app-id", + "type": "java.lang.String", + "description": "微信小程序appid" + }, + { + "name": "oauth.wx.miniapp.app-secret", + "type": "java.lang.String", + "description": "微信小程序appSecret" + }, + { + "name": "oauth.wx.miniapp.url", + "type": "java.lang.String", + "description": "微信小程序认证地址" + }, + { + "name": "oauth.wx.pub.app-id", + "type": "java.lang.String", + "description": "微信公众号appid" + }, + { + "name": "oauth.wx.pub.app-secret", + "type": "java.lang.String", + "description": "微信公众号appSecret" + }, + { + "name": "oauth.wx.pub.url", + "type": "java.lang.String", + "description": "微信公众号认证地址" + } + ] +} \ No newline at end of file diff --git a/ruoyi-scene-auth/ruoyi-tfa-email/pom.xml b/ruoyi-scene-auth/ruoyi-tfa-email/pom.xml new file mode 100644 index 0000000..bb5d9ee --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-tfa-email/pom.xml @@ -0,0 +1,41 @@ + + + + ruoyi-scene-auth + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-tfa-email + + + 邮箱认证模块 + + + + + + + com.ruoyi.geekxd + ruoyi-auth-common + + + org.springframework.boot + spring-boot-starter-mail + + + + jakarta.mail + jakarta.mail-api + + + + com.sun.mail + jakarta.mail + + + + + \ No newline at end of file diff --git a/ruoyi-scene-auth/ruoyi-tfa-email/src/main/java/com/ruoyi/tfa/email/service/IMailService.java b/ruoyi-scene-auth/ruoyi-tfa-email/src/main/java/com/ruoyi/tfa/email/service/IMailService.java new file mode 100644 index 0000000..3660a62 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-tfa-email/src/main/java/com/ruoyi/tfa/email/service/IMailService.java @@ -0,0 +1,7 @@ +package com.ruoyi.tfa.email.service; + +import com.ruoyi.auth.common.service.OauthVerificationCodeService; +import com.ruoyi.auth.common.service.TfaService; + +public interface IMailService extends OauthVerificationCodeService,TfaService { +} diff --git a/ruoyi-scene-auth/ruoyi-tfa-email/src/main/java/com/ruoyi/tfa/email/service/impl/MailServiceImpl.java b/ruoyi-scene-auth/ruoyi-tfa-email/src/main/java/com/ruoyi/tfa/email/service/impl/MailServiceImpl.java new file mode 100644 index 0000000..9074c06 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-tfa-email/src/main/java/com/ruoyi/tfa/email/service/impl/MailServiceImpl.java @@ -0,0 +1,182 @@ +package com.ruoyi.tfa.email.service.impl; + +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.auth.common.enums.OauthVerificationUse; +import com.ruoyi.auth.common.utils.RandomCodeUtil; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginBody; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.domain.model.RegisterBody; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.web.service.SysLoginService; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.framework.web.service.UserDetailsServiceImpl; +import com.ruoyi.system.service.ISysUserService; +import com.ruoyi.tfa.email.service.IMailService; +import com.ruoyi.tfa.email.utils.EmailUtil; + +@Service("auth:service:mail") +public class MailServiceImpl implements IMailService { + + @Autowired + private ISysUserService userService; + @Autowired + private TokenService tokenService; + @Autowired + private UserDetailsServiceImpl userDetailsServiceImpl; + @Autowired + private SysLoginService sysLoginService; + + private static final Logger log = LoggerFactory.getLogger(MailServiceImpl.class); + + @Override + public boolean sendCode(String email, String code, OauthVerificationUse use) { + if (CacheUtils.hasKey(CacheConstants.EMAIL_CODES, use.getValue() + email)) { + throw new ServiceException("当前验证码未失效,请在1分钟后再发送"); + } + + try { + EmailUtil.sendMessage(email, "验证码邮件", "您收到的验证码是:" + code); + CacheUtils.put(CacheConstants.EMAIL_CODES, use.getValue() + email, code, 1, TimeUnit.MINUTES); + log.info("发送邮箱验证码成功:{ email: " + email + " code:" + code + "}"); + return true; + } catch (Exception e) { + throw new ServiceException("发送邮箱验证码异常:" + email); + } + } + + @Override + public boolean checkCode(String email, String code, OauthVerificationUse use) { + if (StringUtils.isEmpty(code)) { + return false; + } + String cachedCode = CacheUtils.get(CacheConstants.EMAIL_CODES, use.getValue() + email, String.class); // 从缓存中获取验证码 + boolean isValid = code.equals(cachedCode); + if (isValid) { + CacheUtils.remove(CacheConstants.EMAIL_CODES, use.getValue() + email); + } + return isValid; + } + + @Override + public void doLogin(LoginBody loginBody, boolean autoRegister) { + SysUser sysUser = userService.selectUserByEmail(loginBody.getEmail()); + if (sysUser == null && !autoRegister) { + throw new ServiceException("该邮箱未绑定用户"); + } else { + sendCode(loginBody.getEmail(), RandomCodeUtil.numberCode(6), OauthVerificationUse.LOGIN); + } + } + + @Override + public String doLoginVerify(LoginBody loginBody, boolean autoRegister) { + if (checkCode(loginBody.getEmail(), loginBody.getCode(), OauthVerificationUse.LOGIN)) { + SysUser sysUser = userService.selectUserByEmail(loginBody.getEmail()); + if (sysUser == null) { + if (!autoRegister) { + throw new ServiceException("该邮箱未绑定用户"); + } + sysUser = new SysUser(); + sysUser.setUserName(loginBody.getEmail()); + sysUser.setNickName(loginBody.getEmail()); + sysUser.setPassword(SecurityUtils.encryptPassword(loginBody.getCode())); + sysUser.setEmail(loginBody.getEmail()); + userService.registerUser(sysUser); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, + MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) userDetailsServiceImpl.createLoginUser(sysUser); + sysLoginService.recordLoginInfo(loginUser.getUserId()); + return tokenService.createToken(loginUser); + } else { + throw new ServiceException("验证码错误"); + } + } + + @Override + public void doRegister(RegisterBody registerBody) { + SysUser sysUser = userService.selectUserByEmail(registerBody.getEmail()); + if (sysUser != null) { + throw new ServiceException("该邮箱已绑定用户"); + } else { + sendCode(registerBody.getEmail(), RandomCodeUtil.numberCode(6), OauthVerificationUse.REGISTER); + } + } + + @Override + public void doRegisterVerify(RegisterBody registerBody) { + if (checkCode(registerBody.getEmail(), registerBody.getCode(), OauthVerificationUse.REGISTER)) { + SysUser sysUser = new SysUser(); + sysUser.setUserName(registerBody.getEmail()); + sysUser.setNickName(registerBody.getEmail()); + sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword())); + sysUser.setEmail(registerBody.getEmail()); + userService.registerUser(sysUser); + AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.REGISTER, + MessageUtils.message("user.register.success"))); + } else { + throw new ServiceException("验证码错误"); + } + } + + public void doReset(RegisterBody registerBody) { + SysUser sysUser = userService.selectUserByEmail(registerBody.getEmail()); + if (sysUser == null) { + throw new ServiceException("该邮箱未绑定用户"); + } else { + sendCode(registerBody.getEmail(), RandomCodeUtil.numberCode(6), OauthVerificationUse.RESET); + } + } + + public void doResetVerify(RegisterBody registerBody) { + if (checkCode(registerBody.getEmail(), registerBody.getCode(), OauthVerificationUse.RESET)) { + SysUser sysUser = userService.selectUserById(SecurityUtils.getUserId()); + sysUser.setEmail(registerBody.getEmail()); + userService.updateUser(sysUser); + } else { + throw new ServiceException("验证码错误"); + } + } + + @Override + public void doBind(LoginBody loginBody) { + SysUser sysUser = userService.selectUserByEmail(loginBody.getEmail()); + if (sysUser != null) { + throw new ServiceException("该邮箱已绑定用户"); + } + sysUser = userService.selectUserById(SecurityUtils.getUserId()); + if (!SecurityUtils.matchesPassword(loginBody.getPassword(), sysUser.getPassword())) { + throw new ServiceException("密码错误"); + } + sendCode(loginBody.getEmail(), RandomCodeUtil.numberCode(6), OauthVerificationUse.BIND); + } + + @Override + public void doBindVerify(LoginBody loginBody) { + if (checkCode(loginBody.getEmail(), loginBody.getCode(), OauthVerificationUse.BIND)) { + SysUser sysUser = userService.selectUserById(SecurityUtils.getUserId()); + if (!SecurityUtils.matchesPassword(loginBody.getPassword(), sysUser.getPassword())) { + throw new ServiceException("密码错误"); + } + sysUser.setEmail(loginBody.getEmail()); + userService.updateUser(sysUser); + } else { + throw new ServiceException("验证码错误"); + } + } + +} diff --git a/ruoyi-scene-auth/ruoyi-tfa-email/src/main/java/com/ruoyi/tfa/email/utils/EmailUtil.java b/ruoyi-scene-auth/ruoyi-tfa-email/src/main/java/com/ruoyi/tfa/email/utils/EmailUtil.java new file mode 100644 index 0000000..33739fc --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-tfa-email/src/main/java/com/ruoyi/tfa/email/utils/EmailUtil.java @@ -0,0 +1,25 @@ +package com.ruoyi.tfa.email.utils; + +import org.springframework.boot.autoconfigure.mail.MailProperties; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; + +public class EmailUtil { + public static void sendMessage(String email, String title, String message) { + if (StringUtils.isEmpty(email)) { + throw new ServiceException("邮箱不能为空"); + } + MailProperties mailProperties = SpringUtils.getBean(MailProperties.class); + JavaMailSenderImpl mailSender = SpringUtils.getBean(JavaMailSenderImpl.class); + SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); + simpleMailMessage.setSubject(title); + simpleMailMessage.setText(message); + simpleMailMessage.setFrom(mailProperties.getUsername()); + simpleMailMessage.setTo(email); + mailSender.send(simpleMailMessage); + } +} diff --git a/ruoyi-scene-auth/ruoyi-tfa-phone/pom.xml b/ruoyi-scene-auth/ruoyi-tfa-phone/pom.xml new file mode 100644 index 0000000..66d197b --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-tfa-phone/pom.xml @@ -0,0 +1,33 @@ + + + + ruoyi-scene-auth + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-tfa-phone + + + 手机号认证模块 + + + + + + + com.ruoyi.geekxd + ruoyi-auth-common + + + + com.aliyun + dysmsapi20170525 + + + + + \ No newline at end of file diff --git a/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/config/DySmsConfig.java b/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/config/DySmsConfig.java new file mode 100644 index 0000000..cd73809 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/config/DySmsConfig.java @@ -0,0 +1,47 @@ +package com.ruoyi.tfa.phone.config; + +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import com.ruoyi.tfa.phone.domain.DySmsTemplate; + +/** + * 手机号认证数据 + * + * @author Dftre + * @date 2024-04-16 + */ +@Configuration +@ConfigurationProperties("tfa.phone.dysms") +public class DySmsConfig { + private String accessKeyId; + private String accessKeySecret; + private Map template; + + public String getAccessKeyId() { + return accessKeyId; + } + + public void setAccessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + } + + public String getAccessKeySecret() { + return accessKeySecret; + } + + public void setAccessKeySecret(String accessKeySecret) { + this.accessKeySecret = accessKeySecret; + } + + public Map getTemplate() { + return template; + } + + public void setTemplate(Map template) { + this.template = template; + } + +} \ No newline at end of file diff --git a/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/domain/DySmsTemplate.java b/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/domain/DySmsTemplate.java new file mode 100644 index 0000000..8c18e38 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/domain/DySmsTemplate.java @@ -0,0 +1,46 @@ +package com.ruoyi.tfa.phone.domain; + +/** + * 手机号认证短信模板 + * + * @author Dftre + * @date 2024-04-16 + */ +public class DySmsTemplate { + /** + * 短信模板编码 + */ + private String templateCode; + /** + * 签名 + */ + private String signName; + /** + * 短信模板必需的数据名称,多个key以逗号分隔,此处配置作为校验 + */ + private String keys; + + public String getTemplateCode() { + return templateCode; + } + + public void setTemplateCode(String templateCode) { + this.templateCode = templateCode; + } + + public String getSignName() { + return signName; + } + + public void setSignName(String signName) { + this.signName = signName; + } + + public String getKeys() { + return keys; + } + + public void setKeys(String keys) { + this.keys = keys; + } +} diff --git a/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/service/DySmsService.java b/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/service/DySmsService.java new file mode 100644 index 0000000..cbcf930 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/service/DySmsService.java @@ -0,0 +1,14 @@ +package com.ruoyi.tfa.phone.service; + +import com.ruoyi.auth.common.service.OauthVerificationCodeService; +import com.ruoyi.auth.common.service.TfaService; + +/** + * 手机号认证Servcie + * + * @author zlh + * @date 2024-04-16 + */ +public interface DySmsService extends OauthVerificationCodeService, TfaService { + +} diff --git a/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/service/Impl/DySmsServiceImpl.java b/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/service/Impl/DySmsServiceImpl.java new file mode 100644 index 0000000..b9629c3 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/service/Impl/DySmsServiceImpl.java @@ -0,0 +1,193 @@ +package com.ruoyi.tfa.phone.service.Impl; + +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.auth.common.enums.OauthVerificationUse; +import com.ruoyi.auth.common.utils.RandomCodeUtil; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginBody; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.domain.model.RegisterBody; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.web.service.SysLoginService; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.framework.web.service.UserDetailsServiceImpl; +import com.ruoyi.system.service.ISysUserService; +import com.ruoyi.tfa.phone.config.DySmsConfig; +import com.ruoyi.tfa.phone.service.DySmsService; +import com.ruoyi.tfa.phone.utils.DySmsUtil; + +/** + * 手机号认证Servcie + * + * @author zlh + * @date 2024-04-16 + */ +@Service("auth:service:dySms") +public class DySmsServiceImpl implements DySmsService { + + @Autowired + private ISysUserService userService; + @Autowired + private UserDetailsServiceImpl userDetailsServiceImpl; + @Autowired + private TokenService tokenService; + @Autowired + private SysLoginService sysLoginService; + @Autowired + private DySmsConfig dySmsConfig; + + private static final Logger log = LoggerFactory.getLogger(DySmsServiceImpl.class); + + @Override + public boolean sendCode(String phone, String code, OauthVerificationUse use) { + if (CacheUtils.hasKey(CacheConstants.PHONE_CODES, use.getValue() + phone)) { + throw new ServiceException("当前验证码未失效,请在1分钟后再发送"); + } + + try { + JSONObject templateParams = new JSONObject(); + templateParams.put("code", code); + DySmsUtil.sendSms(phone, dySmsConfig.getTemplate().get("VerificationCode"), templateParams); + CacheUtils.put(CacheConstants.PHONE_CODES, use.getValue() + phone, code, 1, TimeUnit.MINUTES); + log.info("发送手机验证码成功:{ phone: " + phone + " code:" + code + "}"); + return true; + } catch (Exception e) { + throw new ServiceException("发送手机验证码异常:" + phone); + } + } + + @Override + public boolean checkCode(String phone, String code, OauthVerificationUse use) { + if (StringUtils.isEmpty(code)) { + return false; + } + String cachedCode = CacheUtils.get(CacheConstants.PHONE_CODES, use.getValue() + phone, String.class); // 从缓存中获取验证码 + boolean isValid = code.equals(cachedCode); + if (isValid) { + CacheUtils.remove(CacheConstants.PHONE_CODES, use.getValue() + phone); + } + return isValid; + } + + @Override + public void doLogin(LoginBody loginBody, boolean autoRegister) { + SysUser sysUser = userService.selectUserByPhone(loginBody.getPhonenumber()); + if (sysUser == null && !autoRegister) { + throw new ServiceException("该手机号未绑定用户"); + } else { + sendCode(loginBody.getPhonenumber(), RandomCodeUtil.numberCode(6), OauthVerificationUse.LOGIN); + } + } + + @Override + public String doLoginVerify(LoginBody loginBody, boolean autoRegister) { + if (checkCode(loginBody.getPhonenumber(), loginBody.getCode(), OauthVerificationUse.LOGIN)) { + SysUser sysUser = userService.selectUserByPhone(loginBody.getPhonenumber()); + if (sysUser == null) { + if (!autoRegister) { + throw new ServiceException("该手机号未绑定用户"); + } + sysUser = new SysUser(); + sysUser.setUserName(loginBody.getPhonenumber()); + sysUser.setNickName(loginBody.getPhonenumber()); + sysUser.setPassword(SecurityUtils.encryptPassword(loginBody.getCode())); + sysUser.setPhonenumber(loginBody.getPhonenumber()); + userService.registerUser(sysUser); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, + MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) userDetailsServiceImpl.createLoginUser(sysUser); + sysLoginService.recordLoginInfo(loginUser.getUserId()); + return tokenService.createToken(loginUser); + } else { + throw new ServiceException("验证码错误"); + } + } + + @Override + public void doRegister(RegisterBody registerBody) { + SysUser sysUser = userService.selectUserByPhone(registerBody.getPhonenumber()); + if (sysUser != null) { + throw new ServiceException("该手机号已绑定用户"); + } else { + sendCode(registerBody.getPhonenumber(), RandomCodeUtil.numberCode(6), OauthVerificationUse.REGISTER); + } + } + + @Override + public void doRegisterVerify(RegisterBody registerBody) { + if (checkCode(registerBody.getPhonenumber(), registerBody.getCode(), OauthVerificationUse.REGISTER)) { + SysUser sysUser = new SysUser(); + sysUser.setUserName(registerBody.getPhonenumber()); + sysUser.setNickName(registerBody.getUsername()); + sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword())); + sysUser.setPhonenumber(registerBody.getPhonenumber()); + userService.registerUser(sysUser); + AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.REGISTER, + MessageUtils.message("user.register.success"))); + } else { + throw new ServiceException("验证码错误"); + } + } + + public void doReset(RegisterBody registerBody) { + SysUser sysUser = userService.selectUserByPhone(registerBody.getPhonenumber()); + if (sysUser == null) { + throw new ServiceException("该手机号未绑定用户"); + } else { + sendCode(registerBody.getPhonenumber(), RandomCodeUtil.numberCode(6), OauthVerificationUse.RESET); + } + } + + public void doResetVerify(RegisterBody registerBody) { + if (checkCode(registerBody.getPhonenumber(), registerBody.getCode(), OauthVerificationUse.RESET)) { + SysUser sysUser = userService.selectUserById(SecurityUtils.getUserId()); + sysUser.setPhonenumber(registerBody.getPhonenumber()); + userService.updateUser(sysUser); + } else { + throw new ServiceException("验证码错误"); + } + } + + @Override + public void doBind(LoginBody loginBody) { + SysUser sysUser = userService.selectUserByPhone(loginBody.getPhonenumber()); + if (sysUser != null) { + throw new ServiceException("该手机号已绑定用户"); + } + sysUser = userService.selectUserById(SecurityUtils.getUserId()); + if (!SecurityUtils.matchesPassword(loginBody.getPassword(), sysUser.getPassword())) { + throw new ServiceException("密码错误"); + } + sendCode(loginBody.getPhonenumber(), RandomCodeUtil.numberCode(6), OauthVerificationUse.BIND); + } + + @Override + public void doBindVerify(LoginBody loginBody) { + if (checkCode(loginBody.getPhonenumber(), loginBody.getCode(), OauthVerificationUse.BIND)) { + SysUser sysUser = userService.selectUserById(SecurityUtils.getUserId()); + if (!SecurityUtils.matchesPassword(loginBody.getPassword(), sysUser.getPassword())) { + throw new ServiceException("密码错误"); + } + sysUser.setPhonenumber(loginBody.getPhonenumber()); + userService.updateUser(sysUser); + } else { + throw new ServiceException("验证码错误"); + } + } +} diff --git a/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/utils/DySmsUtil.java b/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/utils/DySmsUtil.java new file mode 100644 index 0000000..6154b47 --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/java/com/ruoyi/tfa/phone/utils/DySmsUtil.java @@ -0,0 +1,88 @@ +package com.ruoyi.tfa.phone.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson2.JSONObject; +import com.aliyun.dysmsapi20170525.Client; +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; +import com.aliyun.tea.TeaException; +import com.aliyun.teaopenapi.models.Config; +import com.aliyun.teautil.Common; +import com.aliyun.teautil.models.RuntimeOptions; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.tfa.phone.config.DySmsConfig; +import com.ruoyi.tfa.phone.domain.DySmsTemplate; + +public class DySmsUtil { + protected final static Logger logger = LoggerFactory.getLogger(DySmsUtil.class); + + /** + * 使用AK&SK初始化账号Client + * + * @param accessKeyId + * @param accessKeySecret + * @return Client + * @throws Exception + */ + private static Client createClient() throws Exception { + DySmsConfig dySmsConfig = SpringUtils.getBean(DySmsConfig.class); + Config config = new Config() + // 必填,您的 AccessKey ID + .setAccessKeyId(dySmsConfig.getAccessKeyId()) + // 必填,您的 AccessKey Secret + .setAccessKeySecret(dySmsConfig.getAccessKeySecret()); + config.endpoint = "dysmsapi.aliyuncs.com"; + return new Client(config); + } + + /** + * 验证参数 + * + * @param templateParamJson + * @param dySmsTemplate + * @throws Exception + */ + private static void validateParam(JSONObject templateParamJson, DySmsTemplate dySmsTemplate) { + String keys = dySmsTemplate.getKeys(); + String[] keyArr = keys.split(","); + for (String item : keyArr) { + if (!templateParamJson.containsKey(item)) { + throw new RuntimeException("模板缺少参数:" + item); + } + } + } + + public static void sendSms(String phone, DySmsTemplate dySmsTemplate, JSONObject templateParamJson) + throws Exception { + if (StringUtils.isEmpty(phone)) { + throw new ServiceException("手机号不能为空"); + } + validateParam(templateParamJson, dySmsTemplate); + Client client = createClient(); + SendSmsRequest sendSmsRequest = new SendSmsRequest() + .setPhoneNumbers(phone) + .setSignName(dySmsTemplate.getSignName()) + .setTemplateCode(dySmsTemplate.getTemplateCode()) + .setTemplateParam(templateParamJson.toJSONString()); + try { + // 复制代码运行请自行打印 API 的返回值 + SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, new RuntimeOptions()); + if ("OK".equals(sendSmsResponse.getBody().getCode())) { + logger.info("短信接口返回的数据--- {}", sendSmsResponse.getBody().getMessage()); + } else { + logger.error("短信接口返回的数据--- {}", sendSmsResponse.getBody().getMessage()); + throw new ServiceException(sendSmsResponse.getBody().getMessage()); + } + } catch (TeaException error) { + // 错误 message + System.out.println(error.getMessage()); + // 诊断地址 + System.out.println(error.getData().get("Recommend")); + Common.assertAsString(error.message); + } + } +} \ No newline at end of file diff --git a/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..794fc0e --- /dev/null +++ b/ruoyi-scene-auth/ruoyi-tfa-phone/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,19 @@ +{ + "properties": [ + { + "name": "tfa.phone.dysms.access-key-id", + "type": "java.lang.String", + "description": "阿里云短信 AccessKey ID" + }, + { + "name": "tfa.phone.dysms.access-key-secret", + "type": "java.lang.String", + "description": "阿里云短信 AccessKey Secret" + }, + { + "name": "tfa.phone.dysms.template", + "type": "java.util.Map", + "description": "短信模板" + } + ] +} \ No newline at end of file diff --git a/ruoyi-scene-file/pom.xml b/ruoyi-scene-file/pom.xml new file mode 100644 index 0000000..85e2d0f --- /dev/null +++ b/ruoyi-scene-file/pom.xml @@ -0,0 +1,72 @@ + + + + ruoyi + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-scene-file + + + 3.18.1 + 8.2.1 + + + + 文件模块 + + + + + com.ruoyi.geekxd + ruoyi-file-common + ${ruoyi.version} + + + + io.minio + minio + ${minio.version} + + + + + com.aliyun.oss + aliyun-sdk-oss + ${aliyunoss.version} + + + + com.ruoyi.geekxd + ruoyi-file-local + ${ruoyi.version} + + + + com.ruoyi.geekxd + ruoyi-file-oss-alibaba + ${ruoyi.version} + + + + com.ruoyi.geekxd + ruoyi-file-minio + ${ruoyi.version} + + + + + + + + ruoyi-file-common + ruoyi-file-local + ruoyi-file-minio + ruoyi-file-oss-alibaba + ruoyi-file-starter + + pom + \ No newline at end of file diff --git a/ruoyi-scene-file/ruoyi-file-common/pom.xml b/ruoyi-scene-file/ruoyi-file-common/pom.xml new file mode 100644 index 0000000..b2b2631 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/pom.xml @@ -0,0 +1,24 @@ + + + + ruoyi-scene-file + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-file-common + + + 文件模块公共依赖 + + + + + com.ruoyi.geekxd + ruoyi-common + + + + \ No newline at end of file diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/domain/SysFileInfo.java b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/domain/SysFileInfo.java new file mode 100644 index 0000000..7128836 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/domain/SysFileInfo.java @@ -0,0 +1,60 @@ +package com.ruoyi.file.domain; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 文件对象 sys_file_info + * + * @author ruoyi + * @date 2025-04-25 + */ +@Schema(description = "文件对象") +@Data +@EqualsAndHashCode(callSuper = true) +public class SysFileInfo extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 文件主键 */ + @Schema(title = "文件主键") + private Long fileId; + + /** 原始文件名 */ + @Schema(title = "原始文件名") + @Excel(name = "原始文件名") + private String fileName; + + /** 统一逻辑路径(/开头) */ + @Schema(title = "统一逻辑路径(/开头)") + @Excel(name = "统一逻辑路径", readConverterExp = "/=开头") + private String filePath; + + /** 存储类型(local/minio/oss) */ + @Schema(title = "存储类型(local/minio/oss)") + @Excel(name = "存储类型", readConverterExp = "l=ocal/minio/oss") + private String storageType; + + /** 文件类型/后缀 */ + @Schema(title = "文件类型/后缀") + @Excel(name = "文件类型/后缀") + private String fileType; + + /** 文件大小(字节) */ + @Schema(title = "文件大小(字节)") + @Excel(name = "文件大小", readConverterExp = "字=节") + private Long fileSize; + + /** 文件MD5 */ + @Schema(title = "文件MD5") + @Excel(name = "文件MD5") + private String md5; + + /** 删除标志(0代表存在 2代表删除) */ + @Schema(title = "删除标志(0代表存在 2代表删除)") + private String delFlag; + +} diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/domain/SysFilePartETag.java b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/domain/SysFilePartETag.java new file mode 100644 index 0000000..cc18da0 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/domain/SysFilePartETag.java @@ -0,0 +1,61 @@ +package com.ruoyi.file.domain; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Data; + +@Data +public class SysFilePartETag implements Serializable { + private static final long serialVersionUID = 2471854027355307627L; + private Integer partNumber; + @JsonProperty("ETag") + private String eTag; + private Long partSize; + private Long partCRC; + private Long fileSize; + private String filePath; + private Long taskId; + + public void seteTag(String eTag) { + this.eTag = eTag; + } + + public SysFilePartETag() { + } + + public SysFilePartETag(Integer partNumber, String eTag) { + this.partNumber = partNumber; + this.eTag = eTag; + } + + public SysFilePartETag(Integer partNumber, String eTag, long partSize, Long partCRC) { + this.partNumber = partNumber; + this.eTag = eTag; + this.partSize = partSize; + this.partCRC = partCRC; + } + + public int hashCode() { + int result = 1; + result = 31 * result + (this.eTag == null ? 0 : this.eTag.hashCode()); + result = 31 * result + this.partNumber; + return result; + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof SysFilePartETag)) { + return false; + } else { + SysFilePartETag other = (SysFilePartETag) obj; + if (this.partNumber != other.partNumber) { + return false; + } else { + return this.eTag == null ? other.eTag == null : this.eTag.equals(other.eTag); + } + } + } +} diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/mapper/SysFileInfoMapper.java b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/mapper/SysFileInfoMapper.java new file mode 100644 index 0000000..b8e01f2 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/mapper/SysFileInfoMapper.java @@ -0,0 +1,62 @@ +package com.ruoyi.file.mapper; + +import java.util.List; + +import com.ruoyi.file.domain.SysFileInfo; + +/** + * 文件Mapper接口 + * + * @author ruoyi + * @date 2025-04-25 + */ +public interface SysFileInfoMapper +{ + /** + * 查询文件 + * + * @param fileId 文件主键 + * @return 文件 + */ + public SysFileInfo selectSysFileInfoByFileId(Long fileId); + + /** + * 查询文件列表 + * + * @param sysFileInfo 文件 + * @return 文件集合 + */ + public List selectSysFileInfoList(SysFileInfo sysFileInfo); + + /** + * 新增文件 + * + * @param sysFileInfo 文件 + * @return 结果 + */ + public int insertSysFileInfo(SysFileInfo sysFileInfo); + + /** + * 修改文件 + * + * @param sysFileInfo 文件 + * @return 结果 + */ + public int updateSysFileInfo(SysFileInfo sysFileInfo); + + /** + * 删除文件 + * + * @param fileId 文件主键 + * @return 结果 + */ + public int deleteSysFileInfoByFileId(Long fileId); + + /** + * 批量删除文件 + * + * @param fileIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSysFileInfoByFileIds(Long[] fileIds); +} diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/service/ISysFileInfoService.java b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/service/ISysFileInfoService.java new file mode 100644 index 0000000..76f9acc --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/service/ISysFileInfoService.java @@ -0,0 +1,88 @@ +package com.ruoyi.file.service; + +import java.util.Date; +import java.util.List; + +import org.springframework.web.multipart.MultipartFile; + +import com.ruoyi.common.utils.sign.Md5Utils; +import com.ruoyi.file.domain.SysFileInfo; + +/** + * 文件Service接口 + * + * @author ruoyi + * @date 2025-04-25 + */ +public interface ISysFileInfoService { + /** + * 查询文件 + * + * @param fileId 文件主键 + * @return 文件 + */ + public SysFileInfo selectSysFileInfoByFileId(Long fileId); + + /** + * 查询文件列表 + * + * @param sysFileInfo 文件 + * @return 文件集合 + */ + public List selectSysFileInfoList(SysFileInfo sysFileInfo); + + /** + * 新增文件 + * + * @param sysFileInfo 文件 + * @return 结果 + */ + public int insertSysFileInfo(SysFileInfo sysFileInfo); + + /** + * 新增文件 + * + * @param file + * @return 结果 + */ + default public SysFileInfo buildSysFileInfo(MultipartFile file) { + String fileType = null; + if (file.getOriginalFilename() != null && file.getOriginalFilename().contains(".")) { + fileType = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.') + 1); + } + SysFileInfo fileInfo = new SysFileInfo(); + String md5 = Md5Utils.getMd5(file); + fileInfo.setFileName(file.getOriginalFilename()); + fileInfo.setFileType(fileType); + fileInfo.setFileSize(file.getSize()); + fileInfo.setMd5(md5); + fileInfo.setCreateTime(new Date()); + fileInfo.setUpdateTime(new Date()); + fileInfo.setDelFlag("0"); + return fileInfo; + } + + /** + * 修改文件 + * + * @param sysFileInfo 文件 + * @return 结果 + */ + public int updateSysFileInfo(SysFileInfo sysFileInfo); + + /** + * 批量删除文件 + * + * @param fileIds 需要删除的文件主键集合 + * @return 结果 + */ + public int deleteSysFileInfoByFileIds(Long[] fileIds); + + /** + * 删除文件信息 + * + * @param fileId 文件主键 + * @return 结果 + */ + public int deleteSysFileInfoByFileId(Long fileId); +} diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/service/StorageService.java b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/service/StorageService.java new file mode 100644 index 0000000..6c6ccd3 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/service/StorageService.java @@ -0,0 +1,206 @@ +package com.ruoyi.file.service; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import org.springframework.http.MediaType; +import org.springframework.web.multipart.MultipartFile; + +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.common.utils.file.MimeTypeUtils; +import com.ruoyi.common.utils.sign.Md5Utils; +import com.ruoyi.file.domain.SysFilePartETag; +import com.ruoyi.file.storage.StorageBucket; +import com.ruoyi.file.storage.StorageEntity; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 存储操作业务 + */ +public class StorageService { + + private StorageBucket storageBucket; + private String[] allowedExtension = MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION; + private Boolean fastUpload = true; + private Integer expireTime = 3600; + private Long MAX_FILE_SIZE = 500 * 1024 * 1024L; // 500MB + + public StorageService(StorageBucket storageBucket) { + this.storageBucket = storageBucket; + } + + public void setAllowedExtension(String[] allowedExtension) { + this.allowedExtension = allowedExtension; + } + + /** + * 上传文件(指定文件路径) + * + * @param filePath 指定上传文件的路径 + * @param file 上传的文件 + * @return 上传后的访问链接 + * @throws Exception 比如读写文件出错时 + * + */ + public String upload(String filePath, MultipartFile file) throws Exception { + FileUtils.assertAllowed(file, allowedExtension); + if (file.getSize() > MAX_FILE_SIZE) { + throw new IllegalArgumentException("文件过大"); + } + if (this.fastUpload) { + String md5 = Md5Utils.getMd5(file); + String pathForMd5 = CacheUtils.get(CacheConstants.FILE_MD5_PATH_KEY, md5, String.class); + if (StringUtils.isNotEmpty(pathForMd5)) { + filePath = pathForMd5; + } else { + this.storageBucket.put(filePath, file); + CacheUtils.put(CacheConstants.FILE_MD5_PATH_KEY, md5, filePath); + CacheUtils.put(CacheConstants.FILE_PATH_MD5_KEY, filePath, md5); + } + } else { + this.storageBucket.put(filePath, file); + } + return generateUrl(filePath); + } + + /** + * 下载文件 + * + * @param filePath 文件路径 + * @return 返回文件输入流 + * @throws Exception 比如读写文件出错时 + * + */ + public InputStream downLoad(String filePath) throws Exception { + return this.storageBucket.get(filePath).getInputStream(); + } + + /** + * 根据文件路径下载 + * + * @param fileUrl 下载文件路径 + * @param outputStream 需要输出到的输出流 + * @return 文件名称 + * @throws IOException + */ + public void downLoad(String filePath, OutputStream outputStream) throws Exception { + InputStream inputStream = downLoad(filePath); + FileUtils.writeBytes(inputStream, outputStream); + } + + /** + * 下载文件 + * + * @param filePath 文件路径 + * @return 返回文件输入流 + * @throws Exception 比如读写文件出错时 + * + */ + public void downLoad(String filePath, HttpServletResponse response) throws Exception { + StorageEntity fileEntity = this.storageBucket.get(filePath); + InputStream inputStream = fileEntity.getInputStream(); + OutputStream outputStream = response.getOutputStream(); + FileUtils.setAttachmentResponseHeader(response, FileUtils.getName(fileEntity.getFilePath())); + response.setContentLengthLong(fileEntity.getByteCount()); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.writeBytes(inputStream, outputStream); + } + + /** + * 获取文件实体对象 + * + * @param filePath 文件路径 + * @return 文件对象 + * @throws Exception + */ + public StorageEntity getFile(String filePath) throws Exception { + return this.storageBucket.get(filePath); + } + + /** + * 删除文件 + * + * @param filePath 文件路径 + * @return 返回是否删除成功 + * @throws Exception 比如读写文件出错时 + * + */ + public boolean deleteFile(String filePath) throws Exception { + this.storageBucket.remove(filePath); + if (this.fastUpload) { + String md5 = CacheUtils.get(CacheConstants.FILE_PATH_MD5_KEY, filePath, String.class); + if (StringUtils.isNotEmpty(md5)) { + CacheUtils.remove(CacheConstants.FILE_PATH_MD5_KEY, filePath); + CacheUtils.remove(CacheConstants.FILE_MD5_PATH_KEY, md5); + } + } + return true; + } + + /** + * 生成文件访问链接 + * + * @param filePath 文件路径 + * @return 返回文件访问链接 + * @throws Exception 比如读写文件出错时 + * + */ + public String generateUrl(String filePath) throws Exception { + if ("public".equals(this.storageBucket.getPermission())) { + return this.storageBucket.generatePublicURL(filePath).toString(); + } else { + return this.storageBucket.generatePresignedUrl(filePath, expireTime).toString(); + } + } + + /** + * 初始化分片上传 + * + * @param filePath 文件路径 + * @return 返回uploadId + */ + public String initMultipartUpload(String filePath, Long fileSize) throws Exception { + if (fileSize > MAX_FILE_SIZE) { + throw new IllegalArgumentException("文件过大"); + } + return this.storageBucket.initMultipartUpload(filePath); + } + + /** + * 上传分片 + * + * @param filePath 文件路径 + * @param uploadId 上传ID + * @param partNumber 分片序号 + * @param partSize 分片大小 + * @param inputStream 分片数据流 + * @return 分片的ETag + */ + public String uploadPart(String filePath, String uploadId, int partNumber, long partSize, InputStream inputStream) + throws Exception { + return this.storageBucket.uploadPart(filePath, uploadId, partNumber, partSize, inputStream).getETag(); + } + + /** + * 完成分片上传 + * + * @param filePath 文件路径 + * @param uploadId 上传ID + * @param partETags 分片的ETag列表 + * @return 文件的最终路径 + */ + public String completeMultipartUpload(String filePath, String uploadId, List partETags) + throws Exception { + if (partETags == null || partETags.isEmpty()) { + throw new IllegalArgumentException("分片标识列表不能为空"); + } + return this.storageBucket.completeMultipartUpload(filePath, uploadId, partETags); + } + +} diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/service/impl/SysFileInfoServiceImpl.java b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/service/impl/SysFileInfoServiceImpl.java new file mode 100644 index 0000000..6b65eb9 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/service/impl/SysFileInfoServiceImpl.java @@ -0,0 +1,98 @@ +package com.ruoyi.file.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.file.domain.SysFileInfo; +import com.ruoyi.file.mapper.SysFileInfoMapper; +import com.ruoyi.file.service.ISysFileInfoService; + +/** + * 文件Service业务层处理 + * + * @author ruoyi + * @date 2025-04-25 + */ +@Service +public class SysFileInfoServiceImpl implements ISysFileInfoService +{ + @Autowired + private SysFileInfoMapper sysFileInfoMapper; + + /** + * 查询文件 + * + * @param fileId 文件主键 + * @return 文件 + */ + @Override + public SysFileInfo selectSysFileInfoByFileId(Long fileId) + { + return sysFileInfoMapper.selectSysFileInfoByFileId(fileId); + } + + /** + * 查询文件列表 + * + * @param sysFileInfo 文件 + * @return 文件 + */ + @Override + public List selectSysFileInfoList(SysFileInfo sysFileInfo) + { + return sysFileInfoMapper.selectSysFileInfoList(sysFileInfo); + } + + /** + * 新增文件 + * + * @param sysFileInfo 文件 + * @return 结果 + */ + @Override + public int insertSysFileInfo(SysFileInfo sysFileInfo) + { + sysFileInfo.setCreateTime(DateUtils.getNowDate()); + return sysFileInfoMapper.insertSysFileInfo(sysFileInfo); + } + + /** + * 修改文件 + * + * @param sysFileInfo 文件 + * @return 结果 + */ + @Override + public int updateSysFileInfo(SysFileInfo sysFileInfo) + { + sysFileInfo.setUpdateTime(DateUtils.getNowDate()); + return sysFileInfoMapper.updateSysFileInfo(sysFileInfo); + } + + /** + * 批量删除文件 + * + * @param fileIds 需要删除的文件主键 + * @return 结果 + */ + @Override + public int deleteSysFileInfoByFileIds(Long[] fileIds) + { + return sysFileInfoMapper.deleteSysFileInfoByFileIds(fileIds); + } + + /** + * 删除文件信息 + * + * @param fileId 文件主键 + * @return 结果 + */ + @Override + public int deleteSysFileInfoByFileId(Long fileId) + { + return sysFileInfoMapper.deleteSysFileInfoByFileId(fileId); + } +} diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/storage/StorageBucket.java b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/storage/StorageBucket.java new file mode 100644 index 0000000..26a448f --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/storage/StorageBucket.java @@ -0,0 +1,112 @@ +package com.ruoyi.file.storage; + +import java.io.InputStream; +import java.net.URL; +import java.util.List; + +import org.springframework.web.multipart.MultipartFile; + +import com.ruoyi.file.domain.SysFilePartETag; + +/** 存储桶 */ +public interface StorageBucket { + + /** + * 获取文件实例 + * + * @param filePath + * @return + * @throws Exception + */ + StorageEntity get(String filepath) throws Exception; + + /** + * 上传文件 + * + * @param filePath + * @param file + * @throws Exception + */ + void put(String filePath, MultipartFile file) throws Exception; + + /** + * 删除文件 + * + * @param filePath + * @throws Exception + */ + void remove(String filePath) throws Exception; + + /** + * 生成预签名URL + * + * @param filePath + * @param expireTime 过期时间(秒) + * @return + * @throws Exception + */ + URL generatePresignedUrl(String filePath, int expireTime) throws Exception; + + /** + * 获取文件的公开访问方式的URL + * + * @param filePath 文件路径 + * @return 公开访问URL + */ + URL generatePublicURL(String filePath) throws Exception; + + /** + * 获取存储渠道权限 + * + * @return public/private + */ + String getPermission(); + + /** + * 获取文件的默认访问方式的URL + * + * @param filePath + * @return + * @throws Exception + */ + default URL getUrl(String filePath) throws Exception { + if ("public".equals(getPermission())) { + return generatePublicURL(filePath); + } else { + return generatePresignedUrl(filePath, 3600); + } + }; + + /** + * 初始化分片上传 + * + * @param filePath 文件路径 + * @return 返回uploadId + */ + public String initMultipartUpload(String filePath) throws Exception; + + /** + * 上传分片 + * + * @param filePath 文件路径 + * @param uploadId 上传ID + * @param partNumber 分片序号 + * @param partSize 分片大小 + * @param inputStream 分片数据流 + * @return 分片的ETag + */ + public SysFilePartETag uploadPart(String filePath, String uploadId, int partNumber, long partSize, + InputStream inputStream) + throws Exception; + + /** + * 完成分片上传 + * + * @param filePath 文件路径 + * @param uploadId 上传ID + * @param partETags 分片的ETag列表 + * @return 文件的最终路径 + */ + public String completeMultipartUpload(String filePath, String uploadId, List partETags) + throws Exception; +} diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/storage/StorageEntity.java b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/storage/StorageEntity.java new file mode 100644 index 0000000..046420e --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/storage/StorageEntity.java @@ -0,0 +1,13 @@ +package com.ruoyi.file.storage; + +import java.io.InputStream; + +import lombok.Data; + +/** 存储实体 */ +@Data +public class StorageEntity { + private InputStream inputStream; + private Long byteCount; + private String filePath; +} diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/storage/StorageManagement.java b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/storage/StorageManagement.java new file mode 100644 index 0000000..e6918b9 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/storage/StorageManagement.java @@ -0,0 +1,32 @@ +package com.ruoyi.file.storage; + +import java.util.Map; + +import org.springframework.beans.factory.InitializingBean; + +/** 存储管理器 */ +public interface StorageManagement extends InitializingBean { + + /** + * 获取主存储桶 + * + * @return + */ + StorageBucket getPrimaryBucket(); + + /** + * 获取存储桶 + * + * @param clientName 客户端名称 + * @return 存储桶 + */ + StorageBucket getBucket(String clientName); + + /** + * 获取存储桶 + * + * @return 存储桶 + */ + public Map getClient(); + +} diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/utils/FileOperateUtils.java b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/utils/FileOperateUtils.java new file mode 100644 index 0000000..4d4500f --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/utils/FileOperateUtils.java @@ -0,0 +1,149 @@ +package com.ruoyi.file.utils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.springframework.http.MediaType; +import org.springframework.web.multipart.MultipartFile; + +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.common.utils.file.MimeTypeUtils; +import com.ruoyi.file.service.StorageService; +import com.ruoyi.file.storage.StorageEntity; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 文件操作工具类 + * + * @author ruoyi + */ +public class FileOperateUtils { + + /** + * 以默认配置进行文件上传 + * + * @param file 上传的文件 + * @return 文件路径 + * @throws Exception + */ + public static final String upload(MultipartFile file) throws IOException { + return upload(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + + /** + * 以默认配置进行文件上传 + * + * @param file 上传的文件 + * @return 文件路径 + * @throws Exception + */ + public static final String upload(MultipartFile file, String fileName) throws Exception { + return upload(DateUtils.datePath() + File.separator + fileName, file); + } + + /** + * 以默认配置进行文件上传 + * + * @param file 上传的文件 + * @param allowedExtension 允许的扩展名 + * @return 文件路径 + * @throws Exception + */ + public static final String upload(MultipartFile file, String[] allowedExtension) + throws IOException { + return upload(FileUtils.fastFilePath(file), file, allowedExtension); + } + + /** + * 根据文件路径上传 + * + * @param filePath 上传文件的路径 + * @param file 上传的文件 + * @return 文件名称 + * @throws IOException + */ + public static final String upload(String filePath, MultipartFile file) throws Exception { + return upload(filePath, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + + /** + * 根据文件路径上传 + * + * @param filePath 上传文件的路径 + * @param file 上传的文件 + * @param allowedExtension 允许的扩展名 + * @return 访问链接 + * @throws IOException + */ + public static final String upload(String filePath, MultipartFile file, String[] allowedExtension) + throws IOException { + try { + StorageService fileService = new StorageService(StorageUtils.getPrimaryStorageBucket()); + fileService.setAllowedExtension(allowedExtension); + return fileService.upload(filePath, file); + } catch (Exception e) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 根据文件路径下载 + * + * @param fileUrl 下载文件路径 + * @param outputStream 需要输出到的输出流 + * @return 文件名称 + * @throws IOException + */ + public static final void downLoad(String filePath, OutputStream outputStream) throws Exception { + StorageService fileService = new StorageService(StorageUtils.getPrimaryStorageBucket()); + InputStream inputStream = fileService.downLoad(filePath); + FileUtils.writeBytes(inputStream, outputStream); + } + + /** + * 根据文件路径下载 + * + * @param filepath 下载文件路径 + * @param response 响应 + * @return 文件名称 + * @throws IOException + */ + public static final void downLoad(String filePath, HttpServletResponse response) throws Exception { + StorageService fileService = new StorageService(StorageUtils.getPrimaryStorageBucket()); + StorageEntity fileEntity = fileService.getFile(filePath); + InputStream inputStream = fileEntity.getInputStream(); + OutputStream outputStream = response.getOutputStream(); + FileUtils.setAttachmentResponseHeader(response, FileUtils.getName(fileEntity.getFilePath())); + response.setContentLengthLong(fileEntity.getByteCount()); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.writeBytes(inputStream, outputStream); + } + + /** + * 根据文件路径删除 + * + * @param filePath 下载文件路径 + * @return 是否成功 + * @throws IOException + */ + public static final boolean deleteFile(String filePath) throws Exception { + StorageService fileService = new StorageService(StorageUtils.getPrimaryStorageBucket()); + return fileService.deleteFile(filePath); + } + + /** + * 获取文件的访问链接 + * + * @param filePath 文件路径 + * @return 访问链接 + * @throws Exception + */ + public static String getURL(String filePath) throws Exception { + StorageService fileService = new StorageService(StorageUtils.getPrimaryStorageBucket()); + return fileService.generateUrl(filePath); + } +} diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/utils/StorageUtils.java b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/utils/StorageUtils.java new file mode 100644 index 0000000..9b0b49b --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/java/com/ruoyi/file/utils/StorageUtils.java @@ -0,0 +1,84 @@ +package com.ruoyi.file.utils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.file.storage.StorageBucket; +import com.ruoyi.file.storage.StorageManagement; + +import jakarta.annotation.PostConstruct; + +@Component +public class StorageUtils { + private static final Logger logger = LoggerFactory.getLogger(StorageUtils.class); + + private static Map storageManagementMap; + + public static String getPrimaryStorageType() { + return RuoYiConfig.getFileServer(); + } + + public static StorageBucket getPrimaryStorageBucket() { + return storageManagementMap.get(RuoYiConfig.getFileServer()).getPrimaryBucket(); + } + + /** + * 获取指定存储类型和客户端名称的存储桶 + * + * @param storageType 存储类型 + * @param clientName 客户端名称 + * @return 存储桶 + */ + public static StorageBucket getStorageBucket(String storageType, String clientName) { + StorageManagement storageManagement = storageManagementMap.get(storageType); + if (storageManagement == null) { + throw new IllegalArgumentException("Storage management for type " + storageType + " not found"); + } + StorageBucket storageBucket = storageManagement.getBucket(clientName); + if (storageBucket == null) { + throw new IllegalArgumentException( + "Storage bucket for client " + clientName + " not found in type " + storageType); + } + return storageBucket; + } + + /** + * 获取所有可用存储渠道及其client列表 + * + * @return + */ + public static Map> getClientList() { + Map> result = new HashMap<>(); + for (String storageType : storageManagementMap.keySet()) { + StorageManagement config = storageManagementMap.get(storageType); + result.put(storageType, new ArrayList<>(config.getClient().keySet())); + } + return result; + } + + @Autowired(required = false) + private void setStorageManagementMap(Map storageManagementMap) { + StorageUtils.storageManagementMap = storageManagementMap; + } + + @PostConstruct + private void init() { + if (StorageUtils.storageManagementMap == null) { + StorageUtils.storageManagementMap = new HashMap<>(); + logger.warn("请注意,没有加载任何存储服务"); + } else { + StorageUtils.storageManagementMap.forEach((k, v) -> { + logger.info("已加载存储服务 {}", k); + }); + } + } + +} diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/ruoyi-scene-file/ruoyi-file-common/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..97ad885 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,19 @@ +{ + "properties": [ + { + "name": "local.enable", + "type": "java.lang.Boolean", + "description": "是否开启local" + }, + { + "name": "local.primary", + "type": "java.lang.String", + "description": "默认存储名称" + }, + { + "name": "local.client", + "type": "java.util.Map", + "description": "储存桶" + } + ] +} \ No newline at end of file diff --git a/ruoyi-scene-file/ruoyi-file-common/src/main/resources/mapper/file/SysFileInfoMapper.xml b/ruoyi-scene-file/ruoyi-file-common/src/main/resources/mapper/file/SysFileInfoMapper.xml new file mode 100644 index 0000000..dd49ada --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-common/src/main/resources/mapper/file/SysFileInfoMapper.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + select + sfi.file_id, + sfi.file_name, + sfi.file_path, + sfi.storage_type, + sfi.file_type, + sfi.file_size, + sfi.md5, + sfi.create_by, + sfi.create_time, + sfi.update_by, + sfi.update_time, + sfi.remark, + sfi.del_flag + from sys_file_info sfi + + + + + + + + insert into sys_file_info + + file_name, + file_path, + storage_type, + file_type, + file_size, + md5, + create_by, + create_time, + update_by, + update_time, + remark, + del_flag, + + + #{fileName}, + #{filePath}, + #{storageType}, + #{fileType}, + #{fileSize}, + #{md5}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{delFlag}, + + + + + update sys_file_info + + file_name = #{fileName}, + file_path = #{filePath}, + storage_type = #{storageType}, + file_type = #{fileType}, + file_size = #{fileSize}, + md5 = #{md5}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + del_flag = #{delFlag}, + + where sys_file_info.file_id = #{fileId} + + + + delete from sys_file_info where file_id = #{fileId} + + + + delete from sys_file_info where file_id in + + #{fileId} + + + \ No newline at end of file diff --git a/ruoyi-scene-file/ruoyi-file-local/pom.xml b/ruoyi-scene-file/ruoyi-file-local/pom.xml new file mode 100644 index 0000000..5a97bd5 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-local/pom.xml @@ -0,0 +1,27 @@ + + + + ruoyi-scene-file + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-file-local + + + 本地文件存储 + + + + + + + com.ruoyi.geekxd + ruoyi-file-common + + + + + diff --git a/ruoyi-scene-file/ruoyi-file-local/src/main/java/com/ruoyi/file/local/config/LocalBucketProperties.java b/ruoyi-scene-file/ruoyi-file-local/src/main/java/com/ruoyi/file/local/config/LocalBucketProperties.java new file mode 100644 index 0000000..34aebdb --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-local/src/main/java/com/ruoyi/file/local/config/LocalBucketProperties.java @@ -0,0 +1,10 @@ +package com.ruoyi.file.local.config; + +import lombok.Data; + +@Data +public class LocalBucketProperties { + private String path; + private String permission; + private String api; +} diff --git a/ruoyi-scene-file/ruoyi-file-local/src/main/java/com/ruoyi/file/local/config/LocalManagement.java b/ruoyi-scene-file/ruoyi-file-local/src/main/java/com/ruoyi/file/local/config/LocalManagement.java new file mode 100644 index 0000000..c8dff34 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-local/src/main/java/com/ruoyi/file/local/config/LocalManagement.java @@ -0,0 +1,81 @@ +package com.ruoyi.file.local.config; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.ruoyi.file.local.domain.LocalBucket; +import com.ruoyi.file.storage.StorageManagement; + +@Configuration("local") +@ConditionalOnProperty(prefix = "local", name = { "enable" }, havingValue = "true", matchIfMissing = false) +@ConfigurationProperties("local") +public class LocalManagement implements StorageManagement, WebMvcConfigurer { + private static final Logger logger = LoggerFactory.getLogger(LocalManagement.class); + private Map client; + private String primary; + private Map targetLocalBucket = new HashMap<>(); + private LocalBucket primaryBucket; + + @Override + public void afterPropertiesSet() throws Exception { + if (client == null || client.isEmpty()) { + throw new RuntimeException("Local client properties cannot be null or empty"); + } + client.forEach((name, props) -> { + targetLocalBucket.put(name, LocalBucket.builder() + .clientName(name) + .basePath(props.getPath()) + .permission(props.getPermission()) + .api(props.getApi()) + .build()); + logger.info("本地存储目录:{} - 配置成功,路径:{}", name, props.getPath()); + }); + if (targetLocalBucket.get(primary) == null) { + throw new RuntimeException("Primary local client " + primary + " does not exist"); + } + primaryBucket = targetLocalBucket.get(primary); + } + + @Override + public LocalBucket getBucket(String clientName) { + return targetLocalBucket.get(clientName); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + client.forEach((name, props) -> { + if ("public".equals(props.getPermission())) { + registry.addResourceHandler(props.getApi() + "/**") + .addResourceLocations("file:" + props.getPath() + "/"); + } + }); + } + + public LocalBucket getPrimaryBucket() { + return this.primaryBucket; + } + + public Map getClient() { + return client; + } + + public void setClient(Map client) { + this.client = client; + } + + public String getPrimaryStorageBucket() { + return primary; + } + + public void setPrimary(String primary) { + this.primary = primary; + } +} diff --git a/ruoyi-scene-file/ruoyi-file-local/src/main/java/com/ruoyi/file/local/domain/LocalBucket.java b/ruoyi-scene-file/ruoyi-file-local/src/main/java/com/ruoyi/file/local/domain/LocalBucket.java new file mode 100644 index 0000000..7d0f920 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-local/src/main/java/com/ruoyi/file/local/domain/LocalBucket.java @@ -0,0 +1,220 @@ +package com.ruoyi.file.local.domain; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.channels.FileChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.web.multipart.MultipartFile; + +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.sign.Md5Utils; +import com.ruoyi.common.utils.uuid.UUID; +import com.ruoyi.file.domain.SysFilePartETag; +import com.ruoyi.file.storage.StorageBucket; +import com.ruoyi.file.storage.StorageEntity; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; + +@Builder +@Slf4j +public class LocalBucket implements StorageBucket { + + private String clientName; + private String basePath; + private String permission; + private String api; + + @Override + public void put(String filePath, MultipartFile file) { + Path dest = Paths.get(getBasePath(), filePath); + try (InputStream inputStream = file.getInputStream()) { + Files.createDirectories(dest.getParent()); + Files.copy(inputStream, dest); + } catch (Exception e) { + throw new ServiceException("Failed to upload file: " + e.getMessage()); + } + } + + @Override + public StorageEntity get(String filePath) throws IOException { + Path file = Paths.get(getBasePath(), filePath); + StorageEntity fileEntity = new StorageEntity(); + fileEntity.setFilePath(filePath); + fileEntity.setInputStream(new FileInputStream(file.toFile())); + fileEntity.setByteCount(file.toFile().length()); + return fileEntity; + } + + @Override + public void remove(String filePath) throws IOException { + Path file = Paths.get(getBasePath(), filePath); + Files.deleteIfExists(file); + } + + @Override + public URL generatePresignedUrl(String filePath, int expireTime) throws Exception { + HttpServletRequest request = ServletUtils.getRequest(); + StringBuffer url = request.getRequestURL(); + String contextPath = request.getSession().getServletContext().getContextPath(); + String toHex = Md5Utils.hash(filePath + expireTime); + StringBuilder sb = new StringBuilder(); + sb.append(url.delete(url.length() - request.getRequestURI().length(), url.length()) + .append(contextPath).toString()) + .append(getApi()).append("?") + .append("filePath=").append(URLEncoder.encode(filePath, "UTF-8")) + .append("&toHex=").append(toHex); + return URI.create(sb.toString()).toURL(); + } + + @Override + public URL generatePublicURL(String filePath) throws Exception { + HttpServletRequest request = ServletUtils.getRequest(); + StringBuffer url = request.getRequestURL(); + String contextPath = request.getSession().getServletContext().getContextPath(); + StringBuilder sb = new StringBuilder(); + sb.append(url.delete(url.length() - request.getRequestURI().length(), url.length()) + .append(contextPath).toString()).append(getApi()) + .append("/").append(filePath.replace("\\", "/")); + return new URI(sb.toString()).toURL(); + } + + // 存储分片上传的元数据 + private final ConcurrentHashMap>> uploadMetadata = new ConcurrentHashMap<>(); + + public String initMultipartUpload(String filePath) throws Exception { + try { + String uploadId = UUID.randomUUID().toString(); + uploadMetadata.put(uploadId, new ArrayList<>()); + + // 创建临时上传目录 + Path tempDir = Paths.get(getBasePath(), "temp_uploads", uploadId); + Files.createDirectories(tempDir); + return uploadId; + } catch (Exception e) { + log.error("初始化失败: 文件={}, 错误={}", filePath, e.getMessage()); + throw new ServiceException("初始化分片上传失败: " + e.getMessage()); + } + } + + public SysFilePartETag uploadPart(String filePath, String uploadId, int partNumber, long partSize, + InputStream inputStream) + throws Exception { + if (!uploadMetadata.containsKey(uploadId)) { + throw new ServiceException("无效的 uploadId: " + uploadId); + } + Path tempDir = Paths.get(getBasePath(), "temp_uploads", uploadId); + Path partPath = tempDir.resolve("part_" + partNumber); + try (OutputStream fos = Files.newOutputStream(partPath)) { + byte[] buffer = new byte[8192]; + int bytesRead; + long totalBytesWritten = 0; + while ((bytesRead = inputStream.read(buffer)) != -1 && totalBytesWritten < partSize) { + int writeSize = (int) Math.min(bytesRead, partSize - totalBytesWritten); + fos.write(buffer, 0, writeSize); + totalBytesWritten += writeSize; + } + if (totalBytesWritten != partSize) { + throw new ServiceException("分片大小不匹配: 预期=" + partSize + ", 实际=" + totalBytesWritten); + } + } + String etag = Md5Utils.getMd5(partPath.toFile()); + if (etag == null) { + throw new ServiceException("计算分片 MD5 失败"); + } + etag = etag.toUpperCase(); + Map partInfo = Map.of( + "partNumber", partNumber, + "etag", etag, + "size", partSize, + "path", partPath.toString()); + synchronized (uploadMetadata) { + List> parts = uploadMetadata.get(uploadId); + int insertPos = 0; + while (insertPos < parts.size() + && ((Number) parts.get(insertPos).get("partNumber")).intValue() < partNumber) { + insertPos++; + } + parts.add(insertPos, partInfo); + } + return new SysFilePartETag(partNumber, etag, partSize, null); + } + + public String completeMultipartUpload(String filePath, String uploadId, List partETags) + throws Exception { + List> storedParts = uploadMetadata.get(uploadId); + if (storedParts == null) { + throw new ServiceException("无效的 uploadId: " + uploadId); + } + if (partETags.size() != storedParts.size()) { + throw new ServiceException("分片数量不匹配: 预期=" + storedParts.size() + ", 实际=" + partETags.size()); + } + // 验证每个分片的 ETag + for (int i = 0; i < partETags.size(); i++) { + Map expected = storedParts.get(i); + SysFilePartETag actual = partETags.get(i); + + if (!expected.get("etag").equals(actual.getETag()) || + !expected.get("partNumber").equals(actual.getPartNumber())) { + throw new ServiceException("分片验证失败: 序号=" + actual.getPartNumber()); + } + } + Path destPath = Paths.get(getBasePath(), filePath); + Files.createDirectories(destPath.getParent()); + try (WritableByteChannel outChannel = Files.newByteChannel( + destPath, + StandardOpenOption.CREATE, + StandardOpenOption.WRITE, + StandardOpenOption.TRUNCATE_EXISTING)) { + for (Map part : storedParts) { + Path partPath = Paths.get((String) part.get("path")); + try (FileChannel inChannel = FileChannel.open(partPath, StandardOpenOption.READ)) { + inChannel.transferTo(0, inChannel.size(), outChannel); + } + } + } + // 清理临时文件和元数据 + Path tempDir = Paths.get(getBasePath(), "temp_uploads", uploadId); + Files.walk(tempDir).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + uploadMetadata.remove(uploadId); + log.info("分片合并完成: 文件={}, uploadId={}, 分片数={}", filePath, uploadId, storedParts.size()); + return filePath; + } + + public String getClientName() { + return clientName; + } + + public String getBasePath() { + if (basePath != null && !basePath.endsWith("/")) { + basePath = basePath + "/"; + } + return basePath; + } + + public String getPermission() { + return permission; + } + + public String getApi() { + return api; + } +} diff --git a/ruoyi-scene-file/ruoyi-file-local/src/main/resources/additional-spring-configuration-metadata.json b/ruoyi-scene-file/ruoyi-file-local/src/main/resources/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..8817d02 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-local/src/main/resources/additional-spring-configuration-metadata.json @@ -0,0 +1,4 @@ +{ + "properties": [ + ] +} \ No newline at end of file diff --git a/ruoyi-scene-file/ruoyi-file-minio/pom.xml b/ruoyi-scene-file/ruoyi-file-minio/pom.xml new file mode 100644 index 0000000..6a0e225 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-minio/pom.xml @@ -0,0 +1,33 @@ + + + + ruoyi-scene-file + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-file-minio + + + 分布式文件存储 + + + + + + + com.ruoyi.geekxd + ruoyi-file-common + + + + + io.minio + minio + + + + + diff --git a/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/config/MinioBucketProperties.java b/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/config/MinioBucketProperties.java new file mode 100644 index 0000000..1205f2f --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/config/MinioBucketProperties.java @@ -0,0 +1,12 @@ +package com.ruoyi.file.minio.config; + +import lombok.Data; + +@Data +public class MinioBucketProperties { + private String permission; + private String url; + private String accessKey; + private String secretKey; + private String bucketName; +} diff --git a/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/config/MinioManagement.java b/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/config/MinioManagement.java new file mode 100644 index 0000000..8c53fdd --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/config/MinioManagement.java @@ -0,0 +1,119 @@ +package com.ruoyi.file.minio.config; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.file.minio.domain.MinioBucket; +import com.ruoyi.file.storage.StorageManagement; + +import io.minio.BucketExistsArgs; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; + +@Configuration("minio") +@ConditionalOnProperty(prefix = "minio", name = { "enable" }, havingValue = "true", matchIfMissing = false) +@ConfigurationProperties("minio") +public class MinioManagement implements StorageManagement { + private static final Logger logger = LoggerFactory.getLogger(MinioManagement.class); + private Map client; + private String primary; + private Map targetMinioBucket = new HashMap<>(); + private MinioBucket primaryBucket; + + @Override + public void afterPropertiesSet() throws Exception { + if (client == null || client.isEmpty()) { + throw new RuntimeException("Client properties cannot be null or empty"); + } + client.forEach((name, props) -> { + try { + targetMinioBucket.put(name, createMinioClient(name, props)); + } catch (Exception e) { + logger.error("Failed to create MinIO client for {}: {}", name, e.getMessage(), e); + } + }); + + if (targetMinioBucket.get(primary) == null) { + throw new RuntimeException("Primary client " + primary + " does not exist"); + } + primaryBucket = targetMinioBucket.get(primary); + } + + private void validateMinioBucket(MinioBucket minioBucket) { + BucketExistsArgs bucketExistArgs = BucketExistsArgs.builder().bucket(minioBucket.getBucketName()).build(); + boolean b = false; + try { + b = minioBucket.getClient().bucketExists(bucketExistArgs); + // 使用空输入流兼容 httpclient5 移除旧 EmptyInputStream + InputStream empty = InputStream.nullInputStream(); + PutObjectArgs putObjectArgs = PutObjectArgs.builder() + .object(FileUtils.getRelativePath(RuoYiConfig.getProfile()) + "/") + .stream(empty, 0, -1).bucket(minioBucket.getBucketName()).build(); + minioBucket.getClient().putObject(putObjectArgs); + } catch (Exception e) { + logger.error("数据桶:{} - 链接失败", minioBucket.getName()); + throw new RuntimeException(e.getMessage()); + } + if (!b) { + throw new RuntimeException("Bucket " + minioBucket.getBucketName() + " does not exist"); + } + } + + private MinioBucket createMinioClient(String name, MinioBucketProperties props) { + MinioClient client; + if (StringUtils.isEmpty(props.getAccessKey())) { + client = MinioClient.builder() + .endpoint(props.getUrl()) + .build(); + } else { + client = MinioClient.builder() + .endpoint(props.getUrl()) + .credentials(props.getAccessKey(), props.getSecretKey()) + .build(); + } + MinioBucket minioBucket = MinioBucket.builder() + .client(client) + .bucketName(props.getBucketName()) + .permission(props.getPermission()) + .url(props.getUrl()) + .build(); + validateMinioBucket(minioBucket); + logger.info("数据桶:{} - 链接成功", name); + return minioBucket; + } + + @Override + public MinioBucket getBucket(String clent) { + return targetMinioBucket.get(clent); + } + + public MinioBucket getPrimaryBucket() { + return this.primaryBucket; + } + + public Map getClient() { + return client; + } + + public void setClient(Map client) { + this.client = client; + } + + public String getPrimaryStorageBucket() { + return primary; + } + + public void setPrimary(String primary) { + this.primary = primary; + } +} diff --git a/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/domain/MinioBucket.java b/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/domain/MinioBucket.java new file mode 100644 index 0000000..1394367 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/domain/MinioBucket.java @@ -0,0 +1,224 @@ +package com.ruoyi.file.minio.domain; + +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import org.springframework.web.multipart.MultipartFile; + +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.file.domain.SysFilePartETag; +import com.ruoyi.file.minio.exception.MinioClientErrorException; +import com.ruoyi.file.storage.StorageBucket; + +import io.minio.ComposeObjectArgs; +import io.minio.ComposeSource; +import io.minio.GetObjectArgs; +import io.minio.GetObjectResponse; +import io.minio.GetPresignedObjectUrlArgs; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; +import io.minio.RemoveObjectArgs; +import io.minio.http.Method; +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; + +@Builder +@Slf4j +public class MinioBucket implements StorageBucket { + + private String url; + private String permission; + private MinioClient client; + private String bucketName; + + private static final ConcurrentHashMap uploadingParts = new ConcurrentHashMap<>(); + + @Override + public void put(String filePath, MultipartFile file) { + try (InputStream inputStream = file.getInputStream()) { + PutObjectArgs putObjectArgs = PutObjectArgs.builder() + .contentType(file.getContentType()) + .stream(inputStream, file.getSize(), -1) + .bucket(bucketName) + .object(filePath) + .build(); + client.putObject(putObjectArgs); + } catch (Exception e) { + throw new MinioClientErrorException(e.getMessage()); + } + } + + @Override + public void remove(String filePath) throws Exception { + RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder() + .object(filePath) + .bucket(bucketName) + .build(); + client.removeObject(removeObjectArgs); + } + + @Override + public MinioEntityVO get(String filePath) throws Exception { + GetObjectArgs getObjectArgs = GetObjectArgs.builder() + .object(filePath) + .bucket(bucketName) + .build(); + GetObjectResponse response = client.getObject(getObjectArgs); + MinioEntityVO minioFileVO = new MinioEntityVO(); + minioFileVO.setInputStream(response); + minioFileVO.setByteCount(Convert.toLong(response.headers().get("Content-Length"), null)); + minioFileVO.setFilePath(filePath); + minioFileVO.setObject(response.object()); + minioFileVO.setRegion(response.region()); + minioFileVO.setBuket(response.bucket()); + minioFileVO.setHeaders(response.headers()); + return minioFileVO; + } + + @Override + public URL generatePresignedUrl(String filePath, int expireTime) throws Exception { + GetPresignedObjectUrlArgs request = GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucketName) + .object(filePath) + .expiry(expireTime, TimeUnit.SECONDS) + .build(); + String urlString = client.getPresignedObjectUrl(request); + return URI.create(urlString).toURL(); + } + + @Override + public URL generatePublicURL(String filePath) throws Exception { + StringBuilder sb = new StringBuilder(); + sb.append(getUrl()) + .append("/").append(getBucketName()) + .append(filePath.replace("\\", "/")); + return URI.create(sb.toString()).toURL(); + } + + /** + * 初始化分片上传 + */ + public String initMultipartUpload(String filePath) throws Exception { + try { + String uploadId = UUID.randomUUID().toString().replace("-", "").toUpperCase(); + return uploadId; + } catch (Exception e) { + log.error("初始化失败: 文件={}, 错误={}", filePath, e.getMessage()); + throw new MinioClientErrorException("初始化失败", e); + } + } + + /** + * 上传单个分片 + */ + public SysFilePartETag uploadPart(String filePath, String uploadId, int partNumber, long partSize, + InputStream inputStream) + throws Exception { + // 生成唯一的上传键 + String uploadKey = String.format("%s-%s-%d", filePath, uploadId, partNumber); + AtomicBoolean isUploading = uploadingParts.computeIfAbsent(uploadKey, k -> new AtomicBoolean(false)); + // 使用 CAS 检查是否已经在上传中 + if (!isUploading.compareAndSet(false, true)) { + throw new MinioClientErrorException("分片正在上传中: " + uploadKey); + } + try { + // 构建分片存储路径 + String partPath = String.format("%s.%s.part.%d", filePath, uploadId, partNumber); + // 构造上传请求参数 + PutObjectArgs args = PutObjectArgs.builder() + .bucket(bucketName) + .object(partPath) + .stream(inputStream, partSize, -1) + .build(); + // 执行上传操作并获取 ETag + String etag = client.putObject(args).etag().replace("\"", "").toUpperCase(); + return new SysFilePartETag(partNumber, etag); + } catch (Exception e) { + log.error("分片上传失败: 文件={}, 分片={}, 错误={}", filePath, partNumber, e.getMessage()); + throw new MinioClientErrorException("上传分片失败", e); + } finally { + isUploading.set(false);// 标记为上传完成,并清理状态 + uploadingParts.remove(uploadKey); + } + } + + /** + * 完成分片上传并合并文件 + */ + public String completeMultipartUpload(String filePath, String uploadId, List filePartETags) + throws Exception { + if (filePartETags == null || filePartETags.isEmpty()) { + throw new IllegalArgumentException("分片信息不能为空"); + } + + // 按分片序号排序 + List partPaths = filePartETags.stream() + .sorted(Comparator.comparingInt(p -> p.getPartNumber())) + .map(part -> String.format("%s.%s.part.%d", filePath, uploadId, part.getPartNumber())) + .collect(Collectors.toList()); + + List sources = partPaths.stream() + .map(path -> ComposeSource.builder() + .bucket(bucketName) + .object(path) + .build()) + .toList(); + + client.composeObject(ComposeObjectArgs.builder() + .bucket(bucketName) + .object(filePath) + .sources(sources) + .build()); + + for (String partPath : partPaths) { + try { + client.removeObject(RemoveObjectArgs.builder() + .bucket(bucketName) + .object(partPath) + .build()); + } catch (Exception e) { + log.warn("清理分片失败: {}", e.getMessage()); + } + } + log.info("分片合并完成: 文件={}, uploadId={}, 分片数={}", filePath, uploadId, filePartETags.size()); + return filePath; + } + + public String getName() { + return bucketName; + } + + public MinioClient getClient() { + return client; + } + + public String getBucketName() { + return bucketName; + } + + public void setBucketName(String bucketName) { + this.bucketName = bucketName; + } + + public void setClient(MinioClient client) { + this.client = client; + } + + public String getPermission() { + return permission; + } + + public String getUrl() { + return url; + } + +} diff --git a/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/domain/MinioEntityVO.java b/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/domain/MinioEntityVO.java new file mode 100644 index 0000000..6712eca --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/domain/MinioEntityVO.java @@ -0,0 +1,45 @@ +package com.ruoyi.file.minio.domain; + +import com.ruoyi.file.storage.StorageEntity; + +import okhttp3.Headers; + +public class MinioEntityVO extends StorageEntity { + private String object; + private Headers headers; + private String buket; + private String region; + + public String getObject() { + return object; + } + + public void setObject(String object) { + this.object = object; + } + + public Headers getHeaders() { + return headers; + } + + public void setHeaders(Headers headers) { + this.headers = headers; + } + + public String getBuket() { + return buket; + } + + public void setBuket(String buket) { + this.buket = buket; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + +} diff --git a/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/exception/MinioClientErrorException.java b/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/exception/MinioClientErrorException.java new file mode 100644 index 0000000..dbf976a --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/exception/MinioClientErrorException.java @@ -0,0 +1,16 @@ +package com.ruoyi.file.minio.exception; + +/** + * 当与MinIO客户端交互过程中发生错误时抛出此异常。 + */ +public class MinioClientErrorException extends RuntimeException { + + public MinioClientErrorException(String msg) { + super(msg); + } + + public MinioClientErrorException(String message, Throwable cause) { + super(message, cause); + } +} + diff --git a/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/exception/MinioClientNotFundException.java b/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/exception/MinioClientNotFundException.java new file mode 100644 index 0000000..80e43dd --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-minio/src/main/java/com/ruoyi/file/minio/exception/MinioClientNotFundException.java @@ -0,0 +1,15 @@ +package com.ruoyi.file.minio.exception; + +/** + * 当尝试获取MinIO客户端实例但未能找到相应的配置或客户端实例时抛出此异常。 + */ +public class MinioClientNotFundException extends RuntimeException { + + public MinioClientNotFundException(String msg) { + super(msg); + } + + public MinioClientNotFundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/ruoyi-scene-file/ruoyi-file-minio/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/ruoyi-scene-file/ruoyi-file-minio/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..88a5709 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-minio/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,19 @@ +{ + "properties": [ + { + "name": "minio.enable", + "type": "java.lang.Boolean", + "description": "是否开启minio" + }, + { + "name": "minio.primary", + "type": "java.lang.String", + "description": "默认存储名称" + }, + { + "name": "minio.client", + "type": "java.util.Map", + "description": "储存桶" + } + ] +} \ No newline at end of file diff --git a/ruoyi-scene-file/ruoyi-file-oss-alibaba/pom.xml b/ruoyi-scene-file/ruoyi-file-oss-alibaba/pom.xml new file mode 100644 index 0000000..b5917bc --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-oss-alibaba/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + ruoyi-scene-file + com.ruoyi.geekxd + 3.9.0-G + + com.ruoyi.geekxd + ruoyi-file-oss-alibaba + + 阿里云OSS对象存储 + + + + + + + com.ruoyi.geekxd + ruoyi-file-common + + + + + com.aliyun.oss + aliyun-sdk-oss + + + diff --git a/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/config/AliOssBucketProperties.java b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/config/AliOssBucketProperties.java new file mode 100644 index 0000000..891ffb6 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/config/AliOssBucketProperties.java @@ -0,0 +1,12 @@ +package com.ruoyi.file.oss.alibaba.config; + +import lombok.Data; + +@Data +public class AliOssBucketProperties { + private String permission; + private String endpoint; + private String accessKeyId; + private String accessKeySecret; + private String bucketName; +} \ No newline at end of file diff --git a/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/config/AliOssManagement.java b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/config/AliOssManagement.java new file mode 100644 index 0000000..c099d06 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/config/AliOssManagement.java @@ -0,0 +1,114 @@ +package com.ruoyi.file.oss.alibaba.config; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import com.aliyun.oss.ClientException; +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSClientBuilder; +import com.aliyun.oss.OSSException; +import com.ruoyi.file.oss.alibaba.domain.AliOssBucket; +import com.ruoyi.file.storage.StorageManagement; + +/** + * 配置类用于管理阿里云OSS客户端实例及其相关属性。 + */ +@Configuration("oss") +@ConditionalOnProperty(prefix = "oss", name = "enable", havingValue = "true", matchIfMissing = false) +@ConfigurationProperties(prefix = "oss") +public class AliOssManagement implements StorageManagement { + private static final Logger logger = LoggerFactory.getLogger(AliOssManagement.class); + private Map client; + private String primary; + private Map targetAliOssBucket = new HashMap<>(); + private AliOssBucket primaryBucket; + + @Override + public void afterPropertiesSet() throws Exception { + if (client == null || client.isEmpty()) { + throw new RuntimeException("Client properties cannot be null or empty"); + } + + client.forEach((name, props) -> { + try { + AliOssBucket aliOssBucket = createOssClient(name, props); + targetAliOssBucket.put(name, aliOssBucket); + } catch (Exception e) { + logger.error("Failed to create OSS client for {}: {}", name, e.getMessage(), e); + } + }); + + if (targetAliOssBucket.get(primary) == null) { + throw new RuntimeException("Primary client " + primary + " does not exist"); + } + primaryBucket = targetAliOssBucket.get(primary); + } + + private void validateOssBucket(AliOssBucket aliOssBucket) { + OSS ossClient = aliOssBucket.getOssClient(); + String bucketName = aliOssBucket.getBucketName(); + try { + if (!ossClient.doesBucketExist(bucketName)) { + throw new RuntimeException("Bucket " + bucketName + " does not exist"); + } + } catch (OSSException oe) { + logger.error("OSSException: " + oe.getMessage(), oe); + throw new RuntimeException("OSS error: " + oe.getMessage()); + } catch (ClientException ce) { + logger.error("ClientException: " + ce.getMessage(), ce); + throw new RuntimeException("Client error: " + ce.getMessage()); + } catch (Exception e) { + logger.error("Exception: " + e.getMessage(), e); + throw new RuntimeException("Error validating OSS bucket: " + e.getMessage()); + } + } + + private AliOssBucket createOssClient(String name, AliOssBucketProperties props) { + if (props == null || props.getEndpoint() == null || props.getAccessKeyId() == null || + props.getAccessKeySecret() == null || props.getBucketName() == null) { + throw new IllegalArgumentException("AliOssProperties or its required fields cannot be null"); + } + + OSS client = new OSSClientBuilder().build(props.getEndpoint(), props.getAccessKeyId(), + props.getAccessKeySecret()); + AliOssBucket ossBucket = AliOssBucket.builder() + .ossClient(client) + .bucketName(props.getBucketName()) + .endpoint(props.getEndpoint()) + .build(); + validateOssBucket(ossBucket); + logger.info("数据桶:{} - 链接成功", name); + return ossBucket; + } + + public AliOssBucket getPrimaryBucket() { + return this.primaryBucket; + } + + @Override + public AliOssBucket getBucket(String client) { + return targetAliOssBucket.get(client); + } + + public Map getClient() { + return client; + } + + public void setClient(Map client) { + this.client = client; + } + + public String getPrimaryStorageBucket() { + return primary; + } + + public void setPrimary(String primary) { + this.primary = primary; + } +} diff --git a/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/domain/AliOssBucket.java b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/domain/AliOssBucket.java new file mode 100644 index 0000000..70dc7bf --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/domain/AliOssBucket.java @@ -0,0 +1,177 @@ +package com.ruoyi.file.oss.alibaba.domain; + +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.web.multipart.MultipartFile; + +import com.aliyun.oss.OSS; +import com.aliyun.oss.ServiceException; +import com.aliyun.oss.model.AbortMultipartUploadRequest; +import com.aliyun.oss.model.CompleteMultipartUploadRequest; +import com.aliyun.oss.model.GeneratePresignedUrlRequest; +import com.aliyun.oss.model.GetObjectRequest; +import com.aliyun.oss.model.InitiateMultipartUploadRequest; +import com.aliyun.oss.model.OSSObject; +import com.aliyun.oss.model.ObjectMetadata; +import com.aliyun.oss.model.PartETag; +import com.aliyun.oss.model.PutObjectRequest; +import com.aliyun.oss.model.UploadPartRequest; +import com.ruoyi.file.domain.SysFilePartETag; +import com.ruoyi.file.oss.alibaba.exception.AliOssClientErrorException; +import com.ruoyi.file.storage.StorageBucket; + +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; + +@Builder +@Slf4j +public class AliOssBucket implements StorageBucket { + + private String bucketName; + private OSS ossClient; + private String permission; + private String endpoint; + + @Override + public void put(String filePath, MultipartFile file) throws Exception { + try { + InputStream inputStream = file.getInputStream(); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType(file.getContentType()); + metadata.setContentLength(inputStream.available()); + PutObjectRequest putRequest = new PutObjectRequest(bucketName, filePath, inputStream, metadata); + this.ossClient.putObject(putRequest); + } catch (Exception e) { + log.error("Error uploading file to OSS: {}", e.getMessage(), e); + throw new AliOssClientErrorException("Error uploading file to OSS: " + e.getMessage(), e); + } + } + + @Override + public void remove(String filePath) throws Exception { + ossClient.deleteObject(bucketName, filePath); + } + + @Override + public AliOssEntityVO get(String filePath) throws Exception { + GetObjectRequest request = new GetObjectRequest(this.bucketName, filePath); + OSSObject ossObject = this.ossClient.getObject(request); + if (ossObject == null) { + throw new Exception("Failed to retrieve object from OSS."); + } + AliOssEntityVO fileVO = new AliOssEntityVO(); + fileVO.setInputStream(ossObject.getObjectContent()); + fileVO.setKey(ossObject.getKey()); + fileVO.setBucketName(ossObject.getBucketName()); + fileVO.setByteCount(ossObject.getObjectMetadata().getContentLength()); + fileVO.setFilePath(filePath); + return fileVO; + } + + @Override + public URL generatePresignedUrl(String filePath, int expireTime) { + GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, filePath); + Date expiration = new Date(System.currentTimeMillis() + expireTime * 1000); // 设置过期时间为1小时 + request.setExpiration(expiration); + return ossClient.generatePresignedUrl(request); + } + + @Override + public URL generatePublicURL(String filePath) throws Exception { + StringBuilder sb = new StringBuilder(); + sb.append("https://").append(getBucketName()) + .append(".").append(getEndpoint()) + .append(filePath.replace("\\", "/")); + return URI.create(sb.toString()).toURL(); + } + + /** + * 初始化分片上传 + */ + public String initMultipartUpload(String filePath) throws Exception { + InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName, filePath); + String uploadId = ossClient.initiateMultipartUpload(initRequest).getUploadId(); + return uploadId; + } + + /** + * 上传单个分片 + */ + public SysFilePartETag uploadPart(String filePath, String uploadId, int partNumber, long partSize, + InputStream inputStream) + throws Exception { + UploadPartRequest uploadPartRequest = new UploadPartRequest(); + uploadPartRequest.setBucketName(bucketName); + uploadPartRequest.setKey(filePath); + uploadPartRequest.setUploadId(uploadId); + uploadPartRequest.setInputStream(inputStream); + uploadPartRequest.setPartSize(partSize); + uploadPartRequest.setPartNumber(partNumber); + PartETag partETag = ossClient.uploadPart(uploadPartRequest).getPartETag(); + return new SysFilePartETag(partETag.getPartNumber(), partETag.getETag(), partETag.getPartSize(), + partETag.getPartCRC()); + } + + /** + * 完成分片上传 + */ + public String completeMultipartUpload(String filePath, String uploadId, List sysFilePartETags) + throws Exception { + if (sysFilePartETags == null || sysFilePartETags.isEmpty()) { + throw new ServiceException("分片ETag列表不能为空"); + } + List partETags = sysFilePartETags.stream() + .map(part -> new PartETag(part.getPartNumber(), part.getETag().trim())) + .sorted(Comparator.comparingInt(PartETag::getPartNumber)) + .collect(Collectors.toList()); + + try { + CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest(bucketName, filePath, + uploadId, partETags); + ossClient.completeMultipartUpload(completeRequest); + log.info("分片上传已完成并合并: 文件={}, uploadId={}, 分片数={}", + filePath, uploadId, partETags.size()); + return filePath; + } catch (Exception e) { + log.error("合并分片失败: 文件={}, uploadId={}, 错误={}", + filePath, uploadId, e.getMessage()); + try { + ossClient.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, filePath, uploadId)); + } catch (Exception abortEx) { + log.error("取消分片上传失败: {}", abortEx.getMessage()); + } + throw new AliOssClientErrorException("合并分片失败: " + e.getMessage(), e); + } + } + + public OSS getOssClient() { + return ossClient; + } + + public void setOssClient(OSS ossClient) { + this.ossClient = ossClient; + } + + public String getBucketName() { + return bucketName; + } + + public void setBucketName(String bucketName) { + this.bucketName = bucketName; + } + + public String getPermission() { + return permission; + } + + public String getEndpoint() { + return endpoint; + } + +} \ No newline at end of file diff --git a/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/domain/AliOssEntityVO.java b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/domain/AliOssEntityVO.java new file mode 100644 index 0000000..b8206b8 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/domain/AliOssEntityVO.java @@ -0,0 +1,34 @@ +package com.ruoyi.file.oss.alibaba.domain; + +import com.aliyun.oss.model.ObjectMetadata; +import com.ruoyi.file.storage.StorageEntity; + +public class AliOssEntityVO extends StorageEntity { + private String key; + private String bucketName; + private ObjectMetadata metadata; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getBucketName() { + return bucketName; + } + + public void setBucketName(String bucketName) { + this.bucketName = bucketName; + } + + public ObjectMetadata getMetadata() { + return metadata; + } + + public void setMetadata(ObjectMetadata metadata) { + this.metadata = metadata; + } +} \ No newline at end of file diff --git a/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/exception/AliOssClientErrorException.java b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/exception/AliOssClientErrorException.java new file mode 100644 index 0000000..43f89c3 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/exception/AliOssClientErrorException.java @@ -0,0 +1,11 @@ +package com.ruoyi.file.oss.alibaba.exception; + +public class AliOssClientErrorException extends RuntimeException{ + public AliOssClientErrorException(String msg){ + super(msg); + } + + public AliOssClientErrorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/exception/AliOssClientNotFundException.java b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/exception/AliOssClientNotFundException.java new file mode 100644 index 0000000..2da3d56 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/java/com/ruoyi/file/oss/alibaba/exception/AliOssClientNotFundException.java @@ -0,0 +1,27 @@ +package com.ruoyi.file.oss.alibaba.exception; + +/** + * 当尝试获取阿里云OSS客户端实例但未能找到相应的配置或客户端实例时抛出此异常。 + * 此异常表明系统中存在配置问题或者客户端初始化失败的问题。 + */ +public class AliOssClientNotFundException extends RuntimeException { + + /** + * 使用指定的详细信息创建一个新的 {@code AliOssClientNotFundException} 实例。 + * + * @param msg 描述异常原因的信息。 + */ + public AliOssClientNotFundException(String msg) { + super(msg); + } + + /** + * 使用指定的详细信息和导致此异常的原因创建一个新的 {@code AliOssClientNotFundException} 实例。 + * + * @param message 描述异常原因的信息。 + * @param cause 导致此异常的根本原因。 + */ + public AliOssClientNotFundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..4fe224a --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-oss-alibaba/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,19 @@ +{ + "properties": [ + { + "name": "oss.enable", + "type": "java.lang.Boolean", + "description": "是否开启oss" + }, + { + "name": "oss.primary", + "type": "java.lang.String", + "description": "默认存储名称" + }, + { + "name": "oss.client", + "type": "java.util.Map", + "description": "储存桶" + } + ] +} \ No newline at end of file diff --git a/ruoyi-scene-file/ruoyi-file-starter/pom.xml b/ruoyi-scene-file/ruoyi-file-starter/pom.xml new file mode 100644 index 0000000..b9da270 --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-starter/pom.xml @@ -0,0 +1,35 @@ + + + + ruoyi-scene-file + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-file-starter + + + 文件 + + + + + + com.ruoyi.geekxd + ruoyi-file-local + + + + com.ruoyi.geekxd + ruoyi-file-oss-alibaba + + + + com.ruoyi.geekxd + ruoyi-file-minio + + + + diff --git a/ruoyi-scene-file/ruoyi-file-starter/src/main/java/com/ruoyi/file/controller/FileController.java b/ruoyi-scene-file/ruoyi-file-starter/src/main/java/com/ruoyi/file/controller/FileController.java new file mode 100644 index 0000000..72d002b --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-starter/src/main/java/com/ruoyi/file/controller/FileController.java @@ -0,0 +1,281 @@ +package com.ruoyi.file.controller; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.text.SimpleDateFormat; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.file.domain.SysFileInfo; +import com.ruoyi.file.domain.SysFilePartETag; +import com.ruoyi.file.service.ISysFileInfoService; +import com.ruoyi.file.service.StorageService; +import com.ruoyi.file.utils.FileOperateUtils; +import com.ruoyi.file.utils.StorageUtils; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Tag(name = "默认文件存储") +@RestController +@RequestMapping("/file") +public class FileController { + + @Autowired + private ISysFileInfoService sysFileInfoService; + + /** + * 获取所有可用存储渠道及其client列表 + */ + @GetMapping("/client-list") + public AjaxResult getClientList() { + return AjaxResult.success(StorageUtils.getClientList()); + } + + /** + * 统一上传接口:/file/{storageType}/{clientName}/upload + */ + @PostMapping({ "/upload", "/{storageType}/{clientName}/upload" }) + public AjaxResult uploadUnified( + @PathVariable(name = "storageType", required = false) String storageType, + @PathVariable(name = "clientName", required = false) String clientName, + @RequestParam("file") MultipartFile file) { + try { + String filePath = "upload/" + System.currentTimeMillis() + "_" + file.getOriginalFilename(); + StorageService storageService = null; + if (StringUtils.isEmpty(storageType) || StringUtils.isEmpty(clientName)) { + storageService = new StorageService(StorageUtils.getPrimaryStorageBucket()); + } else { + storageService = new StorageService(StorageUtils.getStorageBucket(storageType, clientName)); + } + String url = storageService.upload(filePath, file); + SysFileInfo sysFileInfo = sysFileInfoService.buildSysFileInfo(file); + sysFileInfo.setFilePath(filePath); + sysFileInfo.setStorageType( + StringUtils.isNotEmpty(storageType) ? storageType : StorageUtils.getPrimaryStorageType()); + sysFileInfoService.insertSysFileInfo(sysFileInfo); + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("info", sysFileInfo); + ajax.put("fileName", sysFileInfo.getFileName()); + return ajax; + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 统一下载接口:/file/{storageType}/{clientName}/download?filePath=xxx + */ + @GetMapping({ "/download", "/{storageType}/{clientName}/download" }) + public void downloadUnified( + @PathVariable(name = "storageType", required = false) String storageType, + @PathVariable(name = "clientName", required = false) String clientName, + @RequestParam("filePath") String filePath, + HttpServletResponse response) throws Exception { + try { + StorageService storageService = null; + if (StringUtils.isEmpty(storageType) || StringUtils.isEmpty(clientName)) { + storageService = new StorageService(StorageUtils.getPrimaryStorageBucket()); + } else { + storageService = new StorageService(StorageUtils.getStorageBucket(storageType, clientName)); + } + response.setContentType("application/octet-stream"); + storageService.downLoad(filePath, response); + } catch (Exception e) { + response.setContentType("text/plain;charset=UTF-8"); + response.getWriter().write("下载失败: " + e.getMessage()); + } + } + + /** + * 统一预览接口:/file/{storageType}/{clientName}/preview?filePath=xxx + */ + @Anonymous + @GetMapping({ "/preview", "/{storageType}/{clientName}/preview" }) + public void preview( + @PathVariable(name = "storageType", required = false) String storageType, + @PathVariable(name = "clientName", required = false) String clientName, + @RequestParam("filePath") String filePath, + HttpServletResponse response) throws Exception { + try { + StorageService storageService = null; + if (StringUtils.isEmpty(storageType) || StringUtils.isEmpty(clientName)) { + storageService = new StorageService(StorageUtils.getPrimaryStorageBucket()); + } else { + storageService = new StorageService(StorageUtils.getStorageBucket(storageType, clientName)); + } + filePath = URLDecoder.decode(filePath, "UTF-8"); + InputStream inputStream = storageService.downLoad(filePath); + String contentType = URLConnection.guessContentTypeFromName(FileUtils.getName(filePath)); + if (contentType == null) { + contentType = "application/octet-stream"; + } + response.setContentType(contentType); + IOUtils.copy(inputStream, response.getOutputStream()); + response.flushBuffer(); + } catch (Exception e) { + response.setContentType("text/plain;charset=UTF-8"); + response.getWriter().write("预览失败: " + e.getMessage()); + } + } + + /** + * 本地资源通用下载 + */ + @Operation(summary = "本地资源通用下载") + @GetMapping("/resource") + @Anonymous + public void resourceDownload( + @RequestParam String filePath, + HttpServletRequest request, + HttpServletResponse response) throws Exception { + OutputStream outputStream = response.getOutputStream(); + try { + if (!FileUtils.checkAllowDownload(filePath)) { + throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", filePath)); + } + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, filePath); + FileOperateUtils.downLoad(filePath, outputStream); + } catch (Exception e) { + response.reset(); + response.setContentType(MediaType.TEXT_HTML_VALUE); + response.setCharacterEncoding("UTF-8"); + String errorMessage = "下载文件失败: " + e.getMessage(); + outputStream.write(errorMessage.getBytes("UTF-8")); + outputStream.flush(); + } finally { + outputStream.close(); + } + } + + /** + * 初始化分片上传 + */ + @PostMapping("/initUpload") + public AjaxResult initMultipartUpload( + @RequestParam("fileName") String fileName, + @RequestParam("fileSize") Long fileSize) { + try { + if (fileName == null || fileName.isEmpty() || fileSize == null || fileSize <= 0) { + throw new ServiceException("文件名或文件大小不能为空"); + } + String currentDate = new SimpleDateFormat("yyyy/MM/dd").format(new Date()); + String timestamp = String.valueOf(System.currentTimeMillis()); + String objectName = String.format("%s/%s/%s_%s", "/upload", currentDate, timestamp, fileName); + StorageService fileService = new StorageService(StorageUtils.getPrimaryStorageBucket()); + String uploadId = fileService.initMultipartUpload(objectName, fileSize); + return AjaxResult.success(Map.of( + "uploadId", uploadId, + "filePath", objectName, + "fileName", fileName)); + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 上传文件分片 + */ + @PostMapping("/uploadChunk") + public AjaxResult uploadFileChunk( + @RequestParam("uploadId") String uploadId, + @RequestParam("filePath") String filePath, + @RequestParam("partNumber") int partNumber, + @RequestParam("chunk") MultipartFile chunk) { + try { + if (chunk == null || chunk.isEmpty()) + throw new ServiceException("分片数据不能为空"); + StorageService fileService = new StorageService(StorageUtils.getPrimaryStorageBucket()); + String etag = fileService.uploadPart(filePath, uploadId, partNumber, chunk.getSize(), + chunk.getInputStream()); + if (etag == null || etag.isEmpty()) + throw new ServiceException("上传分片失败:未获取到ETag"); + return AjaxResult.success(Map.of( + "etag", etag, + "partNumber", partNumber)); + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 完成分片上传并合并文件 + */ + @PostMapping("/completeUpload") + public AjaxResult completeMultipartUpload( + @RequestParam("uploadId") String uploadId, + @RequestParam("filePath") String filePath, + @RequestParam("fileSize") Long fileSize, + @RequestParam("fileName") String fileName, + @RequestBody List partETags) { + try { + if (partETags == null || partETags.isEmpty()) + throw new ServiceException("分片信息不能为空"); + // 验证并排序分片信息 + List validParts = partETags.stream() + .filter(part -> part != null && part.getPartNumber() != null && part.getETag() != null) + .peek(part -> { + if (part.getPartNumber() <= 0 || StringUtils.isEmpty(part.getETag())) { + throw new ServiceException("分片序号或ETag无效"); + } + }) + .collect(Collectors.toList()); + if (validParts.size() != partETags.size()) { + throw new ServiceException("分片信息格式不正确"); + } + validParts.sort(Comparator.comparingInt(p -> p.getPartNumber())); + // 完成分片上传并合并文件 + StorageService fileService = new StorageService(StorageUtils.getPrimaryStorageBucket()); + String finalPath = fileService.completeMultipartUpload(filePath, uploadId, validParts); + if (finalPath == null || finalPath.isEmpty()) { + throw new ServiceException("合并分片失败:未获取到最终文件路径"); + } + // 创建文件记录 + int dotIndex = fileName.lastIndexOf('.'); + String userName = SecurityUtils.getUsername(); + SysFileInfo fileInfo = new SysFileInfo(); + fileInfo.setFileName(fileName); + fileInfo.setFilePath(finalPath); + fileInfo.setFileSize(fileSize); + fileInfo.setFileType(dotIndex >= 0 ? fileName.substring(dotIndex + 1) : ""); + fileInfo.setStorageType(RuoYiConfig.getFileServer()); + fileInfo.setCreateBy(userName); + fileInfo.setCreateTime(new Date()); + fileInfo.setUpdateBy(userName); + fileInfo.setUpdateTime(new Date()); + fileInfo.setDelFlag("0"); + sysFileInfoService.insertSysFileInfo(fileInfo); + return AjaxResult.success(fileInfo); + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } +} diff --git a/ruoyi-scene-file/ruoyi-file-starter/src/main/java/com/ruoyi/file/controller/SysFileInfoController.java b/ruoyi-scene-file/ruoyi-file-starter/src/main/java/com/ruoyi/file/controller/SysFileInfoController.java new file mode 100644 index 0000000..ff9acdc --- /dev/null +++ b/ruoyi-scene-file/ruoyi-file-starter/src/main/java/com/ruoyi/file/controller/SysFileInfoController.java @@ -0,0 +1,116 @@ +package com.ruoyi.file.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.file.domain.SysFileInfo; +import com.ruoyi.file.service.ISysFileInfoService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 文件Controller + * + * @author ruoyi + * @date 2025-04-25 + */ +@RestController +@RequestMapping("/file/info") +@Tag(name = "【文件】管理") +public class SysFileInfoController extends BaseController +{ + @Autowired + private ISysFileInfoService sysFileInfoService; + + /** + * 查询文件列表 + */ + @Operation(summary = "查询文件列表") + @PreAuthorize("@ss.hasPermi('system:file:list')") + @GetMapping("/list") + public TableDataInfo list(SysFileInfo sysFileInfo) + { + startPage(); + List list = sysFileInfoService.selectSysFileInfoList(sysFileInfo); + return getDataTable(list); + } + + /** + * 导出文件列表 + */ + @Operation(summary = "导出文件列表") + @PreAuthorize("@ss.hasPermi('system:file:export')") + @Log(title = "文件", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysFileInfo sysFileInfo) + { + List list = sysFileInfoService.selectSysFileInfoList(sysFileInfo); + ExcelUtil util = new ExcelUtil(SysFileInfo.class); + util.exportExcel(response, list, "文件数据"); + } + + /** + * 获取文件详细信息 + */ + @Operation(summary = "获取文件详细信息") + @PreAuthorize("@ss.hasPermi('system:file:query')") + @GetMapping(value = "/{fileId}") + public AjaxResult getInfo(@PathVariable("fileId") Long fileId) + { + return success(sysFileInfoService.selectSysFileInfoByFileId(fileId)); + } + + /** + * 新增文件 + */ + @Operation(summary = "新增文件") + @PreAuthorize("@ss.hasPermi('system:file:add')") + @Log(title = "文件", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysFileInfo sysFileInfo) + { + return toAjax(sysFileInfoService.insertSysFileInfo(sysFileInfo)); + } + + /** + * 修改文件 + */ + @Operation(summary = "修改文件") + @PreAuthorize("@ss.hasPermi('system:file:edit')") + @Log(title = "文件", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysFileInfo sysFileInfo) + { + return toAjax(sysFileInfoService.updateSysFileInfo(sysFileInfo)); + } + + /** + * 删除文件 + */ + @Operation(summary = "删除文件") + @PreAuthorize("@ss.hasPermi('system:file:remove')") + @Log(title = "文件", businessType = BusinessType.DELETE) + @DeleteMapping("/{fileIds}") + public AjaxResult remove(@PathVariable( name = "fileIds" ) Long[] fileIds) + { + return toAjax(sysFileInfoService.deleteSysFileInfoByFileIds(fileIds)); + } +} diff --git a/ruoyi-scene-pay/pom.xml b/ruoyi-scene-pay/pom.xml new file mode 100644 index 0000000..b1a247c --- /dev/null +++ b/ruoyi-scene-pay/pom.xml @@ -0,0 +1,75 @@ + + + + ruoyi + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-scene-pay + + + 0.2.12 + 2.2.0 + + + + 支付模块 + + + + + + com.github.wechatpay-apiv3 + wechatpay-java + ${wechatpay.version} + + + + com.alipay.sdk + alipay-easysdk + ${alipay.version} + + + + + com.ruoyi.geekxd + ruoyi-pay-common + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-pay-sqb + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-pay-alipay + ${ruoyi.version} + + + + + com.ruoyi.geekxd + ruoyi-pay-wx + ${ruoyi.version} + + + + + + + ruoyi-pay-sqb + ruoyi-pay-alipay + ruoyi-pay-wx + ruoyi-pay-common + ruoyi-pay-starter + + pom + \ No newline at end of file diff --git a/ruoyi-scene-pay/ruoyi-pay-alipay/pom.xml b/ruoyi-scene-pay/ruoyi-pay-alipay/pom.xml new file mode 100644 index 0000000..50d530b --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-alipay/pom.xml @@ -0,0 +1,31 @@ + + + + ruoyi-scene-pay + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-pay-alipay + + + 支付宝支付模块 + + + + + + com.ruoyi.geekxd + ruoyi-pay-common + + + + com.alipay.sdk + alipay-easysdk + + + + + \ No newline at end of file diff --git a/ruoyi-scene-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/config/AliPayConfig.java b/ruoyi-scene-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/config/AliPayConfig.java new file mode 100644 index 0000000..697b44d --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/config/AliPayConfig.java @@ -0,0 +1,98 @@ +package com.ruoyi.pay.alipay.config; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.util.StreamUtils; + +import com.alipay.easysdk.factory.Factory; +import com.alipay.easysdk.kernel.Config; + +/** + * @author zlh + */ +@Configuration +@ConditionalOnProperty(prefix = "pay.alipay", name = "enabled", havingValue = "true") +public class AliPayConfig { + @Value("${pay.alipay.appId}") + private String appId; + @Value("${pay.alipay.notifyUrl}") + private String notifyUrl; + @Value("${pay.alipay.appPrivateKey:}") + private String appPrivateKey; + @Value("${pay.alipay.alipayPublicKey:}") + private String alipayPublicKey; + @Value("${pay.alipay.signType:RSA2}") + private String signType; + @Value("${pay.alipay.merchantCertPath:}") + private String merchantCertPath; + @Value("${pay.alipay.alipayCertPath:}") + private String alipayCertPath; + @Value("${pay.alipay.alipayRootCertPath:}") + private String alipayRootCertPath; + @Value("${pay.alipay.gatewayHost:openapi.alipay.com}") + private String gatewayHost; + @Value("${pay.alipay.protocol:https}") + private String protocol; + + @Autowired + private ApplicationContext applicationContext; + + // 强制用UTF-8读取密钥文件,无论JVM file.encoding如何,保证验签一致 + private String getAppPrivateKey() throws Exception { + if (appPrivateKey.startsWith("classpath")) { + Resource resource = applicationContext.getResource(appPrivateKey); + try (InputStream inputStream = resource.getInputStream()) { + // 密钥文件必须为UTF-8编码 + return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); + } + } + return appPrivateKey; + } + + // 强制用UTF-8读取密钥文件,无论JVM file.encoding如何,保证验签一致 + private String getAlipayPublicKey() throws Exception { + if (alipayPublicKey.startsWith("classpath")) { + Resource resource = applicationContext.getResource(alipayPublicKey); + try (InputStream inputStream = resource.getInputStream()) { + // 密钥文件必须为UTF-8编码 + return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); + } + } + return alipayPublicKey; + } + + @Bean + protected Config alipayBaseConfig() throws Exception { + // 设置参数(全局只需设置一次) + Config config = new Config(); + config.protocol = protocol; + config.gatewayHost = gatewayHost;// openapi-sandbox.dl.alipaydev.com||openapi.alipay.com + config.signType = signType; + config.appId = appId; + // 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中 + config.merchantPrivateKey = getAppPrivateKey(); + // 注:证书文件路径支持设置为文件系统中的路径或CLASS_PATH中的路径,优先从文件系统中加载,加载失败后会继续尝试从CLASS_PATH中加载 + // 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt + config.merchantCertPath = merchantCertPath; + // 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt + config.alipayCertPath = alipayCertPath; + // 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt + config.alipayRootCertPath = alipayRootCertPath; + // 注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可 + config.alipayPublicKey = getAlipayPublicKey(); + config.notifyUrl = this.notifyUrl; + Factory.setOptions(config); + return config; + } + +} + +// https://openapi-sandbox.dl.alipaydev.com/gateway.do \ No newline at end of file diff --git a/ruoyi-scene-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/service/IAliPayService.java b/ruoyi-scene-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/service/IAliPayService.java new file mode 100644 index 0000000..9d33122 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/service/IAliPayService.java @@ -0,0 +1,9 @@ +package com.ruoyi.pay.alipay.service; + +import java.util.Map; + +import com.ruoyi.pay.service.PayService; + +public interface IAliPayService extends PayService { + public void callback(Map params); +} diff --git a/ruoyi-scene-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/service/Impl/AliPayService.java b/ruoyi-scene-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/service/Impl/AliPayService.java new file mode 100644 index 0000000..335c852 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/service/Impl/AliPayService.java @@ -0,0 +1,126 @@ +package com.ruoyi.pay.alipay.service.Impl; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import com.alipay.easysdk.factory.Factory; +import com.alipay.easysdk.kernel.Config; +import com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse; +import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.pay.alipay.service.IAliPayService; +import com.ruoyi.pay.domain.PayOrder; +import com.ruoyi.pay.service.IPayOrderService; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Service("pay:service:alipay") +@ConditionalOnProperty(prefix = "pay.alipay", name = "enabled", havingValue = "true") +public class AliPayService implements IAliPayService { + public void callback(Map params) { + } + + @Autowired + private IPayOrderService payOrderService; + + @Autowired + Config config; + + public String payUrl(PayOrder payOrder) { + + try { + StringBuilder notifyUrlBuilder = new StringBuilder(); + String orderNotifyUrl = notifyUrlBuilder.append(config.notifyUrl) + .append("/").append(payOrder.getOrderNumber()) + .append("/pay").toString(); + AlipayTradePagePayResponse response = Factory.Payment.Page().pay( + payOrder.getOrderContent(), + payOrder.getOrderNumber(), + String.valueOf(Double.parseDouble(payOrder.getActualAmount()) / 100), + orderNotifyUrl); + return response.getBody(); + } catch (Exception e) { + throw new ServiceException("创建支付宝支付URL失败"); + } + } + + @Override + public String notify(HttpServletRequest request, HttpServletResponse response, PayOrder payOrder, String type) { + Map parameterMap = request.getParameterMap(); + if (parameterMap != null && !parameterMap.isEmpty()) { + Map params = new HashMap<>(); + for (String name : parameterMap.keySet()) { + params.put(name, request.getParameter(name)); + } + String orderNumber = params.get("out_trade_no"); + String theTradeNo = params.get("trade_no"); + payOrder = payOrderService.selectPayOrderByOrderNumber(orderNumber); + payOrder.setPayType("alipay"); + payOrder.setThirdNumber(theTradeNo); + try { + if (Factory.Payment.Common().verifyNotify(params)) { + payOrder.setOrderStatus("已支付"); + payOrderService.updatePayOrder(payOrder); + } + return "success"; + } catch (Exception e) { + e.printStackTrace(); + } + } + return "fail"; + } + + @Override + public PayOrder query(PayOrder payOrder) { + try { + // 使用支付宝SDK查询订单状态 + com.alipay.easysdk.payment.common.models.AlipayTradeQueryResponse response = Factory.Payment.Common() + .query(payOrder.getOrderNumber()); + + // 根据查询结果更新订单状态 + if ("10000".equals(response.code)) { + String tradeStatus = response.tradeStatus; + String orderStatus = switch (tradeStatus) { + case "TRADE_SUCCESS", "TRADE_FINISHED" -> "已支付"; + case "WAIT_BUYER_PAY" -> "待支付"; + case "TRADE_CLOSED" -> "已关闭"; + default -> "未知状态" + tradeStatus; + }; + // 更新订单信息 + payOrderService.updateStatus(payOrder.getOrderNumber(), orderStatus); + } else { + throw new ServiceException("查询支付宝订单失败:" + response.subMsg); + } + + return payOrder; + } catch (Exception e) { + throw new ServiceException("查询支付宝订单异常:" + e.getMessage()); + } + } + + @Override + public PayOrder refund(PayOrder payOrder) { + try { + // 使用支付宝SDK进行退款 + AlipayTradeRefundResponse response = Factory.Payment.Common().refund( + payOrder.getOrderNumber(), + String.valueOf(Double.parseDouble(payOrder.getActualAmount()) / 100)); + + // 处理退款结果 + if ("10000".equals(response.code)) { + payOrderService.updateStatus(payOrder.getOrderNumber(), "已退款"); + } else { + throw new ServiceException("支付宝退款失败:" + response.subMsg); + } + + return payOrder; + } catch (Exception e) { + throw new ServiceException("支付宝退款异常:" + e.getMessage()); + } + } +} diff --git a/ruoyi-scene-pay/ruoyi-pay-alipay/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/ruoyi-scene-pay/ruoyi-pay-alipay/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..9d3fbe3 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-alipay/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,29 @@ +{ + "properties": [ + { + "name": "pay.alipay.enabled", + "type": "java.lang.Boolean", + "description": "是否启用支付宝支付" + }, + { + "name": "pay.alipay.appId", + "type": "java.lang.String", + "description": "支付宝appid" + }, + { + "name": "pay.alipay.appPrivateKey", + "type": "java.lang.String", + "description": "支付宝应用私钥,可以直接用字符串,也可以是基于classpath的文件路径" + }, + { + "name": "pay.alipay.alipayPublicKey", + "type": "java.lang.String", + "description": "支付宝应用公钥,可以直接用字符串,也可以是基于classpath的文件路径" + }, + { + "name": "pay.alipay.notifyUrl", + "type": "java.lang.String", + "description": "支付宝支付回调地址" + } + ] +} \ No newline at end of file diff --git a/ruoyi-scene-pay/ruoyi-pay-common/pom.xml b/ruoyi-scene-pay/ruoyi-pay-common/pom.xml new file mode 100644 index 0000000..e1a6266 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-common/pom.xml @@ -0,0 +1,28 @@ + + + + ruoyi-scene-pay + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-pay-common + + + 支付基础模块 + + + + + + + com.ruoyi.geekxd + ruoyi-common + + + + + \ No newline at end of file diff --git a/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/domain/PayInvoice.java b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/domain/PayInvoice.java new file mode 100644 index 0000000..128acf2 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/domain/PayInvoice.java @@ -0,0 +1,168 @@ +package com.ruoyi.pay.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 发票对象 pay_invoice + * + * @author ruoyi + * @date 2024-06-11 + */ +@Schema(description = "发票对象") +public class PayInvoice extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + + /** 发票id */ + @Schema(title = "发票id") + private Long invoiceId; + + /** 订单号 */ + @Schema(title = "订单号") + @Excel(name = "订单号") + private String orderNumber; + + /** 发票类型 */ + @Schema(title = "发票类型") + @Excel(name = "发票类型") + private String invoiceType; + + /** 发票抬头 */ + @Schema(title = "发票抬头") + @Excel(name = "发票抬头") + private String invoiceHeader; + + /** 纳税人识别号 */ + @Schema(title = "纳税人识别号") + @Excel(name = "纳税人识别号") + private String invoiceNumber; + + /** 收票人手机号 */ + @Schema(title = "收票人手机号") + @Excel(name = "收票人手机号") + private String invoicePhone; + + /** 收票人邮箱 */ + @Schema(title = "收票人邮箱") + @Excel(name = "收票人邮箱") + private String invoiceEmail; + + /** 发票备注 */ + @Schema(title = "发票备注") + @Excel(name = "发票备注") + private String invoiceRemark; + public void setInvoiceId(Long invoiceId) + { + this.invoiceId = invoiceId; + } + + public Long getInvoiceId() + { + return invoiceId; + } + + + public void setOrderNumber(String orderNumber) + { + this.orderNumber = orderNumber; + } + + public String getOrderNumber() + { + return orderNumber; + } + + + public void setInvoiceType(String invoiceType) + { + this.invoiceType = invoiceType; + } + + public String getInvoiceType() + { + return invoiceType; + } + + + public void setInvoiceHeader(String invoiceHeader) + { + this.invoiceHeader = invoiceHeader; + } + + public String getInvoiceHeader() + { + return invoiceHeader; + } + + + public void setInvoiceNumber(String invoiceNumber) + { + this.invoiceNumber = invoiceNumber; + } + + public String getInvoiceNumber() + { + return invoiceNumber; + } + + + public void setInvoicePhone(String invoicePhone) + { + this.invoicePhone = invoicePhone; + } + + public String getInvoicePhone() + { + return invoicePhone; + } + + + public void setInvoiceEmail(String invoiceEmail) + { + this.invoiceEmail = invoiceEmail; + } + + public String getInvoiceEmail() + { + return invoiceEmail; + } + + + public void setInvoiceRemark(String invoiceRemark) + { + this.invoiceRemark = invoiceRemark; + } + + public String getInvoiceRemark() + { + return invoiceRemark; + } + + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("invoiceId", getInvoiceId()) + .append("orderNumber", getOrderNumber()) + .append("invoiceType", getInvoiceType()) + .append("invoiceHeader", getInvoiceHeader()) + .append("invoiceNumber", getInvoiceNumber()) + .append("invoicePhone", getInvoicePhone()) + .append("invoiceEmail", getInvoiceEmail()) + .append("invoiceRemark", getInvoiceRemark()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/domain/PayOrder.java b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/domain/PayOrder.java new file mode 100644 index 0000000..101716f --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/domain/PayOrder.java @@ -0,0 +1,183 @@ +package com.ruoyi.pay.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 订单对象 pay_order + * + * @author ruoyi + * @date 2024-06-11 + */ +@Schema(description = "订单对象") +public class PayOrder extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 订单ID */ + @Schema(title = "订单ID") + private Long orderId; + + /** 订单号 */ + @Schema(title = "商户订单号") + @Excel(name = "商户订单号") + private String orderNumber; + + /** 第三方订单号 */ + @Schema(title = "第三方订单号") + @Excel(name = "第三方订单号") + private String thirdNumber; + + /** 订单状态 */ + @Schema(title = "订单状态") + @Excel(name = "订单状态") + private String orderStatus; + + /** 订单总金额 */ + @Schema(title = "订单总金额") + @Excel(name = "订单总金额") + private String totalAmount; + + /** 实际支付金额 */ + @Schema(title = "实际支付金额") + @Excel(name = "实际支付金额") + private String actualAmount; + + /** 订单内容 */ + @Schema(title = "订单内容") + @Excel(name = "订单内容") + private String orderContent; + + /** 负载信息 */ + @Schema(title = "负载信息") + @Excel(name = "负载信息") + private String orderMessage; + + /** 支付方式 */ + @Schema(title = "支付方式") + @Excel(name = "支付方式") + private String payType; + + /** 支付时间 */ + @Schema(title = "支付时间") + @Excel(name = "支付时间") + private java.util.Date payTime; + + /** 支付人 */ + @Schema(title = "支付人") + @Excel(name = "支付人") + private String payBy; + + public void setOrderId(Long orderId) { + this.orderId = orderId; + } + + public Long getOrderId() { + return orderId; + } + + public void setOrderNumber(String orderNumber) { + this.orderNumber = orderNumber; + } + + public String getOrderNumber() { + return orderNumber; + } + + public void setThirdNumber(String thirdNumber) { + this.thirdNumber = thirdNumber; + } + + public String getThirdNumber() { + return thirdNumber; + } + + public void setOrderStatus(String orderStatus) { + this.orderStatus = orderStatus; + } + + public String getOrderStatus() { + return orderStatus; + } + + public void setTotalAmount(String totalAmount) { + this.totalAmount = totalAmount; + } + + public String getTotalAmount() { + return totalAmount; + } + + public void setActualAmount(String actualAmount) { + this.actualAmount = actualAmount; + } + + public String getActualAmount() { + return actualAmount; + } + + public void setOrderContent(String orderContent) { + this.orderContent = orderContent; + } + + public String getOrderContent() { + return orderContent; + } + + public void setOrderMessage(String orderMessage) { + this.orderMessage = orderMessage; + } + + public String getOrderMessage() { + return orderMessage; + } + + public void setPayType(String payType) { + this.payType = payType; + } + + public String getPayType() { + return payType; + } + + public void setPayTime(java.util.Date payTime) { + this.payTime = payTime; + } + + public java.util.Date getPayTime() { + return payTime; + } + + public void setPayBy(String payBy) { + this.payBy = payBy; + } + + public String getPayBy() { + return payBy; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("orderId", getOrderId()) + .append("orderNumber", getOrderNumber()) + .append("orderStatus", getOrderStatus()) + .append("totalAmount", getTotalAmount()) + .append("actualAmount", getActualAmount()) + .append("orderContent", getOrderContent()) + .append("orderMessage", getOrderMessage()) + .append("payType", getPayType()) + .append("payTime", getPayTime()) + .append("payBy", getPayBy()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/mapper/PayInvoiceMapper.java b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/mapper/PayInvoiceMapper.java new file mode 100644 index 0000000..8d47aaf --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/mapper/PayInvoiceMapper.java @@ -0,0 +1,62 @@ +package com.ruoyi.pay.mapper; + +import java.util.List; + +import com.ruoyi.pay.domain.PayInvoice; + +/** + * 发票Mapper接口 + * + * @author ruoyi + * @date 2024-06-11 + */ +public interface PayInvoiceMapper +{ + /** + * 查询发票 + * + * @param invoiceId 发票主键 + * @return 发票 + */ + public PayInvoice selectPayInvoiceByInvoiceId(Long invoiceId); + + /** + * 查询发票列表 + * + * @param payInvoice 发票 + * @return 发票集合 + */ + public List selectPayInvoiceList(PayInvoice payInvoice); + + /** + * 新增发票 + * + * @param payInvoice 发票 + * @return 结果 + */ + public int insertPayInvoice(PayInvoice payInvoice); + + /** + * 修改发票 + * + * @param payInvoice 发票 + * @return 结果 + */ + public int updatePayInvoice(PayInvoice payInvoice); + + /** + * 删除发票 + * + * @param invoiceId 发票主键 + * @return 结果 + */ + public int deletePayInvoiceByInvoiceId(Long invoiceId); + + /** + * 批量删除发票 + * + * @param invoiceIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deletePayInvoiceByInvoiceIds(Long[] invoiceIds); +} diff --git a/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/mapper/PayOrderMapper.java b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/mapper/PayOrderMapper.java new file mode 100644 index 0000000..9f88c37 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/mapper/PayOrderMapper.java @@ -0,0 +1,73 @@ +package com.ruoyi.pay.mapper; + +import java.util.List; + +import com.ruoyi.pay.domain.PayOrder; + +/** + * 订单Mapper接口 + * + * @author Dftre + * @date 2024-02-15 + */ +public interface PayOrderMapper +{ + /** + * 查询订单 + * + * @param orderId 订单主键 + * @return 订单 + */ + public PayOrder selectPayOrderByOrderId(Long orderId); + + /** + * 查询订单 + * + * @param orderNumber 订单号 + * @return 订单集合 + */ + public PayOrder selectPayOrderByOrderNumber(String orderNumber); + + /** + * 查询订单列表 + * + * @param payOrder 订单 + * @return 订单集合 + */ + public List selectPayOrderList(PayOrder payOrder); + + /** + * 新增订单 + * + * @param payOrder 订单 + * @return 结果 + */ + public int insertPayOrder(PayOrder payOrder); + + /** + * 修改订单 + * + * @param payOrder 订单 + * @return 结果 + */ + public int updatePayOrder(PayOrder payOrder); + + /** + * 删除订单 + * + * @param orderId 订单主键 + * @return 结果 + */ + public int deletePayOrderByOrderId(Long orderId); + + /** + * 批量删除订单 + * + * @param orderIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deletePayOrderByOrderIds(Long[] orderIds); + + public int deletePayOrderByOrderNumber(String orderNumber); + public int updateStatus(String orderNumber, String orderStatus); +} diff --git a/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/IPayInvoiceService.java b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/IPayInvoiceService.java new file mode 100644 index 0000000..24d42a0 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/IPayInvoiceService.java @@ -0,0 +1,62 @@ +package com.ruoyi.pay.service; + +import java.util.List; + +import com.ruoyi.pay.domain.PayInvoice; + +/** + * 发票Service接口 + * + * @author ruoyi + * @date 2024-06-11 + */ +public interface IPayInvoiceService +{ + /** + * 查询发票 + * + * @param invoiceId 发票主键 + * @return 发票 + */ + public PayInvoice selectPayInvoiceByInvoiceId(Long invoiceId); + + /** + * 查询发票列表 + * + * @param payInvoice 发票 + * @return 发票集合 + */ + public List selectPayInvoiceList(PayInvoice payInvoice); + + /** + * 新增发票 + * + * @param payInvoice 发票 + * @return 结果 + */ + public int insertPayInvoice(PayInvoice payInvoice); + + /** + * 修改发票 + * + * @param payInvoice 发票 + * @return 结果 + */ + public int updatePayInvoice(PayInvoice payInvoice); + + /** + * 批量删除发票 + * + * @param invoiceIds 需要删除的发票主键集合 + * @return 结果 + */ + public int deletePayInvoiceByInvoiceIds(Long[] invoiceIds); + + /** + * 删除发票信息 + * + * @param invoiceId 发票主键 + * @return 结果 + */ + public int deletePayInvoiceByInvoiceId(Long invoiceId); +} diff --git a/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/IPayOrderService.java b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/IPayOrderService.java new file mode 100644 index 0000000..d5d2d52 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/IPayOrderService.java @@ -0,0 +1,80 @@ +package com.ruoyi.pay.service; + +import java.util.List; + +import com.ruoyi.pay.domain.PayOrder; + +/** + * 订单Service接口 + * + * @author ruoyi + * @date 2024-06-11 + */ +public interface IPayOrderService +{ + /** + * 查询订单 + * + * @param orderId 订单主键 + * @return 订单 + */ + public PayOrder selectPayOrderByOrderId(Long orderId); + + /** + * 查询订单 + * + * @param orderNumber 订单号 + * @return 订单集合 + */ + public PayOrder selectPayOrderByOrderNumber(String orderNumber); + + /** + * 查询订单列表 + * + * @param payOrder 订单 + * @return 订单集合 + */ + public List selectPayOrderList(PayOrder payOrder); + + /** + * 新增订单 + * + * @param payOrder 订单 + * @return 结果 + */ + public int insertPayOrder(PayOrder payOrder); + + /** + * 修改订单 + * + * @param payOrder 订单 + * @return 结果 + */ + public int updatePayOrder(PayOrder payOrder); + + /** + * 批量删除订单 + * + * @param orderIds 需要删除的订单主键集合 + * @return 结果 + */ + public int deletePayOrderByOrderIds(Long[] orderIds); + + + /** + * 删除订单信息 + * + * @param orderId 订单主键 + * @return 结果 + */ + public int deletePayOrderByOrderNumber(String orderNumber); + + /** + * 删除订单信息 + * + * @param orderId 订单主键 + * @return 结果 + */ + public int deletePayOrderByOrderId(Long orderId); + public int updateStatus(String orderNumber, String orderStatus); +} diff --git a/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/PayService.java b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/PayService.java new file mode 100644 index 0000000..5955272 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/PayService.java @@ -0,0 +1,17 @@ +package com.ruoyi.pay.service; + +import com.ruoyi.pay.domain.PayOrder; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public interface PayService { + String payUrl(PayOrder payOrder); + + String notify(HttpServletRequest servletRequest, HttpServletResponse response, PayOrder payOrder, String type) + throws Exception; + + PayOrder query(PayOrder payOrder); + + PayOrder refund(PayOrder payOrder); +} diff --git a/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/impl/PayInvoiceServiceImpl.java b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/impl/PayInvoiceServiceImpl.java new file mode 100644 index 0000000..7d3465a --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/impl/PayInvoiceServiceImpl.java @@ -0,0 +1,98 @@ +package com.ruoyi.pay.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.pay.domain.PayInvoice; +import com.ruoyi.pay.mapper.PayInvoiceMapper; +import com.ruoyi.pay.service.IPayInvoiceService; + +/** + * 发票Service业务层处理 + * + * @author ruoyi + * @date 2024-06-11 + */ +@Service +public class PayInvoiceServiceImpl implements IPayInvoiceService +{ + @Autowired + private PayInvoiceMapper payInvoiceMapper; + + /** + * 查询发票 + * + * @param invoiceId 发票主键 + * @return 发票 + */ + @Override + public PayInvoice selectPayInvoiceByInvoiceId(Long invoiceId) + { + return payInvoiceMapper.selectPayInvoiceByInvoiceId(invoiceId); + } + + /** + * 查询发票列表 + * + * @param payInvoice 发票 + * @return 发票 + */ + @Override + public List selectPayInvoiceList(PayInvoice payInvoice) + { + return payInvoiceMapper.selectPayInvoiceList(payInvoice); + } + + /** + * 新增发票 + * + * @param payInvoice 发票 + * @return 结果 + */ + @Override + public int insertPayInvoice(PayInvoice payInvoice) + { + payInvoice.setCreateTime(DateUtils.getNowDate()); + return payInvoiceMapper.insertPayInvoice(payInvoice); + } + + /** + * 修改发票 + * + * @param payInvoice 发票 + * @return 结果 + */ + @Override + public int updatePayInvoice(PayInvoice payInvoice) + { + payInvoice.setUpdateTime(DateUtils.getNowDate()); + return payInvoiceMapper.updatePayInvoice(payInvoice); + } + + /** + * 批量删除发票 + * + * @param invoiceIds 需要删除的发票主键 + * @return 结果 + */ + @Override + public int deletePayInvoiceByInvoiceIds(Long[] invoiceIds) + { + return payInvoiceMapper.deletePayInvoiceByInvoiceIds(invoiceIds); + } + + /** + * 删除发票信息 + * + * @param invoiceId 发票主键 + * @return 结果 + */ + @Override + public int deletePayInvoiceByInvoiceId(Long invoiceId) + { + return payInvoiceMapper.deletePayInvoiceByInvoiceId(invoiceId); + } +} diff --git a/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/impl/PayOrderServiceImpl.java b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/impl/PayOrderServiceImpl.java new file mode 100644 index 0000000..8d0bde8 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-common/src/main/java/com/ruoyi/pay/service/impl/PayOrderServiceImpl.java @@ -0,0 +1,123 @@ +package com.ruoyi.pay.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.pay.domain.PayOrder; +import com.ruoyi.pay.mapper.PayOrderMapper; +import com.ruoyi.pay.service.IPayOrderService; + +/** + * 订单Service业务层处理 + * + * @author ruoyi + * @date 2024-06-11 + */ +@Service +public class PayOrderServiceImpl implements IPayOrderService { + @Autowired + private PayOrderMapper payOrderMapper; + + /** + * 查询订单 + * + * @param orderId 订单主键 + * @return 订单 + */ + @Override + public PayOrder selectPayOrderByOrderId(Long orderId) { + return payOrderMapper.selectPayOrderByOrderId(orderId); + } + + /** + * 查询订单 + * + * @param orderNumber 订单号 + * @return 订单集合 + */ + @Override + public PayOrder selectPayOrderByOrderNumber(String orderNumber) { + PayOrder payOrder = payOrderMapper.selectPayOrderByOrderNumber(orderNumber); + if (payOrder == null) { + throw new ServiceException("订单不存在,请检查订单号是否正确"); + } else { + return payOrder; + } + } + + /** + * 查询订单列表 + * + * @param payOrder 订单 + * @return 订单 + */ + @Override + public List selectPayOrderList(PayOrder payOrder) { + return payOrderMapper.selectPayOrderList(payOrder); + } + + /** + * 新增订单 + * + * @param payOrder 订单 + * @return 结果 + */ + @Override + public int insertPayOrder(PayOrder payOrder) { + return payOrderMapper.insertPayOrder(payOrder); + } + + /** + * 修改订单 + * + * @param payOrder 订单 + * @return 结果 + */ + @Override + public int updatePayOrder(PayOrder payOrder) { + payOrder.setUpdateTime(DateUtils.getNowDate()); + return payOrderMapper.updatePayOrder(payOrder); + } + + /** + * 批量删除订单 + * + * @param orderIds 需要删除的订单主键 + * @return 结果 + */ + @Override + public int deletePayOrderByOrderIds(Long[] orderIds) { + return payOrderMapper.deletePayOrderByOrderIds(orderIds); + } + + /** + * 删除订单信息 + * + * @param orderId 订单主键 + * @return 结果 + */ + @Override + public int deletePayOrderByOrderId(Long orderId) { + return payOrderMapper.deletePayOrderByOrderId(orderId); + } + + /** + * 删除订单信息 + * + * @param orderIds 需要删除的订单主键 + * @return 结果 + */ + @Override + public int deletePayOrderByOrderNumber(String orderNumber) { + return payOrderMapper.deletePayOrderByOrderNumber(orderNumber); + } + + @Override + public int updateStatus(String orderNumber, String orderStatus) { + return payOrderMapper.updateStatus(orderNumber, orderStatus); + } +} diff --git a/ruoyi-scene-pay/ruoyi-pay-common/src/main/resources/mapper/pay/PayInvoiceMapper.xml b/ruoyi-scene-pay/ruoyi-pay-common/src/main/resources/mapper/pay/PayInvoiceMapper.xml new file mode 100644 index 0000000..e661abd --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-common/src/main/resources/mapper/pay/PayInvoiceMapper.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + select invoice_id, order_number, invoice_type, invoice_header, invoice_number, invoice_phone, invoice_email, invoice_remark, create_by, create_time, update_by, update_time, remark from pay_invoice + + + + + + + + insert into pay_invoice + + invoice_id, + order_number, + invoice_type, + invoice_header, + invoice_number, + invoice_phone, + invoice_email, + invoice_remark, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{invoiceId}, + #{orderNumber}, + #{invoiceType}, + #{invoiceHeader}, + #{invoiceNumber}, + #{invoicePhone}, + #{invoiceEmail}, + #{invoiceRemark}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update pay_invoice + + order_number = #{orderNumber}, + invoice_type = #{invoiceType}, + invoice_header = #{invoiceHeader}, + invoice_number = #{invoiceNumber}, + invoice_phone = #{invoicePhone}, + invoice_email = #{invoiceEmail}, + invoice_remark = #{invoiceRemark}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where pay_invoice.invoice_id = #{invoiceId} + + + + delete from pay_invoice where invoice_id = #{invoiceId} + + + + delete from pay_invoice where invoice_id in + + #{invoiceId} + + + \ No newline at end of file diff --git a/ruoyi-scene-pay/ruoyi-pay-common/src/main/resources/mapper/pay/PayOrderMapper.xml b/ruoyi-scene-pay/ruoyi-pay-common/src/main/resources/mapper/pay/PayOrderMapper.xml new file mode 100644 index 0000000..1c0597a --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-common/src/main/resources/mapper/pay/PayOrderMapper.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + select order_id, order_number,third_number, order_status, total_amount, actual_amount, order_content, order_message, pay_type, pay_time, pay_by, create_by, create_time, update_by, update_time, remark from pay_order + + + + + + + + + + insert into pay_order + + order_number, + third_number, + order_status, + total_amount, + actual_amount, + order_content, + order_message, + create_by, + create_time, + update_by, + update_time, + remark, + pay_type, + pay_time, + pay_by, + + + #{orderNumber}, + #{thirdNumber}, + #{orderStatus}, + #{totalAmount}, + #{actualAmount}, + #{orderContent}, + #{orderMessage}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{payType}, + #{payTime}, + #{payBy}, + + + + + update pay_order + + order_number = #{orderNumber}, + third_number = #{thirdNumber}, + order_status = #{orderStatus}, + total_amount = #{totalAmount}, + actual_amount = #{actualAmount}, + order_content = #{orderContent}, + order_message = #{orderMessage}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + pay_type = #{payType}, + pay_time = #{payTime}, + pay_by = #{payBy}, + + where pay_order.order_id = #{orderId} + + + + delete from pay_order where order_id = #{orderId} + + + + delete from pay_order where order_number= #{orderNumber} + + + + delete from pay_order where order_id in + + #{orderId} + + + + + update pay_order set order_status = #{orderStatus} where order_number = #{orderNumber} + + \ No newline at end of file diff --git a/ruoyi-scene-pay/ruoyi-pay-sqb/pom.xml b/ruoyi-scene-pay/ruoyi-pay-sqb/pom.xml new file mode 100644 index 0000000..730a6f9 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-sqb/pom.xml @@ -0,0 +1,27 @@ + + + + ruoyi-scene-pay + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-pay-sqb + + + 收钱吧支付模块 + + + + + + com.ruoyi.geekxd + ruoyi-pay-common + + + + + \ No newline at end of file diff --git a/ruoyi-scene-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/config/SqbConfig.java b/ruoyi-scene-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/config/SqbConfig.java new file mode 100644 index 0000000..b70dd7a --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/config/SqbConfig.java @@ -0,0 +1,116 @@ +package com.ruoyi.pay.sqb.config; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnProperty(prefix = "pay.sqb", name = "enabled", havingValue = "true") +public class SqbConfig { + @Value("${pay.sqb.apiDomain}") + private String apiDomain; + + @Value("${pay.sqb.terminalSn}") + private String terminalSn; + + @Value("${pay.sqb.terminalKey}") + private String terminalKey; + + @Value("${pay.sqb.appId}") + private String appId; + + @Value("${pay.sqb.vendorSn}") + private String vendorSn; + + @Value("${pay.sqb.vendorKey}") + private String vendorKey; + + @Value("${pay.sqb.notifyUrl}") + private String notifyUrl; + + @Value("${pay.sqb.publicKey}") + private String publicKey; + + @Autowired + private ApplicationContext applicationContext; + + public String getPublicKey() throws Exception { + if (publicKey.startsWith("classpath")) { + Resource resource = applicationContext.getResource(publicKey); + InputStream inputStream = resource.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String alipayPublicKeyValue = bufferedReader.lines().collect(Collectors.joining(System.lineSeparator())); + bufferedReader.close(); + publicKey = alipayPublicKeyValue; + } + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getNotifyUrl() { + return notifyUrl; + } + + public void setNotifyUrl(String notifyUrl) { + this.notifyUrl = notifyUrl; + } + + public String getApiDomain() { + return apiDomain; + } + + public void setApiDomain(String apiDomain) { + this.apiDomain = apiDomain; + } + + public String getTerminalSn() { + return terminalSn; + } + + public void setTerminalSn(String terminalSn) { + this.terminalSn = terminalSn; + } + + public String getTerminalKey() { + return terminalKey; + } + + public void setTerminalKey(String terminalKey) { + this.terminalKey = terminalKey; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getVendorSn() { + return vendorSn; + } + + public void setVendorSn(String vendorSn) { + this.vendorSn = vendorSn; + } + + public String getVendorKey() { + return vendorKey; + } + + public void setVendorKey(String vendorKey) { + this.vendorKey = vendorKey; + } +} diff --git a/ruoyi-scene-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/service/ISqbPayService.java b/ruoyi-scene-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/service/ISqbPayService.java new file mode 100644 index 0000000..82fea2b --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/service/ISqbPayService.java @@ -0,0 +1,6 @@ +package com.ruoyi.pay.sqb.service; + +import com.ruoyi.pay.service.PayService; + +public interface ISqbPayService extends PayService { +} diff --git a/ruoyi-scene-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/service/Impl/SQBServiceImpl.java b/ruoyi-scene-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/service/Impl/SQBServiceImpl.java new file mode 100644 index 0000000..08455ba --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/service/Impl/SQBServiceImpl.java @@ -0,0 +1,338 @@ +package com.ruoyi.pay.sqb.service.Impl; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.UnrecoverableKeyException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.springframework.util.StreamUtils; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.http.HttpUtils; +import com.ruoyi.common.utils.sign.Md5Utils; +import com.ruoyi.pay.domain.PayOrder; +import com.ruoyi.pay.service.IPayOrderService; +import com.ruoyi.pay.sqb.config.SqbConfig; +import com.ruoyi.pay.sqb.service.ISqbPayService; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service("pay:service:sqb") +@ConditionalOnProperty(prefix = "pay.sqb", name = "enabled", havingValue = "true") +public class SQBServiceImpl implements ISqbPayService { + @Autowired + private SqbConfig sqbConfig; + + @Autowired + private IPayOrderService payOrderService; + + /** + * http POST 请求 + * + * @param url:请求地址 + * @param body: body实体字符串 + * @param sign:签名 + * @param sn: 序列号 + * @return + */ + public static String httpPost(String url, Object body, String sign, String sn) + throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + String xmlRes = "{}"; + try { + Map header = new HashMap<>(); + header.put("Authorization", sn + " " + sign); + // 使用新的 HttpUtils.postJson 支持 headers + xmlRes = HttpUtils.postJson(url, body, header); + } catch (Exception e) { + } + return xmlRes; + } + + /** + * 计算字符串的MD5值 + * + * @param signStr:签名字符串 + * @return + */ + private String getSign(String signStr) { + try { + String md5 = Md5Utils.encryptMd5(signStr); + return md5; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 终端激活 + * + * @param code:激活码 + * @return {terminal_sn:"$终端号",terminal_key:"$终端密钥"} + */ + public JSONObject activate(String code, String deviceId, String clientSn, String name) { + String url = sqbConfig.getApiDomain() + "/terminal/activate"; + JSONObject params = new JSONObject(); + try { + params.put("app_id", sqbConfig.getAppId()); // app_id,必填 + params.put("code", code); // 激活码,必填 + params.put("device_id", deviceId); // 客户方收银终端序列号,需保证同一app_id下唯一,必填。为方便识别,建议格式为“品牌名+门店编号+‘POS’+POS编号“ + params.put("client_sn", clientSn); // 客户方终端编号,一般客户方或系统给收银终端的编号,必填 + params.put("name", name); // 客户方终端名称,必填 + String sign = getSign(params.toString() + sqbConfig.getVendorKey()); + String result = httpPost(url, params.toString(), sign, sqbConfig.getVendorSn()); + JSONObject retObj = JSON.parseObject(result); + String resCode = retObj.get("result_code").toString(); + if (!"200".equals(resCode)) { + return null; + } + String responseStr = retObj.get("biz_response").toString(); + JSONObject terminal = JSON.parseObject(responseStr); + if (terminal.get("terminal_sn") == null || terminal.get("terminal_key") == null) { + return null; + } + return terminal; + } catch (Exception e) { + return null; + } + } + + /** + * 终端签到 + * + * @return {terminal_sn:"$终端号",terminal_key:"$终端密钥"} + */ + public JSONObject checkin() { + String url = sqbConfig.getApiDomain() + "/terminal/checkin"; + JSONObject params = new JSONObject(); + try { + params.put("terminal_sn", sqbConfig.getTerminalSn()); + params.put("device_id", "HUISUAN001POS01"); + params.put("os_info", "Mac OS"); + params.put("sdk_version", "Java SDK v1.0"); + String sign = getSign(params.toString() + sqbConfig.getTerminalKey()); + String result = httpPost(url, params.toString(), sign, sqbConfig.getTerminalSn()); + JSONObject retObj = JSON.parseObject(result); + String resCode = retObj.get("result_code").toString(); + if (!"200".equals(resCode)) { + return null; + } + String responseStr = retObj.get("biz_response").toString(); + JSONObject terminal = JSON.parseObject(responseStr); + if (terminal.get("terminal_sn") == null || terminal.get("terminal_key") == null) { + return null; + } + return terminal; + } catch (Exception e) { + return null; + } + } + + /** + * 退款 + * + * @return + */ + public PayOrder refund(PayOrder payOrder) { + String url = sqbConfig.getApiDomain() + "/upay/v2/refund"; + JSONObject params = new JSONObject(); + try { + params.put("terminal_sn", sqbConfig.getTerminalSn()); // 收钱吧终端ID + params.put("client_sn", payOrder.getOrderNumber()); // 商户系统订单号,必须在商户系统内唯一;且长度不超过64字节 + params.put("refund_amount", payOrder.getTotalAmount()); // 退款金额 + params.put("refund_request_no", "1"); // 商户退款所需序列号,表明是第几次退款 + params.put("operator", "kay"); // 门店操作员 + + String sign = getSign(params.toString() + sqbConfig.getTerminalKey()); + String result = httpPost(url, params, sign, sqbConfig.getTerminalSn()); + JSONObject retObj = JSON.parseObject(result); + JSONObject bizResponse = retObj.getJSONObject("biz_response"); + if ("REFUNDED".equals(bizResponse.getString("order_status"))) { + payOrderService.updateStatus(payOrder.getOrderNumber(), "已退款"); + } else { + log.error("退款失败"); + } + return payOrder; + } catch (Exception e) { + return null; + } + } + + /** + * 查询 + * + * @return + */ + @Override + public PayOrder query(PayOrder payOrder) { + String url = sqbConfig.getApiDomain() + "/upay/v2/query"; + JSONObject params = new JSONObject(); + try { + params.put("terminal_sn", sqbConfig.getTerminalSn()); // 终端号 + params.put("client_sn", payOrder.getOrderNumber()); // 商户系统订单号,必须在商户系统内唯一;且长度不超过64字节 + System.out.println(params.toString() + sqbConfig.getTerminalKey()); + String sign = getSign(params.toString() + sqbConfig.getTerminalKey()); + String result = httpPost(url, params, sign, sqbConfig.getTerminalSn()); + JSONObject retObj = JSON.parseObject(result); + String resCode = retObj.get("result_code").toString(); + if (!"200".equals(resCode)) { + throw new ServiceException("查询支付订单失败"); + } else { + JSONObject response = retObj.getJSONObject("biz_response"); + System.out.println(response); + } + return payOrder; + } catch (Exception e) { + return null; + } + } + + @Override + public String payUrl(PayOrder payOrder) { + return payUrl(payOrder, null); + } + + public String payUrl(PayOrder payOrder, String notifyBaseUrl) { + if (payOrder.getRemark() == null) { + payOrder.setRemark("支付"); + } + // sqbConfig.getNotifyUrl() + payOrder.getOrderNumber() + "/pay"; + StringBuilder notifyUrlBuilder = new StringBuilder(); + String orderNotifyUrl = notifyUrlBuilder.append(sqbConfig.getNotifyUrl()) + .append("/").append(payOrder.getOrderNumber()) + .append("/pay").toString(); + String param = "" + + "client_sn=" + payOrder.getOrderNumber() + + "¬ify_url=" + orderNotifyUrl + + "&operator=" + payOrder.getCreateBy() + + "&return_url=" + "https://www.shouqianba.com/" + + "&subject=" + payOrder.getRemark() + + "&terminal_sn=" + sqbConfig.getTerminalSn() + + "&total_amount=" + Long.valueOf(payOrder.getTotalAmount().toString()); + String urlParam; + try { + urlParam = "" + + "client_sn=" + payOrder.getOrderNumber() + + "¬ify_url=" + URLEncoder.encode(orderNotifyUrl, "UTF-8") + + "&operator=" + URLEncoder.encode(payOrder.getCreateBy(), "UTF-8") + + "&return_url=" + "https://www.shouqianba.com/" + + "&subject=" + URLEncoder.encode(payOrder.getRemark(), "UTF-8") + + "&terminal_sn=" + sqbConfig.getTerminalSn() + + "&total_amount=" + Long.valueOf(payOrder.getTotalAmount().toString()); + + String sign = getSign(param + "&key=" + sqbConfig.getTerminalKey()); + return "https://qr.shouqianba.com/gateway?" + urlParam + "&sign=" + sign.toUpperCase(); + } catch (Exception e) { + throw new ServiceException("生成收钱吧支付链接失败"); + } + } + + /** + * 预下单 + * + * @return + */ + public String precreate(PayOrder payOrder, String sn, String payway) { + String url = sqbConfig.getApiDomain() + "/upay/v2/precreate"; + JSONObject params = new JSONObject(); + try { + params.put("terminal_sn", sqbConfig.getTerminalSn()); // 收钱吧终端ID + params.put("client_sn", payOrder.getOrderNumber()); // 商户系统订单号,必须在商户系统内唯一;且长度不超过32字节 + params.put("total_amount", payOrder.getTotalAmount()); // 交易总金额 + // params.put("payway", payway); // 支付方式 + params.put("subject", "无简介"); // 交易简介 + params.put("operator", SecurityUtils.getUsername()); // 门店操作员 + + String sign = getSign(params.toString() + sqbConfig.getTerminalKey()); + String result = httpPost(url, params.toString(), sign, sqbConfig.getTerminalSn()); + return result; + } catch (Exception e) { + return null; + } + } + + /** + * 自动撤单 + * + * @param terminal_sn:终端号 + * @param terminal_key:终端密钥 + * @return + */ + public String cancel(String terminal_sn, String terminal_key) { + String url = sqbConfig.getApiDomain() + "/upay/v2/cancel"; + JSONObject params = new JSONObject(); + try { + params.put("terminal_sn", terminal_sn); // 终端号 + params.put("client_sn", "18348290098298292838"); // 商户系统订单号,必须在商户系统内唯一;且长度不超过64字节 + + String sign = getSign(params.toString() + terminal_key); + String result = httpPost(url, params.toString(), sign, terminal_sn); + + return result; + } catch (Exception e) { + return null; + } + } + + public boolean validateSign(String data, String sign) { + try { + // 使用SHA256WithRSA算法 + Signature signature = Signature.getInstance("SHA256WithRSA"); + + // 获取公钥 + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey localPublicKey = keyFactory + .generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(sqbConfig.getPublicKey()))); + // 初始化验证过程 + signature.initVerify(localPublicKey); + signature.update(data.getBytes()); + + // 解码签名 + byte[] bytesSign = Base64.getDecoder().decode(sign); + + // 验证签名 + return signature.verify(bytesSign); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public String notify(HttpServletRequest request, HttpServletResponse response, PayOrder payOrder, String type) { + try { + String requestBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); + JSONObject jsonObject = JSONObject.parseObject(requestBody); + String sign = request.getHeader("Authorization"); + if (!validateSign(requestBody, sign)) { + throw new ServiceException("收钱吧支付回调验签失败"); + } + System.out.println(jsonObject); + return "success"; + } catch (IOException e) { + e.printStackTrace(); + return "fail"; + } + } + +} diff --git a/ruoyi-scene-pay/ruoyi-pay-sqb/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/ruoyi-scene-pay/ruoyi-pay-sqb/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..cf85b11 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-sqb/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,49 @@ +{ + "properties": [ + { + "name": "pay.sqb.enabled", + "type": "java.lang.Boolean", + "description": "启用收钱吧支付" + }, + { + "name": "pay.sqb.appId", + "type": "java.lang.String", + "description": "收钱吧appId" + }, + { + "name": "pay.sqb.apiDomain", + "type": "java.lang.String", + "description": "收钱吧apiDomain" + }, + { + "name": "pay.sqb.terminalSn", + "type": "java.lang.String", + "description": "收钱吧terminalSn" + }, + { + "name": "pay.sqb.terminalKey", + "type": "java.lang.String", + "description": "收钱吧terminalKey" + }, + { + "name": "pay.sqb.vendorSn", + "type": "java.lang.String", + "description": "收钱吧vendorSn" + }, + { + "name": "pay.sqb.vendorKey", + "type": "java.lang.String", + "description": "收钱吧vendorKey" + }, + { + "name": "pay.sqb.publicKey", + "type": "java.lang.String", + "description": "收钱吧公钥,可以直接用字符串,也可以是基于classpath的文件路径" + }, + { + "name": "pay.sqb.notifyUrl", + "type": "java.lang.String", + "description": "收钱吧支付回调地址" + } + ] +} \ No newline at end of file diff --git a/ruoyi-scene-pay/ruoyi-pay-starter/pom.xml b/ruoyi-scene-pay/ruoyi-pay-starter/pom.xml new file mode 100644 index 0000000..1ae3c00 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-starter/pom.xml @@ -0,0 +1,46 @@ + + + + ruoyi-scene-pay + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-pay-starter + + + 第三方支付模块 + + + + + + com.ruoyi.geekxd + ruoyi-pay-common + + + + + com.ruoyi.geekxd + ruoyi-pay-sqb + + + + + com.ruoyi.geekxd + ruoyi-pay-alipay + + + + + com.ruoyi.geekxd + ruoyi-pay-wx + + + + + + \ No newline at end of file diff --git a/ruoyi-scene-pay/ruoyi-pay-starter/src/main/java/com/ruoyi/pay/controller/PayController.java b/ruoyi-scene-pay/ruoyi-pay-starter/src/main/java/com/ruoyi/pay/controller/PayController.java new file mode 100644 index 0000000..de16450 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-starter/src/main/java/com/ruoyi/pay/controller/PayController.java @@ -0,0 +1,130 @@ +package com.ruoyi.pay.controller; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.pay.domain.PayOrder; +import com.ruoyi.pay.service.IPayOrderService; +import com.ruoyi.pay.service.PayService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Tag(name = "支付业务") +@RequestMapping("/pay") +@RestController +public class PayController extends BaseController { + + private static final Logger logger = LoggerFactory.getLogger(PayController.class); + + @Autowired + private IPayOrderService payOrderService; + + @Autowired(required = false) + private Map payServiceMap; // alipay wechat sqb + + @PostConstruct + public void init() { + if (payServiceMap == null) { + payServiceMap = new HashMap<>(); + logger.warn("请注意,没有加载任何支付服务"); + } else { + payServiceMap.forEach((k, v) -> { + logger.info("已加载支付服务 {}", k); + }); + } + } + + @Operation(summary = "获取支付链接", description = "也不一定是链接,比如支付宝是一串表单代码") + @Parameters({ + @Parameter(name = "channel", description = "支付方式", required = true), + @Parameter(name = "orderNumber", description = "订单号", required = true) + }) + @GetMapping("/{channel}/url/{orderNumber}") + public R url(@PathVariable String channel, @PathVariable String orderNumber) throws Exception { + PayService payService = payServiceMap.get("pay:service:" + channel); + PayOrder payOrder = payOrderService.selectPayOrderByOrderNumber(orderNumber); + return R.ok(payService.payUrl(payOrder)); + } + + @Anonymous + @Operation(summary = "支付/退款回调") + @Parameters({ + @Parameter(name = "channel", description = "支付方式", required = true), + @Parameter(name = "type", description = "通知类型", required = false) + }) + @RequestMapping({ + "/{channel}/notify", + "/{channel}/notify/{orderNumber}", + "/{channel}/notify/{orderNumber}/{type}" + }) + public String notify( + @PathVariable String channel, + @PathVariable(name = "orderNumber", required = false) String orderNumber, + @PathVariable(name = "type", required = false) String type, + HttpServletRequest request, + HttpServletResponse response) throws Exception { + logger.info("notify type:{} channel: {}, orderNumber: {}", type, channel, orderNumber); + if (type == null) { + type = "pay"; + } + PayOrder payOrder = null; + if (!StringUtils.isEmpty(orderNumber)) { + payOrder = payOrderService.selectPayOrderByOrderNumber(orderNumber); + payOrder.setPayType(channel); + } + PayService payService = payServiceMap.get("pay:service:" + channel); + return payService.notify(request, response, payOrder, type); + } + + @Operation(summary = "查询支付状态") + @Parameters({ + @Parameter(name = "channel", description = "支付方式", required = true), + @Parameter(name = "orderNumber", description = "订单号", required = true) + }) + @PostMapping("/query/{orderNumber}") + public R query(@PathVariable(name = "orderNumber") String orderNumber) + throws Exception { + PayOrder payOrder = payOrderService.selectPayOrderByOrderNumber(orderNumber); + String channel = payOrder.getPayType(); + if (StringUtils.isEmpty(channel)) { + return R.ok(payOrder); + } + PayService payService = payServiceMap.get("pay:service:" + channel); + return R.ok(payService.query(payOrder)); + } + + @Operation(summary = "退款") + @PostMapping("/refund/{orderNumber}") + @Parameters({ + @Parameter(name = "channel", description = "支付方式", required = true), + }) + public AjaxResult refund(@PathVariable(name = "orderNumber") String orderNumber) { + PayOrder payOrder = payOrderService.selectPayOrderByOrderNumber(orderNumber); + String channel = payOrder.getPayType(); + if (StringUtils.isEmpty(channel)) { + return error("该订单尚未支付,无法退款"); + } + PayService payService = payServiceMap.get("pay:service:" + channel); + return success(payService.refund(payOrder)); + } +} diff --git a/ruoyi-scene-pay/ruoyi-pay-starter/src/main/java/com/ruoyi/pay/controller/PayInvoiceController.java b/ruoyi-scene-pay/ruoyi-pay-starter/src/main/java/com/ruoyi/pay/controller/PayInvoiceController.java new file mode 100644 index 0000000..ba4cb42 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-starter/src/main/java/com/ruoyi/pay/controller/PayInvoiceController.java @@ -0,0 +1,116 @@ +package com.ruoyi.pay.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.pay.domain.PayInvoice; +import com.ruoyi.pay.service.IPayInvoiceService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 发票Controller + * + * @author ruoyi + * @date 2024-06-11 + */ +@RestController +@RequestMapping("/pay/invoice") +@Tag(name = "【发票】管理") +public class PayInvoiceController extends BaseController +{ + @Autowired + private IPayInvoiceService payInvoiceService; + + /** + * 查询发票列表 + */ + @Operation(summary = "查询发票列表") + @PreAuthorize("@ss.hasPermi('pay:invoice:list')") + @GetMapping("/list") + public TableDataInfo list(PayInvoice payInvoice) + { + startPage(); + List list = payInvoiceService.selectPayInvoiceList(payInvoice); + return getDataTable(list); + } + + /** + * 导出发票列表 + */ + @Operation(summary = "导出发票列表") + @PreAuthorize("@ss.hasPermi('pay:invoice:export')") + @Log(title = "发票", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, PayInvoice payInvoice) + { + List list = payInvoiceService.selectPayInvoiceList(payInvoice); + ExcelUtil util = new ExcelUtil(PayInvoice.class); + util.exportExcel(response, list, "发票数据"); + } + + /** + * 获取发票详细信息 + */ + @Operation(summary = "获取发票详细信息") + @PreAuthorize("@ss.hasPermi('pay:invoice:query')") + @GetMapping(value = "/{invoiceId}") + public AjaxResult getInfo(@PathVariable("invoiceId") Long invoiceId) + { + return success(payInvoiceService.selectPayInvoiceByInvoiceId(invoiceId)); + } + + /** + * 新增发票 + */ + @Operation(summary = "新增发票") + @PreAuthorize("@ss.hasPermi('pay:invoice:add')") + @Log(title = "发票", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody PayInvoice payInvoice) + { + return toAjax(payInvoiceService.insertPayInvoice(payInvoice)); + } + + /** + * 修改发票 + */ + @Operation(summary = "修改发票") + @PreAuthorize("@ss.hasPermi('pay:invoice:edit')") + @Log(title = "发票", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody PayInvoice payInvoice) + { + return toAjax(payInvoiceService.updatePayInvoice(payInvoice)); + } + + /** + * 删除发票 + */ + @Operation(summary = "删除发票") + @PreAuthorize("@ss.hasPermi('pay:invoice:remove')") + @Log(title = "发票", businessType = BusinessType.DELETE) + @DeleteMapping("/{invoiceIds}") + public AjaxResult remove(@PathVariable( name = "invoiceIds" ) Long[] invoiceIds) + { + return toAjax(payInvoiceService.deletePayInvoiceByInvoiceIds(invoiceIds)); + } +} diff --git a/ruoyi-scene-pay/ruoyi-pay-starter/src/main/java/com/ruoyi/pay/controller/PayOrderController.java b/ruoyi-scene-pay/ruoyi-pay-starter/src/main/java/com/ruoyi/pay/controller/PayOrderController.java new file mode 100644 index 0000000..ea3d33b --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-starter/src/main/java/com/ruoyi/pay/controller/PayOrderController.java @@ -0,0 +1,133 @@ +package com.ruoyi.pay.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.utils.uuid.Seq; +import com.ruoyi.pay.domain.PayOrder; +import com.ruoyi.pay.service.IPayOrderService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 订单Controller + * + * @author Dftre + * @date 2024-02-15 + */ +@RestController +@RequestMapping("/pay/order") +@Tag(name = "【订单】管理") +public class PayOrderController extends BaseController +{ + @Autowired + private IPayOrderService payOrderService; + + /** + * 查询订单列表 + */ + @Operation(summary = "查询订单列表") + @PreAuthorize("@ss.hasPermi('pay:order:list')") + @GetMapping("/list") + public TableDataInfo list(PayOrder payOrder) + { + startPage(); + List list = payOrderService.selectPayOrderList(payOrder); + return getDataTable(list); + } + + /** + * 导出订单列表 + */ + @Operation(summary = "导出订单列表") + @PreAuthorize("@ss.hasPermi('pay:order:export')") + @Log(title = "订单", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, PayOrder payOrder) + { + List list = payOrderService.selectPayOrderList(payOrder); + ExcelUtil util = new ExcelUtil(PayOrder.class); + util.exportExcel(response, list, "订单数据"); + } + + /** + * 获取订单详细信息 + */ + @Operation(summary = "获取订单详细信息") + @PreAuthorize("@ss.hasPermi('pay:order:query')") + @GetMapping(value = "/{orderId}") + public AjaxResult getInfo(@PathVariable("orderId") Long orderId) + { + return success(payOrderService.selectPayOrderByOrderId(orderId)); + } + + /** + * 新增订单 + */ + @Operation(summary = "新增订单") + @PreAuthorize("@ss.hasPermi('pay:order:add')") + @Log(title = "订单", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody PayOrder payOrder) + { + payOrder.setCreateBy(getUsername()); + payOrder.setOrderNumber(Seq.getId().toString()); + AjaxResult result = toAjax(payOrderService.insertPayOrder(payOrder)); + result.put(AjaxResult.DATA_TAG, payOrder); + return result; + } + + /** + * 修改订单 + */ + @Operation(summary = "修改订单") + @PreAuthorize("@ss.hasPermi('pay:order:edit')") + @Log(title = "订单", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody PayOrder payOrder) + { + return toAjax(payOrderService.updatePayOrder(payOrder)); + } + + /** + * 删除订单 + */ + @Operation(summary = "通过订单号删除订单") + @PreAuthorize("@ss.hasPermi('pay:order:remove')") + @Log(title = "订单", businessType = BusinessType.DELETE) + @DeleteMapping("/orderNumber/{orderNumber}") + public AjaxResult removeByOrderNumber(@PathVariable( name = "orderNumber" ) String orderNumbers) + { + return toAjax(payOrderService.deletePayOrderByOrderNumber(orderNumbers)); + } + + /** + * 删除订单 + */ + @Operation(summary = "删除订单") + @PreAuthorize("@ss.hasPermi('pay:order:remove')") + @Log(title = "订单", businessType = BusinessType.DELETE) + @DeleteMapping("/{orderIds}") + public AjaxResult remove(@PathVariable( name = "orderIds" ) Long[] orderIds) + { + return toAjax(payOrderService.deletePayOrderByOrderIds(orderIds)); + } +} diff --git a/ruoyi-scene-pay/ruoyi-pay-wx/pom.xml b/ruoyi-scene-pay/ruoyi-pay-wx/pom.xml new file mode 100644 index 0000000..2a4245e --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-wx/pom.xml @@ -0,0 +1,31 @@ + + + + ruoyi-scene-pay + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-pay-wx + + + 微信支付模块 + + + + + + com.ruoyi.geekxd + ruoyi-pay-common + + + + com.github.wechatpay-apiv3 + wechatpay-java + + + + + \ No newline at end of file diff --git a/ruoyi-scene-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/config/WxPayConfig.java b/ruoyi-scene-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/config/WxPayConfig.java new file mode 100644 index 0000000..fda8210 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/config/WxPayConfig.java @@ -0,0 +1,139 @@ +package com.ruoyi.pay.wx.config; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; + +import com.wechat.pay.java.core.RSAAutoCertificateConfig; +import com.wechat.pay.java.core.notification.NotificationParser; +import com.wechat.pay.java.service.payments.nativepay.NativePayService; +import com.wechat.pay.java.service.refund.RefundService; + +/** + * 配置我们自己的信息 + * + * @author ZlH + */ +@Configuration +@ConditionalOnProperty(prefix = "pay.wechat", name = "enabled", havingValue = "true") +public class WxPayConfig { + + /** 商户号 */ + @Value("${pay.wechat.merchantId}") + private String merchantId; + + /** 商户证书序列号 */ + @Value("${pay.wechat.merchantSerialNumber}") + private String merchantSerialNumber; + + /** 商户APIV3密钥 */ + @Value("${pay.wechat.apiV3Key}") + private String apiV3Key; + + /** 商户API私钥路径 */ + @Value("${pay.wechat.privateKeyPath}") + private String privateKeyPath; + + @Value("${pay.wechat.appId}") + private String appId; + + @Value("${pay.wechat.notifyUrl}") + private String notifyUrl; + + public String getMerchantId() { + return merchantId; + } + + public void setMerchantId(String merchantId) { + this.merchantId = merchantId; + } + + public String getMerchantSerialNumber() { + return merchantSerialNumber; + } + + public void setMerchantSerialNumber(String merchantSerialNumber) { + this.merchantSerialNumber = merchantSerialNumber; + } + + public String getApiV3Key() { + return apiV3Key; + } + + public void setApiV3Key(String apiV3Key) { + this.apiV3Key = apiV3Key; + } + + public void setPrivateKeyPath(String privateKeyPath) { + this.privateKeyPath = privateKeyPath; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getNotifyUrl() { + return notifyUrl; + } + + public void setNotifyUrl(String notifyUrl) { + this.notifyUrl = notifyUrl; + } + + @Bean + public RSAAutoCertificateConfig wxpayBaseConfig() throws Exception { + return new RSAAutoCertificateConfig.Builder() + .merchantId(getMerchantId()) + .privateKeyFromPath(getPrivateKeyPath()) + .merchantSerialNumber(getMerchantSerialNumber()) + .apiV3Key(getApiV3Key()) + .build(); + } + + @Bean + public NativePayService nativePayService() throws Exception { + return new NativePayService.Builder().config(wxpayBaseConfig()).build(); + } + + @Bean + public RefundService refundService() throws Exception { + return new RefundService.Builder().config(wxpayBaseConfig()).build(); + } + + @Bean + public NotificationParser notificationParser() throws Exception { + return new NotificationParser(wxpayBaseConfig()); + } + + @Autowired + private ApplicationContext applicationContext; + + public String getPrivateKeyPath() throws Exception { + if (privateKeyPath.startsWith("classpath:")) { + Resource resource = applicationContext.getResource(privateKeyPath); + String tempFilePath = System.getProperty("java.io.tmpdir") + "/temp_wxcert.pem"; + try (InputStream inputStream = resource.getInputStream()) { + Files.copy(inputStream, Paths.get(tempFilePath), StandardCopyOption.REPLACE_EXISTING); + privateKeyPath = tempFilePath; + } catch (Exception e) { + Files.deleteIfExists(Paths.get(tempFilePath)); + throw new RuntimeException("微信支付证书文件读取失败", e); + } + } + return privateKeyPath; + } + +} diff --git a/ruoyi-scene-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/service/IWxPayService.java b/ruoyi-scene-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/service/IWxPayService.java new file mode 100644 index 0000000..46f6538 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/service/IWxPayService.java @@ -0,0 +1,6 @@ +package com.ruoyi.pay.wx.service; + +import com.ruoyi.pay.service.PayService; + +public interface IWxPayService extends PayService { +} diff --git a/ruoyi-scene-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/service/Impl/WxPayService.java b/ruoyi-scene-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/service/Impl/WxPayService.java new file mode 100644 index 0000000..556ec6c --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/service/Impl/WxPayService.java @@ -0,0 +1,194 @@ +package com.ruoyi.pay.wx.service.Impl; + +import java.nio.charset.StandardCharsets; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.springframework.util.StreamUtils; + +import com.ruoyi.pay.domain.PayOrder; +import com.ruoyi.pay.service.IPayOrderService; +import com.ruoyi.pay.wx.config.WxPayConfig; +import com.ruoyi.pay.wx.service.IWxPayService; +import com.wechat.pay.java.core.exception.ServiceException; +import com.wechat.pay.java.core.notification.NotificationParser; +import com.wechat.pay.java.core.notification.RequestParam; +import com.wechat.pay.java.service.payments.model.Transaction; +import com.wechat.pay.java.service.payments.nativepay.NativePayService; +import com.wechat.pay.java.service.payments.nativepay.model.Amount; +import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest; +import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse; +import com.wechat.pay.java.service.payments.nativepay.model.QueryOrderByIdRequest; +import com.wechat.pay.java.service.refund.RefundService; +import com.wechat.pay.java.service.refund.model.AmountReq; +import com.wechat.pay.java.service.refund.model.CreateRequest; +import com.wechat.pay.java.service.refund.model.Refund; +import com.wechat.pay.java.service.refund.model.RefundNotification; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Service("pay:service:wechat") +@ConditionalOnProperty(prefix = "pay.wechat", name = "enabled", havingValue = "true") +public class WxPayService implements IWxPayService { + + @Autowired + private IPayOrderService payOrderService; + + @Autowired + private NativePayService nativePayService; + + @Autowired + private WxPayConfig wxPayAppConfig; + + @Autowired + private NotificationParser notificationParser; + + @Autowired + private RefundService refundService; + + @Override + public String payUrl(PayOrder payOrder) { + PrepayRequest request = new PrepayRequest(); + Amount amount = new Amount(); + amount.setTotal(Integer.parseInt(payOrder.getTotalAmount())); + request.setAmount(amount); + request.setAppid(wxPayAppConfig.getAppId()); + request.setMchid(wxPayAppConfig.getMerchantId()); + request.setDescription(payOrder.getOrderContent()); + StringBuilder notifyUrlBuilder = new StringBuilder(); + String orderNotifyUrl = notifyUrlBuilder.append(wxPayAppConfig.getNotifyUrl()) + .append("/").append(payOrder.getOrderNumber()) + .append("/pay").toString(); + request.setNotifyUrl(orderNotifyUrl); + request.setOutTradeNo(payOrder.getOrderNumber()); + PrepayResponse response = nativePayService.prepay(request); + return response.getCodeUrl(); + } + + @Override + public String notify(HttpServletRequest request, HttpServletResponse response, PayOrder payOrder, String type) { + String timeStamp = request.getHeader("Wechatpay-Timestamp"); + String nonce = request.getHeader("Wechatpay-Nonce"); + String signature = request.getHeader("Wechatpay-Signature"); + String certSn = request.getHeader("Wechatpay-Serial"); + if (type.equals("pay")) { + try { + String requestBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); + RequestParam requestParam = new RequestParam.Builder() + .serialNumber(certSn) + .nonce(nonce) + .signature(signature) + .timestamp(timeStamp) + .body(requestBody) + .build(); + Transaction transaction = notificationParser.parse(requestParam, Transaction.class); + // String orderNumber = transaction.getOutTradeNo(); + payOrder.setOrderStatus(switch (transaction.getTradeState()) { + case SUCCESS -> "已支付"; + case REFUND -> "已退款"; + case NOTPAY -> "未支付"; + case CLOSED -> "已关闭"; + case REVOKED -> "已撤销"; + case USERPAYING -> "用户支付中"; + case PAYERROR -> "支付错误"; + case ACCEPT -> "已接收待支付"; + default -> "未知状态" + transaction.getTradeState(); + }); + payOrder.setThirdNumber(transaction.getTransactionId()); + payOrder.setActualAmount(transaction.getAmount().getTotal().toString()); + payOrderService.updatePayOrder(payOrder); + return "success"; + } catch (Exception e) { + e.printStackTrace(); + return "fail"; + } + } else if (type.equals("refund")) { + try { + String requestBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); + RequestParam requestParam = new RequestParam.Builder() + .serialNumber(certSn) + .nonce(nonce) + .signature(signature) + .timestamp(timeStamp) + .body(requestBody) + .build(); + RefundNotification transaction = notificationParser.parse(requestParam, RefundNotification.class); + // String orderNumber = transaction.getOutTradeNo(); + payOrder.setOrderStatus(switch (transaction.getRefundStatus()) { + case SUCCESS -> "已退款"; + case PROCESSING -> "退款处理中"; + case CLOSED -> "退款关闭"; + case ABNORMAL -> "退款异常"; + default -> "未知状态" + transaction.getRefundStatus(); + }); + payOrderService.updatePayOrder(payOrder); + return "success"; + } catch (Exception e) { + e.printStackTrace(); + return "fail"; + } + } else { + return "fail"; + } + + } + + @Override + public PayOrder query(PayOrder payOrder) { + QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest(); + queryRequest.setMchid(wxPayAppConfig.getMerchantId()); + queryRequest.setTransactionId(payOrder.getThirdNumber()); + try { + Transaction result = nativePayService.queryOrderById(queryRequest); + payOrder.setActualAmount(result.getAmount().getTotal().toString()); + payOrder.setOrderStatus(switch (result.getTradeState()) { + case SUCCESS -> "已支付"; + case REFUND -> "已退款"; + case NOTPAY -> "未支付"; + case CLOSED -> "已关闭"; + case REVOKED -> "已撤销"; + case USERPAYING -> "用户支付中"; + case PAYERROR -> "支付错误"; + case ACCEPT -> "已接收待支付"; + default -> "未知状态" + result.getTradeState(); + }); + payOrderService.updatePayOrder(payOrder); + } catch (ServiceException e) { + System.out.printf("code=[%s], message=[%s]\n", e.getErrorCode(), e.getErrorMessage()); + System.out.printf("reponse body=[%s]\n", e.getResponseBody()); + } + return payOrder; + } + + @Override + public PayOrder refund(PayOrder payOrder) { + CreateRequest request = new CreateRequest(); + request.setTransactionId(payOrder.getThirdNumber()); + request.setOutRefundNo(payOrder.getOrderNumber()); + request.setOutTradeNo(payOrder.getOrderNumber()); + AmountReq amount = new AmountReq(); + amount.setCurrency("CNY"); + amount.setTotal(Long.valueOf(payOrder.getTotalAmount())); + amount.setRefund(Long.valueOf(payOrder.getTotalAmount())); + request.setAmount(amount); + StringBuilder notifyUrlBuilder = new StringBuilder(); + String orderNotifyUrl = notifyUrlBuilder.append(wxPayAppConfig.getNotifyUrl()) + .append("/").append(payOrder.getOrderNumber()) + .append("/refund").toString(); + request.setNotifyUrl(orderNotifyUrl); + Refund refund = refundService.create(request); + String status = switch (refund.getStatus()) { + case SUCCESS -> "已退款"; + case PROCESSING -> "退款处理中"; + case CLOSED -> "退款关闭"; + case ABNORMAL -> "退款异常"; + default -> "未知状态" + refund.getStatus(); + }; + payOrder.setOrderStatus(status); + payOrderService.updateStatus(payOrder.getOrderNumber(), status); + return payOrder; + } + +} diff --git a/ruoyi-scene-pay/ruoyi-pay-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/ruoyi-scene-pay/ruoyi-pay-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..edafe82 --- /dev/null +++ b/ruoyi-scene-pay/ruoyi-pay-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,39 @@ +{ + "properties": [ + { + "name": "pay.wechat.enabled", + "type": "java.lang.Boolean", + "description": "是否启用微信支付" + }, + { + "name": "pay.wechat.appId", + "type": "java.lang.String", + "description": "微信支付appid" + }, + { + "name": "pay.wechat.apiV3Key", + "type": "java.lang.String", + "description": "微信支付apiV3Key" + }, + { + "name": "pay.wechat.privateKeyPath", + "type": "java.lang.String", + "description": "微信支付私钥,可以直接用字符串,也可以是基于classpath的文件路径" + }, + { + "name": "pay.wechat.merchantId", + "type": "java.lang.String", + "description": "微信支付merchantId" + }, + { + "name": "pay.wechat.merchantSerialNumber", + "type": "java.lang.String", + "description": "微信支付merchantSerialNumber" + }, + { + "name": "pay.wechat.notifyUrl", + "type": "java.lang.String", + "description": "微信支付回调地址" + } + ] +} \ No newline at end of file diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml new file mode 100644 index 0000000..a689cfb --- /dev/null +++ b/ruoyi-system/pom.xml @@ -0,0 +1,36 @@ + + + + ruoyi + com.ruoyi.geekxd + 3.9.0-G + + 4.0.0 + + ruoyi-system + + + system系统模块 + + + + + + + com.ruoyi.geekxd + ruoyi-common + + + com.ruoyi.geekxd + ruoyi-common + + + com.ruoyi.geekxd + ruoyi-common + + + + + diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUnitConvertController.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUnitConvertController.java new file mode 100644 index 0000000..c66cd12 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUnitConvertController.java @@ -0,0 +1,113 @@ +package com.ruoyi.system.controller; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.system.domain.SysUnitConvert; +import com.ruoyi.system.service.ISysUnitConvertService; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; + +/** + * 单位换算Controller + * + * @author ruoyi + * @date 2025-10-08 + */ +@RestController +@RequestMapping("/system/convert") +@Tag(name = "【单位换算】管理") +public class SysUnitConvertController extends BaseController +{ + @Autowired + private ISysUnitConvertService sysUnitConvertService; + + /** + * 查询单位换算列表 + */ + @Operation(summary = "查询单位换算列表") + @PreAuthorize("@ss.hasPermi('system:convert:list')") + @GetMapping("/list") + public TableDataInfo list(SysUnitConvert sysUnitConvert) + { + startPage(); + List list = sysUnitConvertService.selectSysUnitConvertList(sysUnitConvert); + return getDataTable(list); + } + + /** + * 导出单位换算列表 + */ + @Operation(summary = "导出单位换算列表") + @PreAuthorize("@ss.hasPermi('system:convert:export')") + @Log(title = "单位换算", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysUnitConvert sysUnitConvert) + { + List list = sysUnitConvertService.selectSysUnitConvertList(sysUnitConvert); + ExcelUtil util = new ExcelUtil(SysUnitConvert.class); + util.exportExcel(response, list, "单位换算数据"); + } + + /** + * 获取单位换算详细信息 + */ + @Operation(summary = "获取单位换算详细信息") + @PreAuthorize("@ss.hasPermi('system:convert:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) + { + return success(sysUnitConvertService.selectSysUnitConvertById(id)); + } + + /** + * 新增单位换算 + */ + @Operation(summary = "新增单位换算") + @PreAuthorize("@ss.hasPermi('system:convert:add')") + @Log(title = "单位换算", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysUnitConvert sysUnitConvert) + { + return toAjax(sysUnitConvertService.insertSysUnitConvert(sysUnitConvert)); + } + + /** + * 修改单位换算 + */ + @Operation(summary = "修改单位换算") + @PreAuthorize("@ss.hasPermi('system:convert:edit')") + @Log(title = "单位换算", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysUnitConvert sysUnitConvert) + { + return toAjax(sysUnitConvertService.updateSysUnitConvert(sysUnitConvert)); + } + + /** + * 删除单位换算 + */ + @Operation(summary = "删除单位换算") + @PreAuthorize("@ss.hasPermi('system:convert:remove')") + @Log(title = "单位换算", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable( name = "ids" ) Long[] ids) + { + return toAjax(sysUnitConvertService.deleteSysUnitConvertByIds(ids)); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/UnitConvert.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/UnitConvert.java new file mode 100644 index 0000000..06edf79 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/UnitConvert.java @@ -0,0 +1,90 @@ +package com.ruoyi.system.controller; + + +import com.ruoyi.system.domain.SysUnitConvert; +import com.ruoyi.system.service.ISysUnitConvertService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +@Component +public class UnitConvert { + + @Autowired + private ISysUnitConvertService sysUnitConvertService; + + public double ConvertUniter(String unitType, double oldValue, int oldUnit, int newUnit) { + // 查询旧单位信息 + if ("temperature".equalsIgnoreCase(unitType)) { + return handleTemperatureConversion(BigDecimal.valueOf(oldValue), (long) oldUnit, (long) newUnit).doubleValue(); + } else { + + SysUnitConvert oldUnitInfo =sysUnitConvertService.selectSysUnitConvertUnitByTypeOrder(unitType, (long) oldUnit,null); + if (oldUnitInfo == null) { + throw new IllegalArgumentException("旧单位 '" + oldUnit + "' 不存在或不可用"); + } + + + SysUnitConvert baseUnitInfo = sysUnitConvertService.selectSysUnitConvertUnitByTypeOrder (unitType,null,"Y"); + if (baseUnitInfo == null) { + throw new IllegalArgumentException("基准单位 不存在或不可用"); + } + + // 查询新单位信息 + SysUnitConvert newUnitInfo = sysUnitConvertService.selectSysUnitConvertUnitByTypeOrder(unitType, (long) newUnit,null); + if (newUnitInfo == null) { + throw new IllegalArgumentException("新单位 '" + newUnit + "' 不存在或不可用"); + } + + BigDecimal oldFactor = oldUnitInfo.getConversionFactor(); + BigDecimal newFactor = newUnitInfo.getConversionFactor(); + + // 检查旧单位转换因子是否为零 + if (oldFactor.compareTo(BigDecimal.ZERO) == 0) { + throw new ArithmeticException("旧单位 '" + oldUnit + "' 的转换因子为零,无法进行转换"); + } + + // 计算基准值:oldValue / oldFactor + int scale = 20; // 设置足够大的精度以避免精度丢失 + BigDecimal baseValue = BigDecimal.valueOf(oldValue).divide(oldFactor, scale, RoundingMode.HALF_UP); + + // 计算新值:baseValue * newFactor + BigDecimal newValue = baseValue.multiply(newFactor); + + // 四舍五入到合理的小数位数(例如10位) + newValue = newValue.setScale(10, RoundingMode.HALF_UP); + + return newValue.doubleValue(); + } + } + + // 温度转换方法 + public static BigDecimal handleTemperatureConversion(BigDecimal oldValue, Long oldUnit, Long newUnit) { + final BigDecimal THIRTY_TWO = BigDecimal.valueOf(32); + final BigDecimal FIVE = BigDecimal.valueOf(5); + final BigDecimal NINE = BigDecimal.valueOf(9); + final BigDecimal TWO_HUNDRED_SEVENTY_THREE_POINT_ONE_FIVE = BigDecimal.valueOf(273.15); + // 使用原始值计算 + BigDecimal celsius; + if (oldUnit == 0) { + celsius = oldValue; + } else if (oldUnit == 1) { + celsius = oldValue.subtract(THIRTY_TWO).multiply(FIVE).divide(NINE, 10, RoundingMode.HALF_UP); + } else if (oldUnit == 2) { + celsius = oldValue.subtract(TWO_HUNDRED_SEVENTY_THREE_POINT_ONE_FIVE); + } else { + throw new IllegalArgumentException("无效温度单位"); + } + + if (newUnit == 0) { + return celsius; + } else if (newUnit == 1) { + return celsius.multiply(NINE).divide(FIVE, 10, RoundingMode.HALF_UP).add(THIRTY_TWO); + } else if (newUnit == 2) { + return celsius.add(TWO_HUNDRED_SEVENTY_THREE_POINT_ONE_FIVE); + } + throw new IllegalArgumentException("无效温度单位"); + } +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java new file mode 100644 index 0000000..ed4ccbc --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java @@ -0,0 +1,88 @@ +package com.ruoyi.system.domain; + +import com.ruoyi.common.utils.StringUtils; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 缓存信息 + * + * @author ruoyi + */ +@Schema(title = "缓存信息") +public class SysCache +{ + /** 缓存名称 */ + @Schema(title = "缓存名称") + private String cacheName = ""; + + /** 缓存键名 */ + @Schema(title = "缓存键名") + private String cacheKey = ""; + + /** 缓存内容 */ + @Schema(title = "缓存内容") + private String cacheValue = ""; + + /** 备注 */ + @Schema(title = "备注") + private String remark = ""; + + public SysCache() + { + + } + + public SysCache(String cacheName, String remark) + { + this.cacheName = cacheName; + this.remark = remark; + } + + public SysCache(String cacheName, String cacheKey, String cacheValue) + { + this.cacheName = StringUtils.replace(cacheName, ":", ""); + this.cacheKey = StringUtils.replace(cacheKey, cacheName, ""); + this.cacheValue = cacheValue; + } + + public String getCacheName() + { + return cacheName; + } + + public void setCacheName(String cacheName) + { + this.cacheName = cacheName; + } + + public String getCacheKey() + { + return cacheKey; + } + + public void setCacheKey(String cacheKey) + { + this.cacheKey = cacheKey; + } + + public String getCacheValue() + { + return cacheValue; + } + + public void setCacheValue(String cacheValue) + { + this.cacheValue = cacheValue; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java new file mode 100644 index 0000000..766628f --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java @@ -0,0 +1,120 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 参数配置表 sys_config + * + * @author ruoyi + */ +@Schema(title = "参数配置表") +public class SysConfig extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 参数主键 */ + @Schema(title = "参数主键") + @Excel(name = "参数主键", cellType = ColumnType.NUMERIC) + private Long configId; + + /** 参数名称 */ + @Schema(title = "参数名称") + @Excel(name = "参数名称") + private String configName; + + /** 参数键名 */ + @Schema(title = "参数键名") + @Excel(name = "参数键名") + private String configKey; + + /** 参数键值 */ + @Schema(title = "参数键值") + @Excel(name = "参数键值") + private String configValue; + + /** 系统内置(Y是 N否) */ + @Schema(title = "系统内置") + @Excel(name = "系统内置", readConverterExp = "Y=是,N=否") + private String configType; + + public Long getConfigId() + { + return configId; + } + + public void setConfigId(Long configId) + { + this.configId = configId; + } + + @NotBlank(message = "参数名称不能为空") + @Size(min = 0, max = 100, message = "参数名称不能超过100个字符") + public String getConfigName() + { + return configName; + } + + public void setConfigName(String configName) + { + this.configName = configName; + } + + @NotBlank(message = "参数键名长度不能为空") + @Size(min = 0, max = 100, message = "参数键名长度不能超过100个字符") + public String getConfigKey() + { + return configKey; + } + + public void setConfigKey(String configKey) + { + this.configKey = configKey; + } + + @NotBlank(message = "参数键值不能为空") + @Size(min = 0, max = 500, message = "参数键值长度不能超过500个字符") + public String getConfigValue() + { + return configValue; + } + + public void setConfigValue(String configValue) + { + this.configValue = configValue; + } + + public String getConfigType() + { + return configType; + } + + public void setConfigType(String configType) + { + this.configType = configType; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("configId", getConfigId()) + .append("configName", getConfigName()) + .append("configKey", getConfigKey()) + .append("configValue", getConfigValue()) + .append("configType", getConfigType()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java new file mode 100644 index 0000000..219dae4 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java @@ -0,0 +1,157 @@ +package com.ruoyi.system.domain; + +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 系统访问记录表 sys_logininfor + * + * @author ruoyi + */ +@Schema(title = "系统访问记录表") +public class SysLogininfor extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @Schema(title = "序号") + @Excel(name = "序号", cellType = ColumnType.NUMERIC) + private Long infoId; + + /** 用户账号 */ + @Schema(title = "用户账号") + @Excel(name = "用户账号") + private String userName; + + /** 登录状态 0成功 1失败 */ + @Schema(title = "登录状态") + @Excel(name = "登录状态", readConverterExp = "0=成功,1=失败") + private String status; + + /** 登录IP地址 */ + @Schema(title = "登录地址") + @Excel(name = "登录地址") + private String ipaddr; + + /** 登录地点 */ + @Schema(title = "登录地点") + @Excel(name = "登录地点") + private String loginLocation; + + /** 浏览器类型 */ + @Schema(title = "浏览器") + @Excel(name = "浏览器") + private String browser; + + /** 操作系统 */ + @Schema(title = "操作系统") + @Excel(name = "操作系统") + private String os; + + /** 提示消息 */ + @Schema(title = "提示消息") + @Excel(name = "提示消息") + private String msg; + + /** 访问时间 */ + @Schema(title = "访问时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "访问时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date loginTime; + + public Long getInfoId() + { + return infoId; + } + + public void setInfoId(Long infoId) + { + this.infoId = infoId; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } + + public Date getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Date loginTime) + { + this.loginTime = loginTime; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java new file mode 100644 index 0000000..432372c --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java @@ -0,0 +1,111 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.xss.Xss; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 通知公告表 sys_notice + * + * @author ruoyi + */ +@Schema(title = "系统访问记录表") +public class SysNotice extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 公告ID */ + @Schema(title = "公告ID") + private Long noticeId; + + /** 公告标题 */ + @Schema(title = "公告标题") + private String noticeTitle; + + /** 公告类型(1通知 2公告) */ + @Schema(title = "公告类型") + private String noticeType; + + /** 公告内容 */ + @Schema(title = "公告内容") + private String noticeContent; + + /** 公告状态(0正常 1关闭) */ + @Schema(title = "公告状态") + private String status; + + public Long getNoticeId() + { + return noticeId; + } + + public void setNoticeId(Long noticeId) + { + this.noticeId = noticeId; + } + + public void setNoticeTitle(String noticeTitle) + { + this.noticeTitle = noticeTitle; + } + + @Xss(message = "公告标题不能包含脚本字符") + @NotBlank(message = "公告标题不能为空") + @Size(min = 0, max = 50, message = "公告标题不能超过50个字符") + public String getNoticeTitle() + { + return noticeTitle; + } + + public void setNoticeType(String noticeType) + { + this.noticeType = noticeType; + } + + public String getNoticeType() + { + return noticeType; + } + + public void setNoticeContent(String noticeContent) + { + this.noticeContent = noticeContent; + } + + public String getNoticeContent() + { + return noticeContent; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getStatus() + { + return status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("noticeId", getNoticeId()) + .append("noticeTitle", getNoticeTitle()) + .append("noticeType", getNoticeType()) + .append("noticeContent", getNoticeContent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java new file mode 100644 index 0000000..c3c80d1 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java @@ -0,0 +1,291 @@ +package com.ruoyi.system.domain; + +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 操作日志记录表 oper_log + * + * @author ruoyi + */ +@Schema(title = "操作日志记录表") +public class SysOperLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 日志主键 */ + @Schema(title = "操作序号") + @Excel(name = "操作序号", cellType = ColumnType.NUMERIC) + private Long operId; + + /** 操作模块 */ + @Schema(title = "操作模块") + @Excel(name = "操作模块") + private String title; + + /** 业务类型(0其它 1新增 2修改 3删除) */ + @Schema(title = "业务类型") + @Excel(name = "业务类型", readConverterExp = "0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据") + private Integer businessType; + + /** 业务类型数组 */ + @Schema(title = "业务类型数组") + private Integer[] businessTypes; + + /** 请求方法 */ + @Schema(title = "请求方法") + @Excel(name = "请求方法") + private String method; + + /** 请求方式 */ + @Schema(title = "请求方式") + @Excel(name = "请求方式") + private String requestMethod; + + /** 操作类别(0其它 1后台用户 2手机端用户) */ + @Schema(title = "操作类别") + @Excel(name = "操作类别", readConverterExp = "0=其它,1=后台用户,2=手机端用户") + private Integer operatorType; + + /** 操作人员 */ + @Schema(title = "操作人员") + @Excel(name = "操作人员") + private String operName; + + /** 部门名称 */ + @Schema(title = "部门名称") + @Excel(name = "部门名称") + private String deptName; + + /** 请求url */ + @Schema(title = "请求地址") + @Excel(name = "请求地址") + private String operUrl; + + /** 操作地址 */ + @Schema(title = "操作地址") + @Excel(name = "操作地址") + private String operIp; + + /** 操作地点 */ + @Schema(title = "操作地点") + @Excel(name = "操作地点") + private String operLocation; + + /** 请求参数 */ + @Schema(title = "请求参数") + @Excel(name = "请求参数") + private String operParam; + + /** 返回参数 */ + @Schema(title = "返回参数") + @Excel(name = "返回参数") + private String jsonResult; + + /** 操作状态(0正常 1异常) */ + @Schema(title = "操作状态") + @Excel(name = "操作状态", readConverterExp = "0=正常,1=异常") + private Integer status; + + /** 错误消息 */ + @Schema(title = "错误消息") + @Excel(name = "错误消息") + private String errorMsg; + + /** 操作时间 */ + @Schema(title = "操作时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date operTime; + + /** 消耗时间 */ + @Schema(title = "消耗时间") + @Excel(name = "消耗时间", suffix = "毫秒") + private Long costTime; + + public Long getOperId() + { + return operId; + } + + public void setOperId(Long operId) + { + this.operId = operId; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public Integer getBusinessType() + { + return businessType; + } + + public void setBusinessType(Integer businessType) + { + this.businessType = businessType; + } + + public Integer[] getBusinessTypes() + { + return businessTypes; + } + + public void setBusinessTypes(Integer[] businessTypes) + { + this.businessTypes = businessTypes; + } + + public String getMethod() + { + return method; + } + + public void setMethod(String method) + { + this.method = method; + } + + public String getRequestMethod() + { + return requestMethod; + } + + public void setRequestMethod(String requestMethod) + { + this.requestMethod = requestMethod; + } + + public Integer getOperatorType() + { + return operatorType; + } + + public void setOperatorType(Integer operatorType) + { + this.operatorType = operatorType; + } + + public String getOperName() + { + return operName; + } + + public void setOperName(String operName) + { + this.operName = operName; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getOperUrl() + { + return operUrl; + } + + public void setOperUrl(String operUrl) + { + this.operUrl = operUrl; + } + + public String getOperIp() + { + return operIp; + } + + public void setOperIp(String operIp) + { + this.operIp = operIp; + } + + public String getOperLocation() + { + return operLocation; + } + + public void setOperLocation(String operLocation) + { + this.operLocation = operLocation; + } + + public String getOperParam() + { + return operParam; + } + + public void setOperParam(String operParam) + { + this.operParam = operParam; + } + + public String getJsonResult() + { + return jsonResult; + } + + public void setJsonResult(String jsonResult) + { + this.jsonResult = jsonResult; + } + + public Integer getStatus() + { + return status; + } + + public void setStatus(Integer status) + { + this.status = status; + } + + public String getErrorMsg() + { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) + { + this.errorMsg = errorMsg; + } + + public Date getOperTime() + { + return operTime; + } + + public void setOperTime(Date operTime) + { + this.operTime = operTime; + } + + public Long getCostTime() + { + return costTime; + } + + public void setCostTime(Long costTime) + { + this.costTime = costTime; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java new file mode 100644 index 0000000..2930993 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java @@ -0,0 +1,134 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * 岗位表 sys_post + * + * @author ruoyi + */ +@Schema(title = "岗位表") +public class SysPost extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 岗位序号 */ + @Schema(title = "岗位序号") + @Excel(name = "岗位序号", cellType = ColumnType.NUMERIC) + private Long postId; + + /** 岗位编码 */ + @Schema(title = "岗位编码") + @Excel(name = "岗位编码") + private String postCode; + + /** 岗位名称 */ + @Schema(title = "岗位名称") + @Excel(name = "岗位名称") + private String postName; + + /** 岗位排序 */ + @Schema(title = "岗位排序") + @Excel(name = "岗位排序") + private Integer postSort; + + /** 状态(0正常 1停用) */ + @Schema(title = "状态") + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 用户是否存在此岗位标识 默认不存在 */ + @Schema(title = "用户是否存在此岗位标识") + private boolean flag = false; + + public Long getPostId() + { + return postId; + } + + public void setPostId(Long postId) + { + this.postId = postId; + } + + @NotBlank(message = "岗位编码不能为空") + @Size(min = 0, max = 64, message = "岗位编码长度不能超过64个字符") + public String getPostCode() + { + return postCode; + } + + public void setPostCode(String postCode) + { + this.postCode = postCode; + } + + @NotBlank(message = "岗位名称不能为空") + @Size(min = 0, max = 50, message = "岗位名称长度不能超过50个字符") + public String getPostName() + { + return postName; + } + + public void setPostName(String postName) + { + this.postName = postName; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getPostSort() + { + return postSort; + } + + public void setPostSort(Integer postSort) + { + this.postSort = postSort; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public boolean isFlag() + { + return flag; + } + + public void setFlag(boolean flag) + { + this.flag = flag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("postId", getPostId()) + .append("postCode", getPostCode()) + .append("postName", getPostName()) + .append("postSort", getPostSort()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java new file mode 100644 index 0000000..9d2873b --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java @@ -0,0 +1,51 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 角色和部门关联 sys_role_dept + * + * @author ruoyi + */ +@Schema(title = "角色和部门关联") +public class SysRoleDept +{ + /** 角色ID */ + @Schema(title = "角色ID") + private Long roleId; + + /** 部门ID */ + @Schema(title = "部门ID") + private Long deptId; + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("deptId", getDeptId()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java new file mode 100644 index 0000000..307de3f --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java @@ -0,0 +1,51 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 角色和菜单关联 sys_role_menu + * + * @author ruoyi + */ +@Schema(title = "角色和菜单关联") +public class SysRoleMenu +{ + /** 角色ID */ + @Schema(title = "角色ID") + private Long roleId; + + /** 菜单ID */ + @Schema(title = "菜单ID") + private Long menuId; + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public Long getMenuId() + { + return menuId; + } + + public void setMenuId(Long menuId) + { + this.menuId = menuId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("menuId", getMenuId()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUnitConvert.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUnitConvert.java new file mode 100644 index 0000000..5bf3de7 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUnitConvert.java @@ -0,0 +1,162 @@ +package com.ruoyi.system.domain; + +import java.math.BigDecimal; +import io.swagger.v3.oas.annotations.media.Schema; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 单位换算对象 sys_unit_convert + * + * @author ruoyi + * @date 2025-10-08 + */ +@Schema(description = "单位换算对象") +public class SysUnitConvert extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + + /** $column.columnComment */ + @Schema(title = "$column.columnComment") + private Long id; + + /** 单位类型 */ + @Schema(title = "单位类型") + @Excel(name = "单位类型") + private String unitType; + + /** 单位名称 */ + @Schema(title = "单位名称") + @Excel(name = "单位名称") + private String unitName; + + /** 是否基准 */ + @Schema(title = "是否基准") + @Excel(name = "是否基准") + private String baseUnit; + + /** 换算因子 */ + @Schema(title = "换算因子") + @Excel(name = "换算因子") + private BigDecimal conversionFactor; + + /** 类型名称 */ + @Schema(title = "类型名称") + @Excel(name = "类型名称") + private String unitTypeName; + + /** 状态 */ + @Schema(title = "状态") + @Excel(name = "状态") + private String status; + + /** 单位序号 */ + @Schema(title = "单位序号") + @Excel(name = "单位序号") + private Long unitOrder; + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + + + public void setUnitType(String unitType) + { + this.unitType = unitType; + } + + public String getUnitType() + { + return unitType; + } + + + public void setUnitName(String unitName) + { + this.unitName = unitName; + } + + public String getUnitName() + { + return unitName; + } + + + public void setBaseUnit(String baseUnit) + { + this.baseUnit = baseUnit; + } + + public String getBaseUnit() + { + return baseUnit; + } + + + public void setConversionFactor(BigDecimal conversionFactor) + { + this.conversionFactor = conversionFactor; + } + + public BigDecimal getConversionFactor() + { + return conversionFactor; + } + + + public void setUnitTypeName(String unitTypeName) + { + this.unitTypeName = unitTypeName; + } + + public String getUnitTypeName() + { + return unitTypeName; + } + + + public void setStatus(String status) + { + this.status = status; + } + + public String getStatus() + { + return status; + } + + + public void setUnitOrder(Long unitOrder) + { + this.unitOrder = unitOrder; + } + + public Long getUnitOrder() + { + return unitOrder; + } + + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("unitType", getUnitType()) + .append("unitName", getUnitName()) + .append("baseUnit", getBaseUnit()) + .append("conversionFactor", getConversionFactor()) + .append("unitTypeName", getUnitTypeName()) + .append("status", getStatus()) + .append("unitOrder", getUnitOrder()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java new file mode 100644 index 0000000..57112db --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java @@ -0,0 +1,124 @@ +package com.ruoyi.system.domain; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 当前在线会话 + * + * @author ruoyi + */ +@Schema(title = "当前在线会话") +public class SysUserOnline +{ + /** 会话编号 */ + @Schema(title = "会话编号") + private String tokenId; + + /** 部门名称 */ + @Schema(title = "部门名称") + private String deptName; + + /** 用户名称 */ + @Schema(title = "用户名称") + private String userName; + + /** 登录IP地址 */ + @Schema(title = "登录IP地址") + private String ipaddr; + + /** 登录地址 */ + @Schema(title = "登录地址") + private String loginLocation; + + /** 浏览器类型 */ + @Schema(title = "浏览器类型") + private String browser; + + /** 操作系统 */ + @Schema(title = "操作系统") + private String os; + + /** 登录时间 */ + @Schema(title = "登录时间") + private Long loginTime; + + public String getTokenId() + { + return tokenId; + } + + public void setTokenId(String tokenId) + { + this.tokenId = tokenId; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java new file mode 100644 index 0000000..1826e79 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java @@ -0,0 +1,51 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户和岗位关联 sys_user_post + * + * @author ruoyi + */ +@Schema(title = "用户和岗位关联") +public class SysUserPost +{ + /** 用户ID */ + @Schema(title = "用户ID") + private Long userId; + + /** 岗位ID */ + @Schema(title = "岗位ID") + private Long postId; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getPostId() + { + return postId; + } + + public void setPostId(Long postId) + { + this.postId = postId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("postId", getPostId()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java new file mode 100644 index 0000000..309e920 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java @@ -0,0 +1,51 @@ +package com.ruoyi.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户和角色关联 sys_user_role + * + * @author ruoyi + */ +@Schema(title = "用户和角色关联") +public class SysUserRole +{ + /** 用户ID */ + @Schema(title = "用户ID") + private Long userId; + + /** 角色ID */ + @Schema(title = "角色ID") + private Long roleId; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("roleId", getRoleId()) + .toString(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java new file mode 100644 index 0000000..a5d5fdc --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java @@ -0,0 +1,106 @@ +package com.ruoyi.system.domain.vo; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 路由显示信息 + * + * @author ruoyi + */ +public class MetaVo +{ + /** + * 设置该路由在侧边栏和面包屑中展示的名字 + */ + private String title; + + /** + * 设置该路由的图标,对应路径src/assets/icons/svg + */ + private String icon; + + /** + * 设置为true,则不会被 缓存 + */ + private boolean noCache; + + /** + * 内链地址(http(s)://开头) + */ + private String link; + + public MetaVo() + { + } + + public MetaVo(String title, String icon) + { + this.title = title; + this.icon = icon; + } + + public MetaVo(String title, String icon, boolean noCache) + { + this.title = title; + this.icon = icon; + this.noCache = noCache; + } + + public MetaVo(String title, String icon, String link) + { + this.title = title; + this.icon = icon; + this.link = link; + } + + public MetaVo(String title, String icon, boolean noCache, String link) + { + this.title = title; + this.icon = icon; + this.noCache = noCache; + if (StringUtils.ishttp(link)) + { + this.link = link; + } + } + + public boolean isNoCache() + { + return noCache; + } + + public void setNoCache(boolean noCache) + { + this.noCache = noCache; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public String getIcon() + { + return icon; + } + + public void setIcon(String icon) + { + this.icon = icon; + } + + public String getLink() + { + return link; + } + + public void setLink(String link) + { + this.link = link; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java new file mode 100644 index 0000000..afff8c9 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java @@ -0,0 +1,148 @@ +package com.ruoyi.system.domain.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; + +/** + * 路由配置信息 + * + * @author ruoyi + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class RouterVo +{ + /** + * 路由名字 + */ + private String name; + + /** + * 路由地址 + */ + private String path; + + /** + * 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现 + */ + private boolean hidden; + + /** + * 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + */ + private String redirect; + + /** + * 组件地址 + */ + private String component; + + /** + * 路由参数:如 {"id": 1, "name": "ry"} + */ + private String query; + + /** + * 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + */ + private Boolean alwaysShow; + + /** + * 其他元素 + */ + private MetaVo meta; + + /** + * 子路由 + */ + private List children; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getPath() + { + return path; + } + + public void setPath(String path) + { + this.path = path; + } + + public boolean getHidden() + { + return hidden; + } + + public void setHidden(boolean hidden) + { + this.hidden = hidden; + } + + public String getRedirect() + { + return redirect; + } + + public void setRedirect(String redirect) + { + this.redirect = redirect; + } + + public String getComponent() + { + return component; + } + + public void setComponent(String component) + { + this.component = component; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public Boolean getAlwaysShow() + { + return alwaysShow; + } + + public void setAlwaysShow(Boolean alwaysShow) + { + this.alwaysShow = alwaysShow; + } + + public MetaVo getMeta() + { + return meta; + } + + public void setMeta(MetaVo meta) + { + this.meta = meta; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java new file mode 100644 index 0000000..13d49d6 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java @@ -0,0 +1,76 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysConfig; + +/** + * 参数配置 数据层 + * + * @author ruoyi + */ +public interface SysConfigMapper +{ + /** + * 查询参数配置信息 + * + * @param config 参数配置信息 + * @return 参数配置信息 + */ + public SysConfig selectConfig(SysConfig config); + + /** + * 通过ID查询配置 + * + * @param configId 参数ID + * @return 参数配置信息 + */ + public SysConfig selectConfigById(Long configId); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List selectConfigList(SysConfig config); + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数配置信息 + */ + public SysConfig checkConfigKeyUnique(String configKey); + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int insertConfig(SysConfig config); + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int updateConfig(SysConfig config); + + /** + * 删除参数配置 + * + * @param configId 参数ID + * @return 结果 + */ + public int deleteConfigById(Long configId); + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + * @return 结果 + */ + public int deleteConfigByIds(Long[] configIds); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java new file mode 100644 index 0000000..384a9b6 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java @@ -0,0 +1,118 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.ruoyi.common.core.domain.entity.SysDept; + +/** + * 部门管理 数据层 + * + * @author ruoyi + */ +public interface SysDeptMapper +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @param deptCheckStrictly 部门树选择项是否关联显示 + * @return 选中部门列表 + */ + public List selectDeptListByRoleId(@Param("roleId") Long roleId, @Param("deptCheckStrictly") boolean deptCheckStrictly); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门 + * + * @param deptId 部门ID + * @return 部门列表 + */ + public List selectChildrenDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); + + /** + * 是否存在子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + public int hasChildByDeptId(Long deptId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 + */ + public int checkDeptExistUser(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param deptName 部门名称 + * @param parentId 父部门ID + * @return 结果 + */ + public SysDept checkDeptNameUnique(@Param("deptName") String deptName, @Param("parentId") Long parentId); + + /** + * 新增部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 修改所在部门正常状态 + * + * @param deptIds 部门ID组 + */ + public void updateDeptStatusNormal(Long[] deptIds); + + /** + * 修改子元素关系 + * + * @param depts 子元素 + * @return 结果 + */ + public int updateDeptChildren(@Param("depts") List depts); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java new file mode 100644 index 0000000..a341f1e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java @@ -0,0 +1,95 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.ruoyi.common.core.domain.entity.SysDictData; + +/** + * 字典表 数据层 + * + * @author ruoyi + */ +public interface SysDictDataMapper +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List selectDictDataList(SysDictData dictData); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List selectDictDataByType(String dictType); + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + public String selectDictLabel(@Param("dictType") String dictType, @Param("dictValue") String dictValue); + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + public SysDictData selectDictDataById(Long dictCode); + + /** + * 查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据 + */ + public int countDictDataByType(String dictType); + + /** + * 通过字典ID删除字典数据信息 + * + * @param dictCode 字典数据ID + * @return 结果 + */ + public int deleteDictDataById(Long dictCode); + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + * @return 结果 + */ + public int deleteDictDataByIds(Long[] dictCodes); + + /** + * 新增字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int insertDictData(SysDictData dictData); + + /** + * 修改字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int updateDictData(SysDictData dictData); + + /** + * 同步修改字典类型 + * + * @param oldDictType 旧字典类型 + * @param newDictType 新旧字典类型 + * @return 结果 + */ + public int updateDictDataType(@Param("oldDictType") String oldDictType, @Param("newDictType") String newDictType); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java new file mode 100644 index 0000000..5fb48fb --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java @@ -0,0 +1,83 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.common.core.domain.entity.SysDictType; + +/** + * 字典表 数据层 + * + * @author ruoyi + */ +public interface SysDictTypeMapper +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List selectDictTypeAll(); + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + public SysDictType selectDictTypeById(Long dictId); + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + public SysDictType selectDictTypeByType(String dictType); + + /** + * 通过字典ID删除字典信息 + * + * @param dictId 字典ID + * @return 结果 + */ + public int deleteDictTypeById(Long dictId); + + /** + * 批量删除字典类型信息 + * + * @param dictIds 需要删除的字典ID + * @return 结果 + */ + public int deleteDictTypeByIds(Long[] dictIds); + + /** + * 新增字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int insertDictType(SysDictType dictType); + + /** + * 修改字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int updateDictType(SysDictType dictType); + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + public SysDictType checkDictTypeUnique(String dictType); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java new file mode 100644 index 0000000..629866f --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java @@ -0,0 +1,42 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysLogininfor; + +/** + * 系统访问日志情况信息 数据层 + * + * @author ruoyi + */ +public interface SysLogininforMapper +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + * + * @return 结果 + */ + public int cleanLogininfor(); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java new file mode 100644 index 0000000..99c0c50 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java @@ -0,0 +1,125 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.ruoyi.common.core.domain.entity.SysMenu; + +/** + * 菜单表 数据层 + * + * @author ruoyi + */ +public interface SysMenuMapper +{ + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuList(SysMenu menu); + + /** + * 根据用户所有权限 + * + * @return 权限列表 + */ + public List selectMenuPerms(); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuListByUserId(SysMenu menu); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + public List selectMenuPermsByRoleId(Long roleId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public List selectMenuPermsByUserId(Long userId); + + /** + * 根据用户ID查询菜单 + * + * @return 菜单列表 + */ + public List selectMenuTreeAll(); + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @param menuCheckStrictly 菜单树选择项是否关联显示 + * @return 选中菜单列表 + */ + public List selectMenuListByRoleId(@Param("roleId") Long roleId, @Param("menuCheckStrictly") boolean menuCheckStrictly); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + public SysMenu selectMenuById(Long menuId); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int hasChildByMenuId(Long menuId); + + /** + * 新增菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int insertMenu(SysMenu menu); + + /** + * 修改菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int updateMenu(SysMenu menu); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + + /** + * 校验菜单名称是否唯一 + * + * @param menuName 菜单名称 + * @param parentId 父菜单ID + * @return 结果 + */ + public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java new file mode 100644 index 0000000..c34f0a2 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java @@ -0,0 +1,60 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysNotice; + +/** + * 通知公告表 数据层 + * + * @author ruoyi + */ +public interface SysNoticeMapper +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List selectNoticeList(SysNotice notice); + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int insertNotice(SysNotice notice); + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int updateNotice(SysNotice notice); + + /** + * 批量删除公告 + * + * @param noticeId 公告ID + * @return 结果 + */ + public int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + public int deleteNoticeByIds(Long[] noticeIds); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java new file mode 100644 index 0000000..16d3ca1 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java @@ -0,0 +1,70 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import java.util.Map; + +import com.ruoyi.system.domain.SysOperLog; + +/** + * 操作日志 数据层 + * + * @author ruoyi + */ +public interface SysOperLogMapper +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + public int deleteOperLogByIds(Long[] operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); + + /** + * 获取成功操作的统计信息 + */ + public List> getSuccessOperationStats(SysOperLog operLog); + + /** + * 获取失败操作的统计信息 + */ + public List> getFailureOperationStats(SysOperLog operLog); + + /** + * 获取按状态分类的操作统计信息 + */ + public List> getStatusStats(SysOperLog operLog); + + /** + * 获取按模块和操作类型分类的操作统计信息 + */ + public List> getModuleOperationStats(SysOperLog operLog); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java new file mode 100644 index 0000000..19be227 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java @@ -0,0 +1,99 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysPost; + +/** + * 岗位信息 数据层 + * + * @author ruoyi + */ +public interface SysPostMapper +{ + /** + * 查询岗位数据集合 + * + * @param post 岗位信息 + * @return 岗位数据集合 + */ + public List selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List selectPostListByUserId(Long userId); + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + public List selectPostsByUserName(String userName); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + public int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + public int deletePostByIds(Long[] postIds); + + /** + * 修改岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int updatePost(SysPost post); + + /** + * 新增岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int insertPost(SysPost post); + + /** + * 校验岗位名称 + * + * @param postName 岗位名称 + * @return 结果 + */ + public SysPost checkPostNameUnique(String postName); + + /** + * 校验岗位编码 + * + * @param postCode 岗位编码 + * @return 结果 + */ + public SysPost checkPostCodeUnique(String postCode); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java new file mode 100644 index 0000000..f9d3a2f --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java @@ -0,0 +1,44 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysRoleDept; + +/** + * 角色与部门关联表 数据层 + * + * @author ruoyi + */ +public interface SysRoleDeptMapper +{ + /** + * 通过角色ID删除角色和部门关联 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleDeptByRoleId(Long roleId); + + /** + * 批量删除角色部门关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleDept(Long[] ids); + + /** + * 查询部门使用数量 + * + * @param deptId 部门ID + * @return 结果 + */ + public int selectCountRoleDeptByDeptId(Long deptId); + + /** + * 批量新增角色部门信息 + * + * @param roleDeptList 角色部门列表 + * @return 结果 + */ + public int batchRoleDept(List roleDeptList); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java new file mode 100644 index 0000000..cf2bd8c --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java @@ -0,0 +1,107 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.common.core.domain.entity.SysRole; + +/** + * 角色表 数据层 + * + * @author ruoyi + */ +public interface SysRoleMapper +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 根据用户ID查询角色 + * + * @param userName 用户名 + * @return 角色列表 + */ + public List selectRolesByUserName(String userName); + + /** + * 校验角色名称是否唯一 + * + * @param roleName 角色名称 + * @return 角色信息 + */ + public SysRole checkRoleNameUnique(String roleName); + + /** + * 校验角色权限是否唯一 + * + * @param roleKey 角色权限 + * @return 角色信息 + */ + public SysRole checkRoleKeyUnique(String roleKey); + + /** + * 修改角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 新增角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int insertRole(SysRole role); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleById(Long roleId); + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + public int deleteRoleByIds(Long[] roleIds); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..6602bee --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java @@ -0,0 +1,44 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysRoleMenu; + +/** + * 角色与菜单关联表 数据层 + * + * @author ruoyi + */ +public interface SysRoleMenuMapper +{ + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int checkMenuExistRole(Long menuId); + + /** + * 通过角色ID删除角色和菜单关联 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleMenuByRoleId(Long roleId); + + /** + * 批量删除角色菜单关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleMenu(Long[] ids); + + /** + * 批量新增角色菜单信息 + * + * @param roleMenuList 角色菜单列表 + * @return 结果 + */ + public int batchRoleMenu(List roleMenuList); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUnitConvertMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUnitConvertMapper.java new file mode 100644 index 0000000..847a72c --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUnitConvertMapper.java @@ -0,0 +1,78 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysUnitConvert; +import org.apache.ibatis.annotations.Param; + +/** + * 单位换算Mapper接口 + * + * @author ruoyi + * @date 2025-10-08 + */ +public interface SysUnitConvertMapper +{ + /** + * 查询单位换算 + * + * @param id 单位换算主键 + * @return 单位换算 + */ + public SysUnitConvert selectSysUnitConvertById(Long id); + + /** + * 查询单位换算列表 + * + * @param sysUnitConvert 单位换算 + * @return 单位换算集合 + */ + public List selectSysUnitConvertList(SysUnitConvert sysUnitConvert); + + /** + * 新增单位换算 + * + * @param sysUnitConvert 单位换算 + * @return 结果 + */ + public int insertSysUnitConvert(SysUnitConvert sysUnitConvert); + + /** + * 修改单位换算 + * + * @param sysUnitConvert 单位换算 + * @return 结果 + */ + public int updateSysUnitConvert(SysUnitConvert sysUnitConvert); + + /** + * 删除单位换算 + * + * @param id 单位换算主键 + * @return 结果 + */ + public int deleteSysUnitConvertById(Long id); + + /** + * 批量删除单位换算 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSysUnitConvertByIds(Long[] ids); + + + /** + * 查询单位换算 + * + * @param unitType 单位换算 + * @param unitOrder 单位换算 + * @param baseUnit 单位换算 + * @return 结果 + */ + public SysUnitConvert selectSysUnitConvertUnitByTypeOrder( + @Param("unitType") String unitType, + @Param("unitOrder") Long unitOrder, // 修正参数类型 + @Param("baseUnit") String baseUnit + ); + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java new file mode 100644 index 0000000..776dbaf --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java @@ -0,0 +1,144 @@ +package com.ruoyi.system.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Param; + +import com.ruoyi.common.core.domain.entity.SysUser; + +/** + * 用户表 数据层 + * + * @author ruoyi + */ +public interface SysUserMapper { + /** + * 根据条件分页查询用户列表 + * + * @param sysUser 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser sysUser); + + /** + * 根据条件分页查询已配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUnallocatedList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByUserName(String userName); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 通过手机号查询用户 + * + * @param phone 手机号 + * @return 用户对象信息 + */ + public SysUser selectUserByPhone(String phone); + + /** + * 通过邮箱查询用户 + * + * @param email 邮箱 + * @return 用户对象信息 + */ + public SysUser selectUserByEmail(String email); + + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + public int updateUserAvatar(@Param("userName") String userName, @Param("avatar") String avatar); + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(@Param("userName") String userName, @Param("password") String password); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + public int deleteUserByIds(Long[] userIds); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public SysUser checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public SysUser checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public SysUser checkEmailUnique(String email); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java new file mode 100644 index 0000000..2a6a720 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java @@ -0,0 +1,44 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.SysUserPost; + +/** + * 用户与岗位关联表 数据层 + * + * @author ruoyi + */ +public interface SysUserPostMapper +{ + /** + * 通过用户ID删除用户和岗位关联 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserPostByUserId(Long userId); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + public int countUserPostById(Long postId); + + /** + * 批量删除用户和岗位关联 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserPost(Long[] ids); + + /** + * 批量新增用户岗位信息 + * + * @param userPostList 用户岗位列表 + * @return 结果 + */ + public int batchUserPost(List userPostList); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..3143ec8 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java @@ -0,0 +1,62 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.ruoyi.system.domain.SysUserRole; + +/** + * 用户与角色关联表 数据层 + * + * @author ruoyi + */ +public interface SysUserRoleMapper +{ + /** + * 通过用户ID删除用户和角色关联 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserRoleByUserId(Long userId); + + /** + * 批量删除用户和角色关联 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserRole(Long[] ids); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 批量新增用户角色信息 + * + * @param userRoleList 用户角色列表 + * @return 结果 + */ + public int batchUserRole(List userRoleList); + + /** + * 删除用户和角色关联信息 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + public int deleteUserRoleInfo(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java new file mode 100644 index 0000000..b307776 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java @@ -0,0 +1,89 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.system.domain.SysConfig; + +/** + * 参数配置 服务层 + * + * @author ruoyi + */ +public interface ISysConfigService +{ + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + public SysConfig selectConfigById(Long configId); + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数键值 + */ + public String selectConfigByKey(String configKey); + + /** + * 获取验证码开关 + * + * @return true开启,false关闭 + */ + public boolean selectCaptchaEnabled(); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List selectConfigList(SysConfig config); + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int insertConfig(SysConfig config); + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int updateConfig(SysConfig config); + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + */ + public void deleteConfigByIds(Long[] configIds); + + /** + * 加载参数缓存数据 + */ + public void loadingConfigCache(); + + /** + * 清空参数缓存数据 + */ + public void clearConfigCache(); + + /** + * 重置参数缓存数据 + */ + public void resetConfigCache(); + + /** + * 校验参数键名是否唯一 + * + * @param config 参数信息 + * @return 结果 + */ + public boolean checkConfigKeyUnique(SysConfig config); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java new file mode 100644 index 0000000..f228208 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java @@ -0,0 +1,124 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.common.core.domain.TreeSelect; +import com.ruoyi.common.core.domain.entity.SysDept; + +/** + * 部门管理 服务层 + * + * @author ruoyi + */ +public interface ISysDeptService +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + public List selectDeptTreeList(SysDept dept); + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + public List buildDeptTree(List depts); + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + public List buildDeptTreeSelect(List depts); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + public List selectDeptListByRoleId(Long roleId); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); + + /** + * 是否存在部门子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + public boolean hasChildByDeptId(Long deptId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkDeptExistUser(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + public boolean checkDeptNameUnique(SysDept dept); + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + public void checkDeptDataScope(Long deptId); + + /** + * 新增保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java new file mode 100644 index 0000000..9bc4f13 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java @@ -0,0 +1,60 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.common.core.domain.entity.SysDictData; + +/** + * 字典 业务层 + * + * @author ruoyi + */ +public interface ISysDictDataService +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List selectDictDataList(SysDictData dictData); + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + public String selectDictLabel(String dictType, String dictValue); + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + public SysDictData selectDictDataById(Long dictCode); + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + */ + public void deleteDictDataByIds(Long[] dictCodes); + + /** + * 新增保存字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int insertDictData(SysDictData dictData); + + /** + * 修改保存字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int updateDictData(SysDictData dictData); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java new file mode 100644 index 0000000..01c1c1d --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java @@ -0,0 +1,98 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.core.domain.entity.SysDictType; + +/** + * 字典 业务层 + * + * @author ruoyi + */ +public interface ISysDictTypeService +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List selectDictTypeAll(); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List selectDictDataByType(String dictType); + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + public SysDictType selectDictTypeById(Long dictId); + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + public SysDictType selectDictTypeByType(String dictType); + + /** + * 批量删除字典信息 + * + * @param dictIds 需要删除的字典ID + */ + public void deleteDictTypeByIds(Long[] dictIds); + + /** + * 加载字典缓存数据 + */ + public void loadingDictCache(); + + /** + * 清空字典缓存数据 + */ + public void clearDictCache(); + + /** + * 重置字典缓存数据 + */ + public void resetDictCache(); + + /** + * 新增保存字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int insertDictType(SysDictType dictType); + + /** + * 修改保存字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int updateDictType(SysDictType dictType); + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + public boolean checkDictTypeUnique(SysDictType dictType); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java new file mode 100644 index 0000000..ce3151d --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java @@ -0,0 +1,40 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.system.domain.SysLogininfor; + +/** + * 系统访问日志情况信息 服务层 + * + * @author ruoyi + */ +public interface ISysLogininforService +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + */ + public void cleanLogininfor(); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java new file mode 100644 index 0000000..7d60696 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java @@ -0,0 +1,144 @@ +package com.ruoyi.system.service; + +import java.util.List; +import java.util.Set; +import com.ruoyi.common.core.domain.TreeSelect; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.system.domain.vo.RouterVo; + +/** + * 菜单 业务层 + * + * @author ruoyi + */ +public interface ISysMenuService +{ + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuList(Long userId); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuList(SysMenu menu, Long userId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectMenuPermsByUserId(Long userId); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + public Set selectMenuPermsByRoleId(Long roleId); + + /** + * 根据用户ID查询菜单树信息 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + public List selectMenuListByRoleId(Long roleId); + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + public List buildMenus(List menus); + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + public List buildMenuTree(List menus); + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + public List buildMenuTreeSelect(List menus); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + public SysMenu selectMenuById(Long menuId); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + public boolean hasChildByMenuId(Long menuId); + + /** + * 查询菜单是否存在角色 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkMenuExistRole(Long menuId); + + /** + * 新增保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int insertMenu(SysMenu menu); + + /** + * 修改保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int updateMenu(SysMenu menu); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean checkMenuNameUnique(SysMenu menu); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java new file mode 100644 index 0000000..47ce1b7 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java @@ -0,0 +1,60 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.system.domain.SysNotice; + +/** + * 公告 服务层 + * + * @author ruoyi + */ +public interface ISysNoticeService +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List selectNoticeList(SysNotice notice); + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int insertNotice(SysNotice notice); + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int updateNotice(SysNotice notice); + + /** + * 删除公告信息 + * + * @param noticeId 公告ID + * @return 结果 + */ + public int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + public int deleteNoticeByIds(Long[] noticeIds); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java new file mode 100644 index 0000000..686abc8 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java @@ -0,0 +1,70 @@ +package com.ruoyi.system.service; + +import java.util.List; +import java.util.Map; + +import com.ruoyi.system.domain.SysOperLog; + +/** + * 操作日志 服务层 + * + * @author ruoyi + */ +public interface ISysOperLogService +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + public int deleteOperLogByIds(Long[] operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); + + /** + * 获取成功操作的统计信息 + */ + List> getSuccessOperationStats(SysOperLog operLog); + + /** + * 获取失败操作的统计信息 + */ + List> getFailureOperationStats(SysOperLog operLog); + + /** + * 获取按状态分类的操作统计信息 + */ + List> getStatusStats(SysOperLog operLog); + + /** + * 获取按模块和操作类型分类的操作统计信息 + */ + List> getModuleOperationStats(SysOperLog operLog); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java new file mode 100644 index 0000000..84779bf --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java @@ -0,0 +1,99 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.system.domain.SysPost; + +/** + * 岗位信息 服务层 + * + * @author ruoyi + */ +public interface ISysPostService +{ + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位列表 + */ + public List selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List selectPostListByUserId(Long userId); + + /** + * 校验岗位名称 + * + * @param post 岗位信息 + * @return 结果 + */ + public boolean checkPostNameUnique(SysPost post); + + /** + * 校验岗位编码 + * + * @param post 岗位信息 + * @return 结果 + */ + public boolean checkPostCodeUnique(SysPost post); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + public int countUserPostById(Long postId); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + public int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + public int deletePostByIds(Long[] postIds); + + /** + * 新增保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int insertPost(SysPost post); + + /** + * 修改保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int updatePost(SysPost post); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java new file mode 100644 index 0000000..6c29f09 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java @@ -0,0 +1,173 @@ +package com.ruoyi.system.service; + +import java.util.List; +import java.util.Set; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.system.domain.SysUserRole; + +/** + * 角色业务层 + * + * @author ruoyi + */ +public interface ISysRoleService +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色列表 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolesByUserId(Long userId); + + /** + * 根据用户ID查询角色权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public boolean checkRoleNameUnique(SysRole role); + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public boolean checkRoleKeyUnique(SysRole role); + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + public void checkRoleAllowed(SysRole role); + + /** + * 校验角色是否有数据权限 + * + * @param roleId 角色id + */ + public void checkRoleDataScope(Long roleId); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 新增保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int insertRole(SysRole role); + + /** + * 修改保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 修改角色状态 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRoleStatus(SysRole role); + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int authDataScope(SysRole role); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleById(Long roleId); + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + public int deleteRoleByIds(Long[] roleIds); + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + public int deleteAuthUser(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + public int deleteAuthUsers(Long roleId, Long[] userIds); + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int insertAuthUsers(Long roleId, Long[] userIds); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUnitConvertService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUnitConvertService.java new file mode 100644 index 0000000..093d26a --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUnitConvertService.java @@ -0,0 +1,73 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.system.domain.SysUnitConvert; + +/** + * 单位换算Service接口 + * + * @author ruoyi + * @date 2025-10-08 + */ +public interface ISysUnitConvertService +{ + /** + * 查询单位换算 + * + * @param id 单位换算主键 + * @return 单位换算 + */ + public SysUnitConvert selectSysUnitConvertById(Long id); + + /** + * 查询单位换算列表 + * + * @param sysUnitConvert 单位换算 + * @return 单位换算集合 + */ + public List selectSysUnitConvertList(SysUnitConvert sysUnitConvert); + + /** + * 新增单位换算 + * + * @param sysUnitConvert 单位换算 + * @return 结果 + */ + public int insertSysUnitConvert(SysUnitConvert sysUnitConvert); + + /** + * 修改单位换算 + * + * @param sysUnitConvert 单位换算 + * @return 结果 + */ + public int updateSysUnitConvert(SysUnitConvert sysUnitConvert); + + /** + * 批量删除单位换算 + * + * @param ids 需要删除的单位换算主键集合 + * @return 结果 + */ + public int deleteSysUnitConvertByIds(Long[] ids); + + /** + * 删除单位换算信息 + * + * @param id 单位换算主键 + * @return 结果 + */ + public int deleteSysUnitConvertById(Long id); + + /** + * 查询单位 + * + * @param unitType 单位换算 + * @param unitOrder 单位换算 + * @param baseUnit 单位换算 + * @return 单位换算集合 + */ + public SysUnitConvert selectSysUnitConvertUnitByTypeOrder(String unitType,Long unitOrder, String baseUnit); + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java new file mode 100644 index 0000000..8eb5448 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java @@ -0,0 +1,48 @@ +package com.ruoyi.system.service; + +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.system.domain.SysUserOnline; + +/** + * 在线用户 服务层 + * + * @author ruoyi + */ +public interface ISysUserOnlineService +{ + /** + * 通过登录地址查询信息 + * + * @param ipaddr 登录地址 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user); + + /** + * 通过用户名称查询信息 + * + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByUserName(String userName, LoginUser user); + + /** + * 通过登录地址/用户名称查询信息 + * + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user); + + /** + * 设置在线用户信息 + * + * @param user 用户信息 + * @return 在线用户 + */ + public SysUserOnline loginUserToUserOnline(LoginUser user); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java new file mode 100644 index 0000000..8536209 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java @@ -0,0 +1,222 @@ +package com.ruoyi.system.service; + +import java.util.List; + +import com.ruoyi.common.core.domain.entity.SysUser; + +/** + * 用户 业务层 + * + * @author ruoyi + */ +public interface ISysUserService { + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser user); + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUnallocatedList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByUserName(String userName); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 通过手机号查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByPhone(String phone); + + /** + * 通过邮箱查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByEmail(String email); + + /** + * 根据用户ID查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + public String selectUserRoleGroup(String userName); + + /** + * 根据用户ID查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + public String selectUserPostGroup(String userName); + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkUserNameUnique(SysUser user); + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkPhoneUnique(SysUser user); + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkEmailUnique(SysUser user); + + /** + * 校验用户是否允许操作 + * + * @param user 用户信息 + */ + public void checkUserAllowed(SysUser user); + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + public void checkUserDataScope(Long userId); + + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean registerUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserAuth(Long userId, Long[] roleIds); + + /** + * 修改用户状态 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserStatus(SysUser user); + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserProfile(SysUser user); + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + public boolean updateUserAvatar(String userName, String avatar); + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + public int resetPwd(SysUser user); + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(String userName, String password); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + public int deleteUserByIds(Long[] userIds); + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @param operName 操作用户 + * @return 结果 + */ + public String importUser(List userList, Boolean isUpdateSupport, String operName); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..269872e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,226 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; +import org.springframework.stereotype.Service; + +import com.ruoyi.common.annotation.DataSource; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.enums.DataSourceType; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.CacheUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysConfig; +import com.ruoyi.system.mapper.SysConfigMapper; +import com.ruoyi.system.service.ISysConfigService; + +import jakarta.annotation.PostConstruct; + +/** + * 参数配置 服务层实现 + * + * @author ruoyi + */ +@Service +public class SysConfigServiceImpl implements ISysConfigService +{ + @Autowired + private SysConfigMapper configMapper; + + /** + * 项目启动时,初始化参数到缓存 + */ + @PostConstruct + public void init() + { + loadingConfigCache(); + } + + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + @Override + @DataSource(DataSourceType.MASTER) + public SysConfig selectConfigById(Long configId) + { + SysConfig config = new SysConfig(); + config.setConfigId(configId); + return configMapper.selectConfig(config); + } + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数key + * @return 参数键值 + */ + @Override + public String selectConfigByKey(String configKey) + { + String configValue = Convert.toStr(getCache().get(configKey, String.class)); + if (StringUtils.isNotEmpty(configValue)) + { + return configValue; + } + SysConfig config = new SysConfig(); + config.setConfigKey(configKey); + SysConfig retConfig = configMapper.selectConfig(config); + if (StringUtils.isNotNull(retConfig)) + { + CacheUtils.put(CacheConstants.SYS_CONFIG_KEY, configKey, retConfig.getConfigValue()); + return retConfig.getConfigValue(); + } + return StringUtils.EMPTY; + } + + /** + * 获取验证码开关 + * + * @return true开启,false关闭 + */ + @Override + public boolean selectCaptchaEnabled() + { + String captchaEnabled = selectConfigByKey("sys.account.captchaEnabled"); + return Convert.toBool(captchaEnabled,true); + } + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + @Override + public List selectConfigList(SysConfig config) + { + return configMapper.selectConfigList(config); + } + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public int insertConfig(SysConfig config) + { + int row = configMapper.insertConfig(config); + if (row > 0) + { + CacheUtils.put(CacheConstants.SYS_CONFIG_KEY, config.getConfigKey(), config.getConfigValue()); + } + return row; + } + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public int updateConfig(SysConfig config) + { + SysConfig temp = configMapper.selectConfigById(config.getConfigId()); + if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey())) + { + CacheUtils.removeIfPresent(CacheConstants.SYS_CONFIG_KEY, temp.getConfigKey()); + } + + int row = configMapper.updateConfig(config); + if (row > 0) + { + CacheUtils.put(CacheConstants.SYS_CONFIG_KEY, config.getConfigKey(), config.getConfigValue()); + } + return row; + } + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + */ + @Override + public void deleteConfigByIds(Long[] configIds) + { + for (Long configId : configIds) + { + SysConfig config = selectConfigById(configId); + if (StringUtils.equals(UserConstants.YES, config.getConfigType())) + { + throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey())); + } + configMapper.deleteConfigById(configId); + getCache().evict(config.getConfigKey()); + } + } + + /** + * 加载参数缓存数据 + */ + @Override + public void loadingConfigCache() + { + List configsList = configMapper.selectConfigList(new SysConfig()); + for (SysConfig config : configsList) + { + getCache().put(config.getConfigKey(), config.getConfigValue()); + } + } + + /** + * 清空参数缓存数据 + */ + @Override + public void clearConfigCache() + { + CacheUtils.getCache(CacheConstants.SYS_CONFIG_KEY).clear(); + } + + /** + * 重置参数缓存数据 + */ + @Override + public void resetConfigCache() + { + clearConfigCache(); + loadingConfigCache(); + } + + /** + * 校验参数键名是否唯一 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public boolean checkConfigKeyUnique(SysConfig config) + { + Long configId = StringUtils.isNull(config.getConfigId()) ? -1L : config.getConfigId(); + SysConfig info = configMapper.checkConfigKeyUnique(config.getConfigKey()); + if (StringUtils.isNotNull(info) && info.getConfigId().longValue() != configId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 获取config缓存 + * + * @return + */ + private Cache getCache() + { + return CacheUtils.getCache(CacheConstants.SYS_CONFIG_KEY); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java new file mode 100644 index 0000000..f7fb088 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java @@ -0,0 +1,338 @@ +package com.ruoyi.system.service.impl; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.annotation.DataScope; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.TreeSelect; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.system.mapper.SysDeptMapper; +import com.ruoyi.system.mapper.SysRoleMapper; +import com.ruoyi.system.service.ISysDeptService; + +/** + * 部门管理 服务实现 + * + * @author ruoyi + */ +@Service +public class SysDeptServiceImpl implements ISysDeptService +{ + @Autowired + private SysDeptMapper deptMapper; + + @Autowired + private SysRoleMapper roleMapper; + + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + @Override + @DataScope(deptAlias = "d") + public List selectDeptList(SysDept dept) + { + return deptMapper.selectDeptList(dept); + } + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + @Override + public List selectDeptTreeList(SysDept dept) + { + List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + return buildDeptTreeSelect(depts); + } + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + @Override + public List buildDeptTree(List depts) + { + List returnList = new ArrayList(); + List tempList = depts.stream().map(SysDept::getDeptId).collect(Collectors.toList()); + for (SysDept dept : depts) + { + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(dept.getParentId())) + { + recursionFn(depts, dept); + returnList.add(dept); + } + } + if (returnList.isEmpty()) + { + returnList = depts; + } + return returnList; + } + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + @Override + public List buildDeptTreeSelect(List depts) + { + List deptTrees = buildDeptTree(depts); + return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + @Override + public List selectDeptListByRoleId(Long roleId) + { + SysRole role = roleMapper.selectRoleById(roleId); + return deptMapper.selectDeptListByRoleId(roleId, role.isDeptCheckStrictly()); + } + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + @Override + public SysDept selectDeptById(Long deptId) + { + return deptMapper.selectDeptById(deptId); + } + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + @Override + public int selectNormalChildrenDeptById(Long deptId) + { + return deptMapper.selectNormalChildrenDeptById(deptId); + } + + /** + * 是否存在子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + @Override + public boolean hasChildByDeptId(Long deptId) + { + int result = deptMapper.hasChildByDeptId(deptId); + return result > 0; + } + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + @Override + public boolean checkDeptExistUser(Long deptId) + { + int result = deptMapper.checkDeptExistUser(deptId); + return result > 0; + } + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public boolean checkDeptNameUnique(SysDept dept) + { + Long deptId = StringUtils.isNull(dept.getDeptId()) ? -1L : dept.getDeptId(); + SysDept info = deptMapper.checkDeptNameUnique(dept.getDeptName(), dept.getParentId()); + if (StringUtils.isNotNull(info) && info.getDeptId().longValue() != deptId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + @Override + public void checkDeptDataScope(Long deptId) + { + if (!SysUser.isAdmin(SecurityUtils.getUserId())) + { + SysDept dept = new SysDept(); + dept.setDeptId(deptId); + List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + if (StringUtils.isEmpty(depts)) + { + throw new ServiceException("没有权限访问部门数据!"); + } + } + } + + /** + * 新增保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public int insertDept(SysDept dept) + { + SysDept info = deptMapper.selectDeptById(dept.getParentId()); + // 如果父节点不为正常状态,则不允许新增子节点 + if (!UserConstants.DEPT_NORMAL.equals(info.getStatus())) + { + throw new ServiceException("部门停用,不允许新增"); + } + dept.setAncestors(info.getAncestors() + "," + dept.getParentId()); + return deptMapper.insertDept(dept); + } + + /** + * 修改保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public int updateDept(SysDept dept) + { + SysDept newParentDept = deptMapper.selectDeptById(dept.getParentId()); + SysDept oldDept = deptMapper.selectDeptById(dept.getDeptId()); + if (StringUtils.isNotNull(newParentDept) && StringUtils.isNotNull(oldDept)) + { + String newAncestors = newParentDept.getAncestors() + "," + newParentDept.getDeptId(); + String oldAncestors = oldDept.getAncestors(); + dept.setAncestors(newAncestors); + updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors); + } + int result = deptMapper.updateDept(dept); + if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors()) + && !StringUtils.equals("0", dept.getAncestors())) + { + // 如果该部门是启用状态,则启用该部门的所有上级部门 + updateParentDeptStatusNormal(dept); + } + return result; + } + + /** + * 修改该部门的父级部门状态 + * + * @param dept 当前部门 + */ + private void updateParentDeptStatusNormal(SysDept dept) + { + String ancestors = dept.getAncestors(); + Long[] deptIds = Convert.toLongArray(ancestors); + deptMapper.updateDeptStatusNormal(deptIds); + } + + /** + * 修改子元素关系 + * + * @param deptId 被修改的部门ID + * @param newAncestors 新的父ID集合 + * @param oldAncestors 旧的父ID集合 + */ + public void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors) + { + List children = deptMapper.selectChildrenDeptById(deptId); + for (SysDept child : children) + { + child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors)); + } + if (children.size() > 0) + { + deptMapper.updateDeptChildren(children); + } + } + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + @Override + public int deleteDeptById(Long deptId) + { + return deptMapper.deleteDeptById(deptId); + } + + /** + * 递归列表 + */ + private void recursionFn(List list, SysDept t) + { + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysDept tChild : childList) + { + if (hasChild(list, tChild)) + { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysDept t) + { + List tlist = new ArrayList(); + Iterator it = list.iterator(); + while (it.hasNext()) + { + SysDept n = (SysDept) it.next(); + if (StringUtils.isNotNull(n.getParentId()) && n.getParentId().longValue() == t.getDeptId().longValue()) + { + tlist.add(n); + } + } + return tlist; + } + + /** + * 判断是否有子节点 + */ + private boolean hasChild(List list, SysDept t) + { + return getChildList(list, t).size() > 0; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java new file mode 100644 index 0000000..fced569 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java @@ -0,0 +1,111 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.system.mapper.SysDictDataMapper; +import com.ruoyi.system.service.ISysDictDataService; + +/** + * 字典 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysDictDataServiceImpl implements ISysDictDataService +{ + @Autowired + private SysDictDataMapper dictDataMapper; + + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataList(SysDictData dictData) + { + return dictDataMapper.selectDictDataList(dictData); + } + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + @Override + public String selectDictLabel(String dictType, String dictValue) + { + return dictDataMapper.selectDictLabel(dictType, dictValue); + } + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + @Override + public SysDictData selectDictDataById(Long dictCode) + { + return dictDataMapper.selectDictDataById(dictCode); + } + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + */ + @Override + public void deleteDictDataByIds(Long[] dictCodes) + { + for (Long dictCode : dictCodes) + { + SysDictData data = selectDictDataById(dictCode); + dictDataMapper.deleteDictDataById(dictCode); + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + } + + /** + * 新增保存字典数据信息 + * + * @param data 字典数据信息 + * @return 结果 + */ + @Override + public int insertDictData(SysDictData data) + { + int row = dictDataMapper.insertDictData(data); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + return row; + } + + /** + * 修改保存字典数据信息 + * + * @param data 字典数据信息 + * @return 结果 + */ + @Override + public int updateDictData(SysDictData data) + { + int row = dictDataMapper.updateDictData(data); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + return row; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java new file mode 100644 index 0000000..805757b --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java @@ -0,0 +1,226 @@ +package com.ruoyi.system.service.impl; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.core.domain.entity.SysDictType; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.mapper.SysDictDataMapper; +import com.ruoyi.system.mapper.SysDictTypeMapper; +import com.ruoyi.system.service.ISysDictTypeService; + +import jakarta.annotation.PostConstruct; + +/** + * 字典 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysDictTypeServiceImpl implements ISysDictTypeService +{ + @Autowired + private SysDictTypeMapper dictTypeMapper; + + @Autowired + private SysDictDataMapper dictDataMapper; + + /** + * 项目启动时,初始化字典到缓存 + */ + @PostConstruct + public void init() + { + loadingDictCache(); + } + + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeList(SysDictType dictType) + { + return dictTypeMapper.selectDictTypeList(dictType); + } + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeAll() + { + return dictTypeMapper.selectDictTypeAll(); + } + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataByType(String dictType) + { + List dictDatas = DictUtils.getDictCache(dictType); + if (StringUtils.isNotEmpty(dictDatas)) + { + return dictDatas; + } + dictDatas = dictDataMapper.selectDictDataByType(dictType); + if (StringUtils.isNotEmpty(dictDatas)) + { + DictUtils.setDictCache(dictType, dictDatas); + return dictDatas; + } + return null; + } + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + @Override + public SysDictType selectDictTypeById(Long dictId) + { + return dictTypeMapper.selectDictTypeById(dictId); + } + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + @Override + public SysDictType selectDictTypeByType(String dictType) + { + return dictTypeMapper.selectDictTypeByType(dictType); + } + + /** + * 批量删除字典类型信息 + * + * @param dictIds 需要删除的字典ID + */ + @Override + public void deleteDictTypeByIds(Long[] dictIds) + { + for (Long dictId : dictIds) + { + SysDictType dictType = selectDictTypeById(dictId); + if (dictDataMapper.countDictDataByType(dictType.getDictType()) > 0) + { + throw new ServiceException("%1$s已分配,不能删除".formatted(dictType.getDictName())); + } + dictTypeMapper.deleteDictTypeById(dictId); + DictUtils.removeDictCache(dictType.getDictType()); + } + } + + /** + * 加载字典缓存数据 + */ + @Override + public void loadingDictCache() + { + SysDictData dictData = new SysDictData(); + dictData.setStatus("0"); + Map> dictDataMap = dictDataMapper.selectDictDataList(dictData).stream().collect(Collectors.groupingBy(SysDictData::getDictType)); + for (Map.Entry> entry : dictDataMap.entrySet()) + { + DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictData::getDictSort)).collect(Collectors.toList())); + } + } + + /** + * 清空字典缓存数据 + */ + @Override + public void clearDictCache() + { + DictUtils.clearDictCache(); + } + + /** + * 重置字典缓存数据 + */ + @Override + public void resetDictCache() + { + clearDictCache(); + loadingDictCache(); + } + + /** + * 新增保存字典类型信息 + * + * @param dict 字典类型信息 + * @return 结果 + */ + @Override + public int insertDictType(SysDictType dict) + { + int row = dictTypeMapper.insertDictType(dict); + if (row > 0) + { + DictUtils.removeDictCache(dict.getDictType()); + } + return row; + } + + /** + * 修改保存字典类型信息 + * + * @param dict 字典类型信息 + * @return 结果 + */ + @Override + @Transactional + public int updateDictType(SysDictType dict) + { + SysDictType oldDict = dictTypeMapper.selectDictTypeById(dict.getDictId()); + dictDataMapper.updateDictDataType(oldDict.getDictType(), dict.getDictType()); + int row = dictTypeMapper.updateDictType(dict); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(dict.getDictType()); + DictUtils.setDictCache(dict.getDictType(), dictDatas); + } + return row; + } + + /** + * 校验字典类型称是否唯一 + * + * @param dict 字典类型 + * @return 结果 + */ + @Override + public boolean checkDictTypeUnique(SysDictType dict) + { + Long dictId = StringUtils.isNull(dict.getDictId()) ? -1L : dict.getDictId(); + SysDictType dictType = dictTypeMapper.checkDictTypeUnique(dict.getDictType()); + if (StringUtils.isNotNull(dictType) && dictType.getDictId().longValue() != dictId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java new file mode 100644 index 0000000..216aecb --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java @@ -0,0 +1,65 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.system.domain.SysLogininfor; +import com.ruoyi.system.mapper.SysLogininforMapper; +import com.ruoyi.system.service.ISysLogininforService; + +/** + * 系统访问日志情况信息 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysLogininforServiceImpl implements ISysLogininforService +{ + + @Autowired + private SysLogininforMapper logininforMapper; + + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + @Override + public void insertLogininfor(SysLogininfor logininfor) + { + logininforMapper.insertLogininfor(logininfor); + } + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + @Override + public List selectLogininforList(SysLogininfor logininfor) + { + return logininforMapper.selectLogininforList(logininfor); + } + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + @Override + public int deleteLogininforByIds(Long[] infoIds) + { + return logininforMapper.deleteLogininforByIds(infoIds); + } + + /** + * 清空系统登录日志 + */ + @Override + public void cleanLogininfor() + { + logininforMapper.cleanLogininfor(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..f6371e0 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,484 @@ +package com.ruoyi.system.service.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.TreeSelect; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.vo.MetaVo; +import com.ruoyi.system.domain.vo.RouterVo; +import com.ruoyi.system.mapper.SysMenuMapper; +import com.ruoyi.system.mapper.SysRoleMapper; +import com.ruoyi.system.mapper.SysRoleMenuMapper; +import com.ruoyi.system.service.ISysMenuService; + +/** + * 菜单 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysMenuServiceImpl implements ISysMenuService { + public static final String PREMISSION_STRING = "perms[\"{0}\"]"; + + @Autowired + private SysMenuMapper menuMapper; + + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + @Override + public List selectMenuList(Long userId) { + return selectMenuList(new SysMenu(), userId); + } + + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + @Override + public List selectMenuList(SysMenu menu, Long userId) { + List menuList = null; + // 管理员显示所有菜单信息 + if (SysUser.isAdmin(userId)) { + menuList = menuMapper.selectMenuList(menu); + } else { + menu.getParams().put("userId", userId); + menuList = menuMapper.selectMenuListByUserId(menu); + } + return menuList; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectMenuPermsByUserId(Long userId) { + List perms = menuMapper.selectMenuPermsByUserId(userId); + Set permsSet = new HashSet<>(); + for (String perm : perms) { + if (StringUtils.isNotEmpty(perm)) { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + @Override + public Set selectMenuPermsByRoleId(Long roleId) { + List perms = menuMapper.selectMenuPermsByRoleId(roleId); + Set permsSet = new HashSet<>(); + for (String perm : perms) { + if (StringUtils.isNotEmpty(perm)) { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户名称 + * @return 菜单列表 + */ + @Override + public List selectMenuTreeByUserId(Long userId) { + List menus = null; + if (SecurityUtils.isAdmin(userId)) { + menus = menuMapper.selectMenuTreeAll(); + } else { + menus = menuMapper.selectMenuTreeByUserId(userId); + } + return getChildPerms(menus, 0); + } + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + @Override + public List selectMenuListByRoleId(Long roleId) { + SysRole role = roleMapper.selectRoleById(roleId); + return menuMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly()); + } + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + @Override + public List buildMenus(List menus) { + List routers = new LinkedList(); + for (SysMenu menu : menus) { + RouterVo router = new RouterVo(); + router.setHidden("1".equals(menu.getVisible())); + router.setName(getRouteName(menu)); + router.setPath(getRouterPath(menu)); + router.setComponent(getComponent(menu)); + router.setQuery(menu.getQuery()); + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), + menu.getPath())); + List cMenus = menu.getChildren(); + if (StringUtils.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType())) { + router.setAlwaysShow(true); + router.setRedirect("noRedirect"); + router.setChildren(buildMenus(cMenus)); + } else if (isMenuFrame(menu)) { + router.setMeta(null); + List childrenList = new ArrayList(); + RouterVo children = new RouterVo(); + children.setPath(menu.getPath()); + children.setComponent(menu.getComponent()); + children.setName(getRouteName(menu.getRouteName(), menu.getPath())); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), + StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + children.setQuery(menu.getQuery()); + childrenList.add(children); + router.setChildren(childrenList); + } else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) { + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon())); + router.setPath("/"); + List childrenList = new ArrayList(); + RouterVo children = new RouterVo(); + String routerPath = innerLinkReplaceEach(menu.getPath()); + children.setPath(routerPath); + children.setComponent(UserConstants.INNER_LINK); + children.setName(getRouteName(menu.getRouteName(), routerPath)); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath())); + childrenList.add(children); + router.setChildren(childrenList); + } + routers.add(router); + } + return routers; + } + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + @Override + public List buildMenuTree(List menus) { + List returnList = new ArrayList(); + List tempList = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList()); + for (Iterator iterator = menus.iterator(); iterator.hasNext();) { + SysMenu menu = (SysMenu) iterator.next(); + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(menu.getParentId())) { + recursionFn(menus, menu); + returnList.add(menu); + } + } + if (returnList.isEmpty()) { + returnList = menus; + } + return returnList; + } + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + @Override + public List buildMenuTreeSelect(List menus) { + List menuTrees = buildMenuTree(menus); + return menuTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + @Override + public SysMenu selectMenuById(Long menuId) { + return menuMapper.selectMenuById(menuId); + } + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean hasChildByMenuId(Long menuId) { + int result = menuMapper.hasChildByMenuId(menuId); + return result > 0; + } + + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean checkMenuExistRole(Long menuId) { + int result = roleMenuMapper.checkMenuExistRole(menuId); + return result > 0; + } + + /** + * 新增保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public int insertMenu(SysMenu menu) { + return menuMapper.insertMenu(menu); + } + + /** + * 修改保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public int updateMenu(SysMenu menu) { + return menuMapper.updateMenu(menu); + } + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public int deleteMenuById(Long menuId) { + return menuMapper.deleteMenuById(menuId); + } + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public boolean checkMenuNameUnique(SysMenu menu) { + Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId(); + SysMenu info = menuMapper.checkMenuNameUnique(menu.getMenuName(), menu.getParentId()); + if (StringUtils.isNotNull(info) && info.getMenuId().longValue() != menuId.longValue()) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 获取路由名称 + * + * @param menu 菜单信息 + * @return 路由名称 + */ + public String getRouteName(SysMenu menu) { + // 非外链并且是一级目录(类型为目录) + if (isMenuFrame(menu)) { + return StringUtils.EMPTY; + } + return getRouteName(menu.getRouteName(), menu.getPath()); + } + + /** + * 获取路由名称,如没有配置路由名称则取路由地址 + * + * @param routerName 路由名称 + * @param path 路由地址 + * @return 路由名称(驼峰格式) + */ + public String getRouteName(String name, String path) { + String routerName = StringUtils.isNotEmpty(name) ? name : path; + return StringUtils.capitalize(routerName); + } + + /** + * 获取路由地址 + * + * @param menu 菜单信息 + * @return 路由地址 + */ + public String getRouterPath(SysMenu menu) { + String routerPath = menu.getPath(); + // 内链打开外网方式 + if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) { + routerPath = innerLinkReplaceEach(routerPath); + } + // 非外链并且是一级目录(类型为目录) + if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType()) + && UserConstants.NO_FRAME.equals(menu.getIsFrame())) { + routerPath = "/" + menu.getPath(); + } + // 非外链并且是一级目录(类型为菜单) + else if (isMenuFrame(menu)) { + routerPath = "/"; + } + return routerPath; + } + + /** + * 获取组件信息 + * + * @param menu 菜单信息 + * @return 组件信息 + */ + public String getComponent(SysMenu menu) { + String component = UserConstants.LAYOUT; + if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) { + component = menu.getComponent(); + } else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 + && isInnerLink(menu)) { + component = UserConstants.INNER_LINK; + } else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) { + component = UserConstants.PARENT_VIEW; + } + return component; + } + + /** + * 是否为菜单内部跳转 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isMenuFrame(SysMenu menu) { + return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType()) + && menu.getIsFrame().equals(UserConstants.NO_FRAME); + } + + /** + * 是否为内链组件 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isInnerLink(SysMenu menu) { + return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath()); + } + + /** + * 是否为parent_view组件 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isParentView(SysMenu menu) { + return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()); + } + + /** + * 根据父节点的ID获取所有子节点 + * + * @param list 分类表 + * @param parentId 传入的父节点ID + * @return String + */ + public List getChildPerms(List list, int parentId) { + List returnList = new ArrayList(); + for (Iterator iterator = list.iterator(); iterator.hasNext();) { + SysMenu t = (SysMenu) iterator.next(); + // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点 + if (t.getParentId() == parentId) { + recursionFn(list, t); + returnList.add(t); + } + } + return returnList; + } + + /** + * 递归列表 + * + * @param list 分类表 + * @param t 子节点 + */ + private void recursionFn(List list, SysMenu t) { + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysMenu tChild : childList) { + if (hasChild(list, tChild)) { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysMenu t) { + List tlist = new ArrayList(); + Iterator it = list.iterator(); + while (it.hasNext()) { + SysMenu n = (SysMenu) it.next(); + if (n.getParentId().longValue() == t.getMenuId().longValue()) { + tlist.add(n); + } + } + return tlist; + } + + /** + * 判断是否有子节点 + */ + private boolean hasChild(List list, SysMenu t) { + return getChildList(list, t).size() > 0; + } + + /** + * 内链域名特殊字符替换 + * + * @return 替换后的内链域名 + */ + public String innerLinkReplaceEach(String path) { + return StringUtils.replaceEach(path, new String[] { Constants.HTTP, Constants.HTTPS, Constants.WWW, "." }, + new String[] { "", "", "", "/" }); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java new file mode 100644 index 0000000..765438b --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java @@ -0,0 +1,92 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.system.domain.SysNotice; +import com.ruoyi.system.mapper.SysNoticeMapper; +import com.ruoyi.system.service.ISysNoticeService; + +/** + * 公告 服务层实现 + * + * @author ruoyi + */ +@Service +public class SysNoticeServiceImpl implements ISysNoticeService +{ + @Autowired + private SysNoticeMapper noticeMapper; + + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + @Override + public SysNotice selectNoticeById(Long noticeId) + { + return noticeMapper.selectNoticeById(noticeId); + } + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + @Override + public List selectNoticeList(SysNotice notice) + { + return noticeMapper.selectNoticeList(notice); + } + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + @Override + public int insertNotice(SysNotice notice) + { + return noticeMapper.insertNotice(notice); + } + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + @Override + public int updateNotice(SysNotice notice) + { + return noticeMapper.updateNotice(notice); + } + + /** + * 删除公告对象 + * + * @param noticeId 公告ID + * @return 结果 + */ + @Override + public int deleteNoticeById(Long noticeId) + { + return noticeMapper.deleteNoticeById(noticeId); + } + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + @Override + public int deleteNoticeByIds(Long[] noticeIds) + { + return noticeMapper.deleteNoticeByIds(noticeIds); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java new file mode 100644 index 0000000..7489242 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java @@ -0,0 +1,99 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ruoyi.system.domain.SysOperLog; +import com.ruoyi.system.mapper.SysOperLogMapper; +import com.ruoyi.system.service.ISysOperLogService; + +/** + * 操作日志 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysOperLogServiceImpl implements ISysOperLogService +{ + @Autowired + private SysOperLogMapper operLogMapper; + + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + @Override + public void insertOperlog(SysOperLog operLog) + { + operLogMapper.insertOperlog(operLog); + } + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + @Override + public List selectOperLogList(SysOperLog operLog) + { + return operLogMapper.selectOperLogList(operLog); + } + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + @Override + public int deleteOperLogByIds(Long[] operIds) + { + return operLogMapper.deleteOperLogByIds(operIds); + } + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + @Override + public SysOperLog selectOperLogById(Long operId) + { + return operLogMapper.selectOperLogById(operId); + } + + /** + * 清空操作日志 + */ + @Override + public void cleanOperLog() + { + operLogMapper.cleanOperLog(); + } + + @Override + public List> getSuccessOperationStats(SysOperLog operLog) { + return operLogMapper.getSuccessOperationStats(operLog); + } + + @Override + public List> getFailureOperationStats(SysOperLog operLog) { + return operLogMapper.getFailureOperationStats(operLog); + } + + @Override + public List> getStatusStats(SysOperLog operLog) { + return operLogMapper.getStatusStats(operLog); + } + + @Override + public List> getModuleOperationStats(SysOperLog operLog) { + return operLogMapper.getModuleOperationStats(operLog); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java new file mode 100644 index 0000000..4964acd --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java @@ -0,0 +1,178 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysPost; +import com.ruoyi.system.mapper.SysPostMapper; +import com.ruoyi.system.mapper.SysUserPostMapper; +import com.ruoyi.system.service.ISysPostService; + +/** + * 岗位信息 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysPostServiceImpl implements ISysPostService +{ + @Autowired + private SysPostMapper postMapper; + + @Autowired + private SysUserPostMapper userPostMapper; + + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位信息集合 + */ + @Override + public List selectPostList(SysPost post) + { + return postMapper.selectPostList(post); + } + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + @Override + public List selectPostAll() + { + return postMapper.selectPostAll(); + } + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + @Override + public SysPost selectPostById(Long postId) + { + return postMapper.selectPostById(postId); + } + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + @Override + public List selectPostListByUserId(Long userId) + { + return postMapper.selectPostListByUserId(userId); + } + + /** + * 校验岗位名称是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public boolean checkPostNameUnique(SysPost post) + { + Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId(); + SysPost info = postMapper.checkPostNameUnique(post.getPostName()); + if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验岗位编码是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public boolean checkPostCodeUnique(SysPost post) + { + Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId(); + SysPost info = postMapper.checkPostCodeUnique(post.getPostCode()); + if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int countUserPostById(Long postId) + { + return userPostMapper.countUserPostById(postId); + } + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int deletePostById(Long postId) + { + return postMapper.deletePostById(postId); + } + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + @Override + public int deletePostByIds(Long[] postIds) + { + for (Long postId : postIds) + { + SysPost post = selectPostById(postId); + if (countUserPostById(postId) > 0) + { + throw new ServiceException("%1$s已分配,不能删除".formatted(post.getPostName())); + } + } + return postMapper.deletePostByIds(postIds); + } + + /** + * 新增保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public int insertPost(SysPost post) + { + return postMapper.insertPost(post); + } + + /** + * 修改保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public int updatePost(SysPost post) + { + return postMapper.updatePost(post); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..51301df --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,424 @@ +package com.ruoyi.system.service.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.ruoyi.common.annotation.DataScope; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.system.domain.SysRoleDept; +import com.ruoyi.system.domain.SysRoleMenu; +import com.ruoyi.system.domain.SysUserRole; +import com.ruoyi.system.mapper.SysRoleDeptMapper; +import com.ruoyi.system.mapper.SysRoleMapper; +import com.ruoyi.system.mapper.SysRoleMenuMapper; +import com.ruoyi.system.mapper.SysUserRoleMapper; +import com.ruoyi.system.service.ISysRoleService; + +/** + * 角色 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysRoleServiceImpl implements ISysRoleService +{ + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private SysRoleDeptMapper roleDeptMapper; + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + @Override + @DataScope(deptAlias = "d") + public List selectRoleList(SysRole role) + { + return roleMapper.selectRoleList(role); + } + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + @Override + public List selectRolesByUserId(Long userId) + { + List userRoles = roleMapper.selectRolePermissionByUserId(userId); + List roles = selectRoleAll(); + for (SysRole role : roles) + { + for (SysRole userRole : userRoles) + { + if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) + { + role.setFlag(true); + break; + } + } + } + return roles; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectRolePermissionByUserId(Long userId) + { + List perms = roleMapper.selectRolePermissionByUserId(userId); + Set permsSet = new HashSet<>(); + for (SysRole perm : perms) + { + if (StringUtils.isNotNull(perm)) + { + permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); + } + } + return permsSet; + } + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + @Override + public List selectRoleAll() + { + return SpringUtils.getAopProxy(this).selectRoleList(new SysRole()); + } + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + @Override + public List selectRoleListByUserId(Long userId) + { + return roleMapper.selectRoleListByUserId(userId); + } + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + @Override + public SysRole selectRoleById(Long roleId) + { + return roleMapper.selectRoleById(roleId); + } + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public boolean checkRoleNameUnique(SysRole role) + { + Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); + SysRole info = roleMapper.checkRoleNameUnique(role.getRoleName()); + if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public boolean checkRoleKeyUnique(SysRole role) + { + Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); + SysRole info = roleMapper.checkRoleKeyUnique(role.getRoleKey()); + if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + @Override + public void checkRoleAllowed(SysRole role) + { + if (StringUtils.isNotNull(role.getRoleId()) && role.isAdmin()) + { + throw new ServiceException("不允许操作超级管理员角色"); + } + } + + /** + * 校验角色是否有数据权限 + * + * @param roleId 角色id + */ + @Override + public void checkRoleDataScope(Long roleId) + { + if (!SysUser.isAdmin(SecurityUtils.getUserId())) + { + SysRole role = new SysRole(); + role.setRoleId(roleId); + List roles = SpringUtils.getAopProxy(this).selectRoleList(role); + if (StringUtils.isEmpty(roles)) + { + throw new ServiceException("没有权限访问角色数据!"); + } + } + } + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + public int countUserRoleByRoleId(Long roleId) + { + return userRoleMapper.countUserRoleByRoleId(roleId); + } + + /** + * 新增保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int insertRole(SysRole role) + { + // 新增角色信息 + roleMapper.insertRole(role); + return insertRoleMenu(role); + } + + /** + * 修改保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int updateRole(SysRole role) + { + // 修改角色信息 + roleMapper.updateRole(role); + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId()); + return insertRoleMenu(role); + } + + /** + * 修改角色状态 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public int updateRoleStatus(SysRole role) + { + return roleMapper.updateRole(role); + } + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int authDataScope(SysRole role) + { + // 修改角色信息 + roleMapper.updateRole(role); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(role.getRoleId()); + // 新增角色和部门信息(数据权限) + return insertRoleDept(role); + } + + /** + * 新增角色菜单信息 + * + * @param role 角色对象 + */ + public int insertRoleMenu(SysRole role) + { + int rows = 1; + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long menuId : role.getMenuIds()) + { + SysRoleMenu rm = new SysRoleMenu(); + rm.setRoleId(role.getRoleId()); + rm.setMenuId(menuId); + list.add(rm); + } + if (list.size() > 0) + { + rows = roleMenuMapper.batchRoleMenu(list); + } + return rows; + } + + /** + * 新增角色部门信息(数据权限) + * + * @param role 角色对象 + */ + public int insertRoleDept(SysRole role) + { + int rows = 1; + // 新增角色与部门(数据权限)管理 + List list = new ArrayList(); + for (Long deptId : role.getDeptIds()) + { + SysRoleDept rd = new SysRoleDept(); + rd.setRoleId(role.getRoleId()); + rd.setDeptId(deptId); + list.add(rd); + } + if (list.size() > 0) + { + rows = roleDeptMapper.batchRoleDept(list); + } + return rows; + } + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + @Transactional + public int deleteRoleById(Long roleId) + { + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(roleId); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(roleId); + return roleMapper.deleteRoleById(roleId); + } + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + @Override + @Transactional + public int deleteRoleByIds(Long[] roleIds) + { + for (Long roleId : roleIds) + { + checkRoleAllowed(new SysRole(roleId)); + checkRoleDataScope(roleId); + SysRole role = selectRoleById(roleId); + if (countUserRoleByRoleId(roleId) > 0) + { + throw new ServiceException("%1$s已分配,不能删除".formatted(role.getRoleName())); + } + } + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenu(roleIds); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDept(roleIds); + return roleMapper.deleteRoleByIds(roleIds); + } + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + @Override + public int deleteAuthUser(SysUserRole userRole) + { + return userRoleMapper.deleteUserRoleInfo(userRole); + } + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + @Override + public int deleteAuthUsers(Long roleId, Long[] userIds) + { + return userRoleMapper.deleteUserRoleInfos(roleId, userIds); + } + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要授权的用户数据ID + * @return 结果 + */ + @Override + public int insertAuthUsers(Long roleId, Long[] userIds) + { + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long userId : userIds) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + return userRoleMapper.batchUserRole(list); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUnitConvertServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUnitConvertServiceImpl.java new file mode 100644 index 0000000..7248f24 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUnitConvertServiceImpl.java @@ -0,0 +1,108 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.system.mapper.SysUnitConvertMapper; +import com.ruoyi.system.domain.SysUnitConvert; +import com.ruoyi.system.service.ISysUnitConvertService; + +/** + * 单位换算Service业务层处理 + * + * @author ruoyi + * @date 2025-10-08 + */ +@Service +public class SysUnitConvertServiceImpl implements ISysUnitConvertService +{ + @Autowired + private SysUnitConvertMapper sysUnitConvertMapper; + + /** + * 查询单位换算 + * + * @param id 单位换算主键 + * @return 单位换算 + */ + @Override + public SysUnitConvert selectSysUnitConvertById(Long id) + { + return sysUnitConvertMapper.selectSysUnitConvertById(id); + } + + /** + * 查询单位换算列表 + * + * @param sysUnitConvert 单位换算 + * @return 单位换算 + */ + @Override + public List selectSysUnitConvertList(SysUnitConvert sysUnitConvert) + { + return sysUnitConvertMapper.selectSysUnitConvertList(sysUnitConvert); + } + + /** + * 新增单位换算 + * + * @param sysUnitConvert 单位换算 + * @return 结果 + */ + @Override + public int insertSysUnitConvert(SysUnitConvert sysUnitConvert) + { + return sysUnitConvertMapper.insertSysUnitConvert(sysUnitConvert); + } + + /** + * 修改单位换算 + * + * @param sysUnitConvert 单位换算 + * @return 结果 + */ + @Override + public int updateSysUnitConvert(SysUnitConvert sysUnitConvert) + { + return sysUnitConvertMapper.updateSysUnitConvert(sysUnitConvert); + } + + /** + * 批量删除单位换算 + * + * @param ids 需要删除的单位换算主键 + * @return 结果 + */ + @Override + public int deleteSysUnitConvertByIds(Long[] ids) + { + return sysUnitConvertMapper.deleteSysUnitConvertByIds(ids); + } + + /** + * 删除单位换算信息 + * + * @param id 单位换算主键 + * @return 结果 + */ + @Override + public int deleteSysUnitConvertById(Long id) + { + return sysUnitConvertMapper.deleteSysUnitConvertById(id); + } + + /** + * 查询单位换算 + * @param unitType 单位换算 + * @param unitOrder 单位换算 + * @param baseUnit 单位换算 + * @return 单位换算 + */ + + @Override + + public SysUnitConvert selectSysUnitConvertUnitByTypeOrder(String unitType,Long unitOrder, String baseUnit) { + return sysUnitConvertMapper.selectSysUnitConvertUnitByTypeOrder(unitType, unitOrder,baseUnit); + } + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java new file mode 100644 index 0000000..f80a877 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java @@ -0,0 +1,96 @@ +package com.ruoyi.system.service.impl; + +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysUserOnline; +import com.ruoyi.system.service.ISysUserOnlineService; + +/** + * 在线用户 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysUserOnlineServiceImpl implements ISysUserOnlineService +{ + /** + * 通过登录地址查询信息 + * + * @param ipaddr 登录地址 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user) + { + if (StringUtils.equals(ipaddr, user.getIpaddr())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 通过用户名称查询信息 + * + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByUserName(String userName, LoginUser user) + { + if (StringUtils.equals(userName, user.getUsername())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 通过登录地址/用户名称查询信息 + * + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user) + { + if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 设置在线用户信息 + * + * @param user 用户信息 + * @return 在线用户 + */ + @Override + public SysUserOnline loginUserToUserOnline(LoginUser user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUser())) + { + return null; + } + SysUserOnline sysUserOnline = new SysUserOnline(); + sysUserOnline.setTokenId(user.getToken()); + sysUserOnline.setUserName(user.getUsername()); + sysUserOnline.setIpaddr(user.getIpaddr()); + sysUserOnline.setLoginLocation(user.getLoginLocation()); + sysUserOnline.setBrowser(user.getBrowser()); + sysUserOnline.setOs(user.getOs()); + sysUserOnline.setLoginTime(user.getLoginTime()); + if (StringUtils.isNotNull(user.getUser().getDept())) + { + sysUserOnline.setDeptName(user.getUser().getDept().getDeptName()); + } + return sysUserOnline; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..f3ecf44 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java @@ -0,0 +1,515 @@ +package com.ruoyi.system.service.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import com.ruoyi.common.annotation.DataScope; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.bean.BeanValidators; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.system.domain.SysPost; +import com.ruoyi.system.domain.SysUserPost; +import com.ruoyi.system.domain.SysUserRole; +import com.ruoyi.system.mapper.SysPostMapper; +import com.ruoyi.system.mapper.SysRoleMapper; +import com.ruoyi.system.mapper.SysUserMapper; +import com.ruoyi.system.mapper.SysUserPostMapper; +import com.ruoyi.system.mapper.SysUserRoleMapper; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysUserService; + +import jakarta.validation.Validator; + +/** + * 用户 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysUserServiceImpl implements ISysUserService { + private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class); + + @Autowired + private SysUserMapper userMapper; + + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysPostMapper postMapper; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private SysUserPostMapper userPostMapper; + + @Autowired + private ISysConfigService configService; + + @Autowired + protected Validator validator; + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUserList(SysUser user) { + return userMapper.selectUserList(user); + } + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectAllocatedList(SysUser user) { + return userMapper.selectAllocatedList(user); + } + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUnallocatedList(SysUser user) { + return userMapper.selectUnallocatedList(user); + } + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByUserName(String userName) { + return userMapper.selectUserByUserName(userName); + } + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + @Override + public SysUser selectUserById(Long userId) { + return userMapper.selectUserById(userId); + } + + /** + * 通过手机号查询用户 + * + * @param phone 用户ID + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByPhone(String phone) { + return userMapper.selectUserByPhone(phone); + } + + /** + * 通过邮箱查询用户 + * + * @param email 用户名 + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByEmail(String email){ + return userMapper.selectUserByEmail(email); + } + + /** + * 查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserRoleGroup(String userName) { + List list = roleMapper.selectRolesByUserName(userName); + if (CollectionUtils.isEmpty(list)) { + return StringUtils.EMPTY; + } + return list.stream().map(SysRole::getRoleName).collect(Collectors.joining(",")); + } + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserPostGroup(String userName) { + List list = postMapper.selectPostsByUserName(userName); + if (CollectionUtils.isEmpty(list)) { + return StringUtils.EMPTY; + } + return list.stream().map(SysPost::getPostName).collect(Collectors.joining(",")); + } + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean checkUserNameUnique(SysUser user) { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkUserNameUnique(user.getUserName()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public boolean checkPhoneUnique(SysUser user) { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkPhoneUnique(user.getPhonenumber()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public boolean checkEmailUnique(SysUser user) { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkEmailUnique(user.getEmail()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验用户是否允许操作 + * + * @param user 用户信息 + */ + @Override + public void checkUserAllowed(SysUser user) { + if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin()) { + throw new ServiceException("不允许操作超级管理员用户"); + } + } + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + @Override + public void checkUserDataScope(Long userId) { + if (!SysUser.isAdmin(SecurityUtils.getUserId())) { + SysUser user = new SysUser(); + user.setUserId(userId); + List users = SpringUtils.getAopProxy(this).selectUserList(user); + if (StringUtils.isEmpty(users)) { + throw new ServiceException("没有权限访问用户数据!"); + } + } + } + + /** + * 新增保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int insertUser(SysUser user) { + // 新增用户信息 + int rows = userMapper.insertUser(user); + // 新增用户岗位关联 + insertUserPost(user); + // 新增用户与角色管理 + insertUserRole(user); + return rows; + } + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean registerUser(SysUser user) { + return userMapper.insertUser(user) > 0; + } + + /** + * 修改保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int updateUser(SysUser user) { + Long userId = user.getUserId(); + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 新增用户与角色管理 + insertUserRole(user); + // 删除用户与岗位关联 + userPostMapper.deleteUserPostByUserId(userId); + // 新增用户与岗位管理 + insertUserPost(user); + return userMapper.updateUser(user); + } + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + @Override + @Transactional + public void insertUserAuth(Long userId, Long[] roleIds) { + userRoleMapper.deleteUserRoleByUserId(userId); + insertUserRole(userId, roleIds); + } + + /** + * 修改用户状态 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserStatus(SysUser user) { + return userMapper.updateUser(user); + } + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserProfile(SysUser user) { + return userMapper.updateUser(user); + } + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + @Override + public boolean updateUserAvatar(String userName, String avatar) { + return userMapper.updateUserAvatar(userName, avatar) > 0; + } + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int resetPwd(SysUser user) { + return userMapper.updateUser(user); + } + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + @Override + public int resetUserPwd(String userName, String password) { + return userMapper.resetUserPwd(userName, password); + } + + /** + * 新增用户角色信息 + * + * @param user 用户对象 + */ + public void insertUserRole(SysUser user) { + this.insertUserRole(user.getUserId(), user.getRoleIds()); + } + + /** + * 新增用户岗位信息 + * + * @param user 用户对象 + */ + public void insertUserPost(SysUser user) { + Long[] posts = user.getPostIds(); + if (StringUtils.isNotEmpty(posts)) { + // 新增用户与岗位管理 + List list = new ArrayList(posts.length); + for (Long postId : posts) { + SysUserPost up = new SysUserPost(); + up.setUserId(user.getUserId()); + up.setPostId(postId); + list.add(up); + } + userPostMapper.batchUserPost(list); + } + } + + /** + * 新增用户角色信息 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserRole(Long userId, Long[] roleIds) { + if (StringUtils.isNotEmpty(roleIds)) { + // 新增用户与角色管理 + List list = new ArrayList(roleIds.length); + for (Long roleId : roleIds) { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + userRoleMapper.batchUserRole(list); + } + } + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserById(Long userId) { + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 删除用户与岗位表 + userPostMapper.deleteUserPostByUserId(userId); + return userMapper.deleteUserById(userId); + } + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserByIds(Long[] userIds) { + for (Long userId : userIds) { + checkUserAllowed(new SysUser(userId)); + checkUserDataScope(userId); + } + // 删除用户与角色关联 + userRoleMapper.deleteUserRole(userIds); + // 删除用户与岗位关联 + userPostMapper.deleteUserPost(userIds); + return userMapper.deleteUserByIds(userIds); + } + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @param operName 操作用户 + * @return 结果 + */ + @Override + public String importUser(List userList, Boolean isUpdateSupport, String operName) { + if (StringUtils.isNull(userList) || userList.size() == 0) { + throw new ServiceException("导入用户数据不能为空!"); + } + int successNum = 0; + int failureNum = 0; + StringBuilder successMsg = new StringBuilder(); + StringBuilder failureMsg = new StringBuilder(); + String password = configService.selectConfigByKey("sys.user.initPassword"); + for (SysUser user : userList) { + try { + // 验证是否存在这个用户 + SysUser u = userMapper.selectUserByUserName(user.getUserName()); + if (StringUtils.isNull(u)) { + BeanValidators.validateWithException(validator, user); + user.setPassword(SecurityUtils.encryptPassword(password)); + user.setCreateBy(operName); + userMapper.insertUser(user); + successNum++; + successMsg.append("
" + successNum + "、账号 " + user.getUserName() + " 导入成功"); + } else if (isUpdateSupport) { + BeanValidators.validateWithException(validator, user); + checkUserAllowed(u); + checkUserDataScope(u.getUserId()); + user.setUserId(u.getUserId()); + user.setUpdateBy(operName); + userMapper.updateUser(user); + successNum++; + successMsg.append("
" + successNum + "、账号 " + user.getUserName() + " 更新成功"); + } else { + failureNum++; + failureMsg.append("
" + failureNum + "、账号 " + user.getUserName() + " 已存在"); + } + } catch (Exception e) { + failureNum++; + String msg = "
" + failureNum + "、账号 " + user.getUserName() + " 导入失败:"; + failureMsg.append(msg + e.getMessage()); + log.error(msg, e); + } + } + if (failureNum > 0) { + failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); + throw new ServiceException(failureMsg.toString()); + } else { + successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); + } + return successMsg.toString(); + } +} diff --git a/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml new file mode 100644 index 0000000..9764c47 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark + from sys_config + + + + + + + and config_id = #{configId} + + + and config_key = #{configKey} + + + + + + + + + + + + + + insert into sys_config ( + config_name, + config_key, + config_value, + config_type, + create_by, + remark, + create_time + )values( + #{configName}, + #{configKey}, + #{configValue}, + #{configType}, + #{createBy}, + #{remark}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + + update sys_config + + config_name = #{configName}, + config_key = #{configKey}, + config_value = #{configValue}, + config_type = #{configType}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = + + CURRENT_TIMESTAMP + + + sysdate() + + + + where config_id = #{configId} + + + + delete from sys_config where config_id = #{configId} + + + + delete from sys_config where config_id in + + #{configId} + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml new file mode 100644 index 0000000..53dfcdd --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time + from sys_dept d + + + + + + + + + + + + + + + + + + + + insert into sys_dept( + dept_id, + parent_id, + dept_name, + ancestors, + order_num, + leader, + phone, + email, + status, + create_by, + create_time + )values( + #{deptId}, + #{parentId}, + #{deptName}, + #{ancestors}, + #{orderNum}, + #{leader}, + #{phone}, + #{email}, + #{status}, + #{createBy}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + + update sys_dept + + parent_id = #{parentId}, + dept_name = #{deptName}, + ancestors = #{ancestors}, + order_num = #{orderNum}, + leader = #{leader}, + phone = #{phone}, + email = #{email}, + status = #{status}, + update_by = #{updateBy}, + update_time = + + CURRENT_TIMESTAMP + + + sysdate() + + + + where dept_id = #{deptId} + + + + update sys_dept set ancestors = + + when #{item.deptId} then #{item.ancestors} + + where dept_id in + + #{item.deptId} + + + + + update sys_dept set status = '0' where dept_id in + + #{deptId} + + + + + update sys_dept set del_flag = '2' where dept_id = #{deptId} + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml new file mode 100644 index 0000000..86dae42 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + select dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark + from sys_dict_data + + + + + + + + + + + + + + delete from sys_dict_data where dict_code = #{dictCode} + + + + delete from sys_dict_data where dict_code in + + #{dictCode} + + + + + update sys_dict_data + + dict_sort = #{dictSort}, + dict_label = #{dictLabel}, + dict_value = #{dictValue}, + dict_type = #{dictType}, + css_class = #{cssClass}, + list_class = #{listClass}, + is_default = #{isDefault}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = + + CURRENT_TIMESTAMP + + + sysdate() + + + + where dict_code = #{dictCode} + + + + update sys_dict_data set dict_type = #{newDictType} where dict_type = #{oldDictType} + + + + insert into sys_dict_data( + dict_sort, + dict_label, + dict_value, + dict_type, + css_class, + list_class, + is_default, + status, + remark, + create_by, + create_time + )values( + #{dictSort}, + #{dictLabel}, + #{dictValue}, + #{dictType}, + #{cssClass}, + #{listClass}, + #{isDefault}, + #{status}, + #{remark}, + #{createBy}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml new file mode 100644 index 0000000..3c96fc8 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + select dict_id, dict_name, dict_type, status, create_by, create_time, remark + from sys_dict_type + + + + + + + + + + + + + + delete from sys_dict_type where dict_id = #{dictId} + + + + delete from sys_dict_type where dict_id in + + #{dictId} + + + + + update sys_dict_type + + dict_name = #{dictName}, + dict_type = #{dictType}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = + + CURRENT_TIMESTAMP + + + sysdate() + + + + where dict_id = #{dictId} + + + + insert into sys_dict_type( + dict_name, + dict_type, + status, + remark, + create_by, + create_time + )values( + #{dictName}, + #{dictType}, + #{status}, + #{remark}, + #{createBy}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml new file mode 100644 index 0000000..81a89d2 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + insert into sys_logininfor (user_name, status, ipaddr, login_location, browser, os, msg, login_time) + values (#{userName}, #{status}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{msg}, + + CURRENT_TIMESTAMP + + + sysdate() + + ) + + + + + + delete from sys_logininfor where info_id in + + #{infoId} + + + + + truncate table sys_logininfor + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml new file mode 100644 index 0000000..8569a69 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select menu_id, menu_name, parent_id, order_num, path, component, sys_menu.query, route_name, is_frame, is_cache, menu_type, visible, status, COALESCE(perms,'') as perms, icon, create_time + from sys_menu + + + + + + + + + + + + + + + + + + + + + + + + + + update sys_menu + + menu_name = #{menuName}, + parent_id = #{parentId}, + order_num = #{orderNum}, + path = #{path}, + component = #{component}, + query = #{query}, + route_name = #{routeName}, + is_frame = #{isFrame}, + is_cache = #{isCache}, + menu_type = #{menuType}, + visible = #{visible}, + status = #{status}, + perms = #{perms}, + icon = #{icon}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = + + CURRENT_TIMESTAMP + + + sysdate() + + + + where menu_id = #{menuId} + + + + insert into sys_menu( + menu_id, + parent_id, + menu_name, + order_num, + path, + component, + query, + route_name, + is_frame, + is_cache, + menu_type, + visible, + status, + perms, + icon, + remark, + create_by, + create_time + )values( + #{menuId}, + #{parentId}, + #{menuName}, + #{orderNum}, + #{path}, + #{component}, + #{query}, + #{routeName}, + #{isFrame}, + #{isCache}, + #{menuType}, + #{visible}, + #{status}, + #{perms}, + #{icon}, + #{remark}, + #{createBy}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + + delete from sys_menu where menu_id = #{menuId} + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml new file mode 100644 index 0000000..b3e4196 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + select notice_id, notice_title, notice_type, cast(notice_content as char) as notice_content, status, create_by, create_time, update_by, update_time, remark + from sys_notice + + + + + + + + insert into sys_notice ( + notice_title, + notice_type, + notice_content, + status, + remark, + create_by, + create_time + )values( + #{noticeTitle}, + #{noticeType}, + #{noticeContent}, + #{status}, + #{remark}, + #{createBy}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + + update sys_notice + + notice_title = #{noticeTitle}, + notice_type = #{noticeType}, + notice_content = #{noticeContent}, + status = #{status}, + update_by = #{updateBy}, + update_time = + + CURRENT_TIMESTAMP + + + sysdate() + + + + where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id in + + #{noticeId} + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml new file mode 100644 index 0000000..0630ac9 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, oper_time, cost_time + from sys_oper_log + + + + insert into sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, cost_time, oper_time) + values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, + + CURRENT_TIMESTAMP + + + sysdate() + + ) + + + + + + delete from sys_oper_log where oper_id in + + #{operId} + + + + + + + truncate table sys_oper_log + + + + + + AND title like concat('%', #{title}, '%') + + + AND business_type = #{businessType} + + + AND request_method = #{requestMethod} + + + AND business_type in + + #{businessType} + + + + AND status = #{status} + + + AND oper_time >= #{params.beginTime} + + + AND oper_time <= #{params.endTime} + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml new file mode 100644 index 0000000..cc9b4f2 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark + from sys_post + + + + + + + + + + + + + + + + + + update sys_post + + post_code = #{postCode}, + post_name = #{postName}, + post_sort = #{postSort}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = + + CURRENT_TIMESTAMP + + + sysdate() + + + + where post_id = #{postId} + + + + insert into sys_post( + post_id, + post_code, + post_name, + post_sort, + status, + remark, + create_by, + create_time + )values( + #{postId}, + #{postCode}, + #{postName}, + #{postSort}, + #{status}, + #{remark}, + #{createBy}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + + delete from sys_post where post_id = #{postId} + + + + delete from sys_post where post_id in + + #{postId} + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml new file mode 100644 index 0000000..7c4139b --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_role_dept where role_id=#{roleId} + + + + + + delete from sys_role_dept where role_id in + + #{roleId} + + + + + insert into sys_role_dept(role_id, dept_id) values + + (#{item.roleId},#{item.deptId}) + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml new file mode 100644 index 0000000..56e326a --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly, + r.status, r.del_flag, r.create_time, r.remark + from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id + left join sys_user u on u.user_id = ur.user_id + left join sys_dept d on u.dept_id = d.dept_id + + + + + + + + + + + + + + + + + + + + insert into sys_role( + role_id, + role_name, + role_key, + role_sort, + data_scope, + menu_check_strictly, + dept_check_strictly, + status, + remark, + create_by, + create_time + )values( + #{roleId}, + #{roleName}, + #{roleKey}, + #{roleSort}, + #{dataScope}, + #{menuCheckStrictly}, + #{deptCheckStrictly}, + #{status}, + #{remark}, + #{createBy}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + + update sys_role + + role_name = #{roleName}, + role_key = #{roleKey}, + role_sort = #{roleSort}, + data_scope = #{dataScope}, + menu_check_strictly = #{menuCheckStrictly}, + dept_check_strictly = #{deptCheckStrictly}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = + + CURRENT_TIMESTAMP + + + sysdate() + + + + where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id in + + #{roleId} + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml new file mode 100644 index 0000000..cb60a85 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + delete from sys_role_menu where role_id=#{roleId} + + + + delete from sys_role_menu where role_id in + + #{roleId} + + + + + insert into sys_role_menu(role_id, menu_id) values + + (#{item.roleId},#{item.menuId}) + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUnitConvertMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUnitConvertMapper.xml new file mode 100644 index 0000000..4b36bb9 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysUnitConvertMapper.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + select + suc.id, + suc.unit_type, + suc.unit_name, + suc.base_unit, + suc.conversion_factor, + suc.unit_type_name, + suc.status, + suc.unit_order + from sys_unit_convert suc + + + + + + + + insert into sys_unit_convert + + unit_type, + unit_name, + base_unit, + conversion_factor, + unit_type_name, + status, + unit_order, + + + #{unitType}, + #{unitName}, + #{baseUnit}, + #{conversionFactor}, + #{unitTypeName}, + #{status}, + #{unitOrder}, + + + + + + + update sys_unit_convert + + unit_type = #{unitType}, + unit_name = #{unitName}, + base_unit = #{baseUnit}, + conversion_factor = #{conversionFactor}, + unit_type_name = #{unitTypeName}, + status = #{status}, + unit_order = #{unitOrder}, + + where sys_unit_convert.id = #{id} + + + + delete from sys_unit_convert where id = #{id} + + + + delete from sys_unit_convert where id in + + #{id} + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml new file mode 100644 index 0000000..b369f2e --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -0,0 +1,266 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date,u.pwd_update_date, u.create_by, u.create_time, u.remark, + d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, + r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id + + + + + + + + + + + + + + + + + + + + + + + + insert into sys_user( + user_id, + dept_id, + user_name, + nick_name, + email, + avatar, + phonenumber, + sex, + password, + status, + create_by, + remark, + create_time + )values( + #{userId}, + #{deptId}, + #{userName}, + #{nickName}, + #{email}, + #{avatar}, + #{phonenumber}, + #{sex}, + #{password}, + #{status}, + #{createBy}, + #{remark}, + + + CURRENT_TIMESTAMP + + + sysdate() + + + ) + + + + update sys_user + + dept_id = #{deptId}, + user_name = #{userName}, + nick_name = #{nickName}, + email = #{email}, + phonenumber = #{phonenumber}, + sex = #{sex}, + avatar = #{avatar}, + password = #{password}, + status = #{status}, + login_ip = #{loginIp}, + login_date = #{loginDate}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = + + CURRENT_TIMESTAMP + + + sysdate() + + + + where user_id = #{userId} + + + + update sys_user set status = #{status} where user_id = #{userId} + + + + update sys_user set avatar = #{avatar} where user_name = #{userName} + + + + update sys_user set pwd_update_date = + + CURRENT_TIMESTAMP + + + sysdate() + + , password = #{password} where user_name = #{userName} + + + + update sys_user set del_flag = '2' where user_id = #{userId} + + + + update sys_user set del_flag = '2' where user_id in + + #{userId} + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml new file mode 100644 index 0000000..2b90bc4 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_user_post where user_id=#{userId} + + + + + + delete from sys_user_post where user_id in + + #{userId} + + + + + insert into sys_user_post(user_id, post_id) values + + (#{item.userId},#{item.postId}) + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml new file mode 100644 index 0000000..dd72689 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + delete from sys_user_role where user_id=#{userId} + + + + + + delete from sys_user_role where user_id in + + #{userId} + + + + + insert into sys_user_role(user_id, role_id) values + + (#{item.userId},#{item.roleId}) + + + + + delete from sys_user_role where user_id=#{userId} and role_id=#{roleId} + + + + delete from sys_user_role where role_id=#{roleId} and user_id in + + #{userId} + + + \ No newline at end of file diff --git a/ry.bat b/ry.bat new file mode 100644 index 0000000..ac1e437 --- /dev/null +++ b/ry.bat @@ -0,0 +1,67 @@ +@echo off + +rem jarƽĿ¼ +set AppName=ruoyi-admin.jar + +rem JVM +set JVM_OPTS="-Dname=%AppName% -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC" + + +ECHO. + ECHO. [1] %AppName% + ECHO. [2] ر%AppName% + ECHO. [3] %AppName% + ECHO. [4] ״̬ %AppName% + ECHO. [5] +ECHO. + +ECHO.ѡĿ: +set /p ID= + IF "%id%"=="1" GOTO start + IF "%id%"=="2" GOTO stop + IF "%id%"=="3" GOTO restart + IF "%id%"=="4" GOTO status + IF "%id%"=="5" EXIT +PAUSE +:start + for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( + set pid=%%a + set image_name=%%b + ) + if defined pid ( + echo %%is running + PAUSE + ) + +start javaw %JVM_OPTS% -jar %AppName% + +echo starting +echo Start %AppName% success... +goto:eof + +rem stopͨjpspid +:stop + for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( + set pid=%%a + set image_name=%%b + ) + if not defined pid (echo process %AppName% does not exists) else ( + echo prepare to kill %image_name% + echo start kill %pid% ... + rem ݽIDkill + taskkill /f /pid %pid% + ) +goto:eof +:restart + call :stop + call :start +goto:eof +:status + for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( + set pid=%%a + set image_name=%%b + ) + if not defined pid (echo process %AppName% is dead ) else ( + echo %image_name% is running + ) +goto:eof diff --git a/ry.sh b/ry.sh new file mode 100644 index 0000000..d6a9cf3 --- /dev/null +++ b/ry.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# ./ry.sh start 启动 stop 停止 restart 重启 status 状态 +AppName=ruoyi-admin.jar + +# JVM参数 +JVM_OPTS="-Dname=$AppName -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC" +APP_HOME=`pwd` +LOG_PATH=$APP_HOME/logs/$AppName.log + +if [ "$1" = "" ]; +then + echo -e "\033[0;31m 未输入操作名 \033[0m \033[0;34m {start|stop|restart|status} \033[0m" + exit 1 +fi + +if [ "$AppName" = "" ]; +then + echo -e "\033[0;31m 未输入应用名 \033[0m" + exit 1 +fi + +function start() +{ + PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` + + if [ x"$PID" != x"" ]; then + echo "$AppName is running..." + else + nohup java $JVM_OPTS -jar $AppName > /dev/null 2>&1 & + echo "Start $AppName success..." + fi +} + +function stop() +{ + echo "Stop $AppName" + + PID="" + query(){ + PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` + } + + query + if [ x"$PID" != x"" ]; then + kill -TERM $PID + echo "$AppName (pid:$PID) exiting..." + while [ x"$PID" != x"" ] + do + sleep 1 + query + done + echo "$AppName exited." + else + echo "$AppName already stopped." + fi +} + +function restart() +{ + stop + sleep 2 + start +} + +function status() +{ + PID=`ps -ef |grep java|grep $AppName|grep -v grep|wc -l` + if [ $PID != 0 ];then + echo "$AppName is running..." + else + echo "$AppName is not running..." + fi +} + +case $1 in + start) + start;; + stop) + stop;; + restart) + restart;; + status) + status;; + *) + +esac diff --git a/sql/mysql/auth.sql b/sql/mysql/auth.sql new file mode 100644 index 0000000..6b9848d --- /dev/null +++ b/sql/mysql/auth.sql @@ -0,0 +1,46 @@ +DROP TABLE IF EXISTS oauth_user; +CREATE TABLE oauth_user ( + id INT NOT NULL AUTO_INCREMENT COMMENT '主键', + uuid VARCHAR(255) NOT NULL COMMENT '第三方系统的唯一ID,详细解释请参考:名词解释', + user_id bigint(20) NOT NULL comment '用户ID', + source VARCHAR(255) NOT NULL COMMENT '第三方用户来源,可选值:GITHUB、GITEE、QQ,更多请参考:AuthDefaultSource.java(opens new window)', + access_token VARCHAR(255) NOT NULL COMMENT '用户的授权令牌', + expire_in INT COMMENT '第三方用户的授权令牌的有效期,部分平台可能没有', + refresh_token VARCHAR(255) COMMENT '刷新令牌,部分平台可能没有', + open_id VARCHAR(255) COMMENT '第三方用户的 open id,部分平台可能没有', + uid VARCHAR(255) COMMENT '第三方用户的 ID,部分平台可能没有', + access_code VARCHAR(255) COMMENT '个别平台的授权信息,部分平台可能没有', + union_id VARCHAR(255) COMMENT '第三方用户的 union id,部分平台可能没有', + scope VARCHAR(255) COMMENT '第三方用户授予的权限,部分平台可能没有', + token_type VARCHAR(255) COMMENT '个别平台的授权信息,部分平台可能没有', + id_token VARCHAR(255) COMMENT 'id token,部分平台可能没有', + mac_algorithm VARCHAR(255) COMMENT '小米平台用户的附带属性,部分平台可能没有', + mac_key VARCHAR(255) COMMENT '小米平台用户的附带属性,部分平台可能没有', + code VARCHAR(255) COMMENT '用户的授权code,部分平台可能没有', + oauth_token VARCHAR(255) COMMENT 'Twitter平台用户的附带属性,部分平台可能没有', + oauth_token_secret VARCHAR(255) COMMENT 'Twitter平台用户的附带属性,部分平台可能没有', + PRIMARY KEY (`id`) +) ENGINE = InnoDB COMMENT = '第三方登录'; + +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('第三方认证', '1', '1', 'oauth', 'system/oauth/index',"", 1, 0, 'C', '0', '0', 'system:oauth:list', 'checkbox', 'admin', sysdate(), '', null, '第三方认证菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('第三方认证查询', @parentId, '1', '#', '','', 1, 0, 'F', '0', '0', 'system:oauth:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('第三方认证新增', @parentId, '2', '#', '','', 1, 0, 'F', '0', '0', 'system:oauth:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('第三方认证修改', @parentId, '3', '#', '','', 1, 0, 'F', '0', '0', 'system:oauth:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('第三方认证删除', @parentId, '4', '#', '','', 1, 0, 'F', '0', '0', 'system:oauth:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('第三方认证导出', @parentId, '5', '#', '','', 1, 0, 'F', '0', '0', 'system:oauth:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/sql/mysql/create_database.sql b/sql/mysql/create_database.sql new file mode 100644 index 0000000..6592849 --- /dev/null +++ b/sql/mysql/create_database.sql @@ -0,0 +1 @@ +CREATE DATABASE IF NOT EXISTS ry CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; \ No newline at end of file diff --git a/sql/mysql/file.sql b/sql/mysql/file.sql new file mode 100644 index 0000000..3d9abd5 --- /dev/null +++ b/sql/mysql/file.sql @@ -0,0 +1,42 @@ +DROP TABLE IF EXISTS `sys_file_info`; +CREATE TABLE sys_file_info ( + file_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '文件主键', + file_name VARCHAR(255) NOT NULL COMMENT '原始文件名', + file_path VARCHAR(500) NOT NULL COMMENT '统一逻辑路径(/开头)', + storage_type VARCHAR(32) NOT NULL COMMENT '存储类型(local/minio/oss)', + file_type VARCHAR(50) COMMENT '文件类型/后缀', + file_size BIGINT COMMENT '文件大小(字节)', + md5 VARCHAR(64) COMMENT '文件MD5', + create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', + create_time DATETIME DEFAULT NULL COMMENT '创建时间', + update_by VARCHAR(64) DEFAULT '' COMMENT '更新者', + update_time DATETIME DEFAULT NULL COMMENT '更新时间', + remark VARCHAR(255) DEFAULT NULL COMMENT '备注', + del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + PRIMARY KEY (file_id), + -- UNIQUE KEY uk_file_path (file_path), + UNIQUE KEY uk_md5 (md5) +) ENGINE=InnoDB COMMENT='文件信息表'; + +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('文件管理', '1', '1', 'file', 'system/file/index', 1, 0, 'C', '0', '0', 'system:file:list', 'excel', 'admin', sysdate(), '', null, '文件管理菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('文件查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', 'system:file:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('文件新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', 'system:file:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('文件修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', 'system:file:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('文件删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', 'system:file:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('文件导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', 'system:file:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/sql/mysql/flowable.sql b/sql/mysql/flowable.sql new file mode 100644 index 0000000..8bde3a0 --- /dev/null +++ b/sql/mysql/flowable.sql @@ -0,0 +1,100 @@ +-- sys_deploy_form definition +DROP TABLE IF EXISTS `sys_deploy_form`; +CREATE TABLE `sys_deploy_form` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `form_id` bigint(20) DEFAULT NULL COMMENT '表单主键', + `deploy_id` varchar(50) DEFAULT NULL COMMENT '流程实例主键', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=9623 COMMENT='流程实例关联表单'; + +-- sys_expression definition +DROP TABLE IF EXISTS `sys_expression`; +CREATE TABLE `sys_expression` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '表单主键', + `name` varchar(50) DEFAULT NULL COMMENT '表达式名称', + `expression` varchar(255) DEFAULT NULL COMMENT '表达式内容', + `data_type` varchar(255) DEFAULT NULL COMMENT '表达式类型', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `create_by` bigint(20) DEFAULT NULL COMMENT '创建人员', + `update_by` bigint(20) DEFAULT NULL COMMENT '更新人员', + `status` tinyint(2) DEFAULT '0' COMMENT '状态', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=69 COMMENT='流程表达式'; + + +-- sys_listener definition +DROP TABLE IF EXISTS `sys_listener`; +CREATE TABLE `sys_listener` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '表单主键', + `name` varchar(128) DEFAULT NULL COMMENT '名称', + `type` char(2) DEFAULT NULL COMMENT '监听类型', + `event_type` varchar(32) DEFAULT NULL COMMENT '事件类型', + `value_type` varchar(32) DEFAULT NULL COMMENT '值类型', + `value` varchar(255) DEFAULT NULL COMMENT '执行内容', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `create_by` bigint(20) DEFAULT NULL COMMENT '创建人员', + `update_by` bigint(20) DEFAULT NULL COMMENT '更新人员', + `status` tinyint(2) DEFAULT '0' COMMENT '状态', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=3 COMMENT='流程监听'; + + +-- 流程相关菜单 +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES('流程管理', 0, 6, 'flowable', NULL, NULL, NULL, 1, 0, 'M', '0', '0', '', 'cascader', 'tony', '2021-03-25 11:35:09', 'admin', '2022-12-29 17:39:22', ''); +SELECT @flowable := LAST_INSERT_ID(); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程定义', @flowable, 2, 'definition', 'flowable/definition/index', NULL, NULL, 1, 0, 'C', '0', '0', '', 'job', 'tony', '2021-03-25 13:53:55', 'admin', '2022-12-29 17:40:39', ''); + +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '任务管理', 0, 7, 'task', NULL, NULL, NULL, 1, 0, 'M', '0', '0', '', 'dict', 'tony', '2021-03-26 10:53:10', 'admin', '2021-03-29 09:37:40', ''); +SELECT @task := LAST_INSERT_ID(); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '待办任务', @task, 2, 'todo', 'flowable/task/todo/index', NULL, NULL, 1, 1, 'C', '0', '0', '', 'cascader', 'admin', '2021-03-26 10:55:52', 'admin', '2021-03-30 09:26:36', ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '已办任务', @task, 3, 'finished', 'flowable/task/finished/index', NULL, NULL, 1, 1, 'C', '0', '0', '', 'time-range', 'admin', '2021-03-26 10:57:54', 'admin', '2021-03-30 09:26:50', ''); + + +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '已发任务', @task, 1, 'process', 'flowable/task/myProcess/index', NULL, NULL, 1, 1, 'C', '0', '0', '', 'guide', 'admin', '2021-03-30 09:26:23', 'admin', '2022-12-12 09:58:07', ''); +SELECT @process := LAST_INSERT_ID(); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '新增', @process, 1, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'system:deployment:add', '#', 'admin', '2021-07-07 14:25:22', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '编辑', @process, 2, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'system:deployment:edit', '#', 'admin', '2021-07-07 14:25:47', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '删除', @process, 3, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'system:deployment:remove', '#', 'admin', '2021-07-07 14:26:02', '', NULL, ''); + +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程表达式', @flowable, 3, 'expression', 'flowable/expression/index', NULL, NULL, 1, 1, 'C', '0', '0', 'system:expression:list', 'list', 'admin', '2022-12-12 17:12:19', 'admin', '2022-12-12 17:13:44', '流程达式菜单'); +SELECT @expression := LAST_INSERT_ID(); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程达式查询', @expression, 1, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:query', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程达式新增', @expression, 2, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:add', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程达式修改', @expression, 3, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:edit', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程达式删除', @expression, 4, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:remove', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程达式导出', @expression, 5, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:export', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); + + +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程监听', @flowable, 4, 'listener', 'flowable/listener/index', NULL, NULL, 1, 0, 'C', '0', '0', 'system:listener:list', 'monitor', 'admin', '2022-12-25 11:44:16', 'admin', '2022-12-29 08:59:21', '流程监听菜单'); +SELECT @listener := LAST_INSERT_ID(); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程监听查询', @listener, 1, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:query', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程监听新增', @listener, 2, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:add', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程监听修改', @listener, 3, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:edit', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程监听删除', @listener, 4, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:remove', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, `path`, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES( '流程监听导出', @listener, 5, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:export', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); + +-- 流程相关字段表信息 + +INSERT INTO sys_dict_type ( dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES( '表达式类型', 'exp_data_type', '0', 'admin', '2024-03-12 09:03:02', '', NULL, NULL); +INSERT INTO sys_dict_type ( dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES( '监听类型', 'sys_listener_type', '0', 'admin', '2022-12-18 22:03:07', '', NULL, NULL); +INSERT INTO sys_dict_type ( dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES( '监听值类型', 'sys_listener_value_type', '0', 'admin', '2022-12-18 22:03:39', '', NULL, NULL); +INSERT INTO sys_dict_type ( dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES( '监听属性', 'sys_listener_event_type', '0', 'admin', '2022-12-18 22:04:29', '', NULL, NULL); +INSERT INTO sys_dict_type ( dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES( '流程分类', 'sys_process_category', '0', 'admin', '2024-03-12 09:08:18', '', NULL, NULL); + + +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES( 0, '系统指定', 'fixed', 'exp_data_type', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:04:46', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES( 0, '动态选择', 'dynamic', 'exp_data_type', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:05:02', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES( 0, '任务监听', '1', 'sys_listener_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:47:26', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES( 2, '执行监听', '2', 'sys_listener_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:47:37', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES( 0, 'JAVA类', 'classListener', 'sys_listener_value_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:48:55', 'admin', '2024-09-05 21:38:02', NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES( 0, '表达式', 'expressionListener', 'sys_listener_value_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:49:05', 'admin', '2024-09-05 21:38:10', NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES( 0, '代理表达式', 'delegateExpressionListener', 'sys_listener_value_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:49:16', 'admin', '2024-09-05 21:38:16', NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES( 0, '请假', 'leave', 'sys_process_category', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:08:42', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES( 0, '报销', 'expense', 'sys_process_category', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:09:02', '', NULL, NULL); diff --git a/sql/mysql/form.sql b/sql/mysql/form.sql new file mode 100644 index 0000000..b70890b --- /dev/null +++ b/sql/mysql/form.sql @@ -0,0 +1,84 @@ +DROP TABLE IF EXISTS `form_template`; + +CREATE TABLE form_template ( + form_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '表单ID', + form_name VARCHAR(100) NOT NULL COMMENT '表单名称', + form_schema JSON COMMENT '表单JSON Schema(vForm配置)', + form_version VARCHAR(10) DEFAULT '1.0.0' COMMENT '表单版本(语义化版本)', + form_status VARCHAR(2) DEFAULT '0' COMMENT '发布状态(0: 草稿, 1: 已发布, 2: 已停用)', + create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', + create_time DATETIME DEFAULT NULL COMMENT '创建时间', + update_by VARCHAR(64) DEFAULT '' COMMENT '更新者', + update_time DATETIME DEFAULT NULL COMMENT '更新时间', + remark VARCHAR(255) DEFAULT NULL COMMENT '备注', + del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)' +) ENGINE = InnoDB COMMENT '表单模板表'; + +DROP TABLE IF EXISTS `form_data`; + +CREATE TABLE form_data ( + data_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '数据ID', + form_id BIGINT NOT NULL COMMENT '关联的表单ID', + form_version VARCHAR(10) COMMENT '表单版本(与模板表版本一致)', + data_content JSON NOT NULL COMMENT '表单数据内容(JSON格式)', + status VARCHAR(20) NOT NULL DEFAULT 'draft' COMMENT '数据状态(draft, submitted, approved, rejected)', + create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', + create_time DATETIME DEFAULT NULL COMMENT '创建时间', + update_by VARCHAR(64) DEFAULT '' COMMENT '更新者', + update_time DATETIME DEFAULT NULL COMMENT '更新时间', + remark VARCHAR(255) DEFAULT NULL COMMENT '备注', + del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)' +) ENGINE = InnoDB COMMENT '表单数据表'; + +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query,route_name, is_frame, is_cache, menu_type, visible, `status`, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ('表单管理', 0, 4, 'formManagement', NULL, NULL, '',1, 0, 'M', '0', '0', NULL, 'form', 'admin', '2024-02-15 22:40:23', '', NULL, ''); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +select @fileParentId := @parentId; + +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('表单模板', @fileParentId, '1', 'formtemplate', 'form/template/index', 1, 0, 'C', '0', '0', 'form:template:list', '#', 'admin', sysdate(), '', null, '表单模板菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('表单模板查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', 'form:template:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('表单模板新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', 'form:template:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('表单模板修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', 'form:template:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('表单模板删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', 'form:template:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('表单模板导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', 'form:template:export', '#', 'admin', sysdate(), '', null, ''); + +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('表单数据', @fileParentId, '1', 'formdata', 'form/data/index', 1, 0, 'C', '0', '0', 'form:data:list', '#', 'admin', sysdate(), '', null, '表单数据菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('表单数据查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', 'form:data:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('表单数据新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', 'form:data:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('表单数据修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', 'form:data:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('表单数据删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', 'form:data:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('表单数据导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', 'form:data:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/sql/mysql/gen.sql b/sql/mysql/gen.sql new file mode 100644 index 0000000..32de084 --- /dev/null +++ b/sql/mysql/gen.sql @@ -0,0 +1,95 @@ + +-- ---------------------------- +-- 18、代码生成业务表 +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table`; +CREATE TABLE `gen_table` ( + `table_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `table_name` varchar(200) NOT NULL COMMENT '表名称', + `table_alias` varchar(200) NOT NULL COMMENT '表别名', + `table_comment` varchar(500) DEFAULT '' COMMENT '表描述', + `have_sub_column` char(1) DEFAULT '0' COMMENT '是否含有关联字段', + `sub_table_name` varchar(64) DEFAULT NULL COMMENT '关联子表的表名', + `sub_table_fk_name` varchar(64) DEFAULT NULL COMMENT '子表关联的外键名', + `class_name` varchar(100) DEFAULT '' COMMENT '实体类名称', + `tpl_category` varchar(200) DEFAULT 'crud' COMMENT '使用的模板(crud单表操作 tree树表操作)', + `tpl_web_type` varchar(200) DEFAULT 'element-plus' COMMENT '使用的模板类型', + `package_name` varchar(100) DEFAULT NULL COMMENT '生成包路径', + `module_name` varchar(30) DEFAULT NULL COMMENT '生成模块名', + `business_name` varchar(30) DEFAULT NULL COMMENT '生成业务名', + `function_name` varchar(50) DEFAULT NULL COMMENT '生成功能名', + `function_author` varchar(50) DEFAULT NULL COMMENT '生成功能作者', + `gen_type` char(1) DEFAULT '0' COMMENT '生成代码方式(0zip压缩包 1自定义路径)', + `gen_path` varchar(200) DEFAULT '/' COMMENT '生成路径(不填默认项目路径)', + `options` varchar(1000) DEFAULT NULL COMMENT '其它生成选项', + `create_by` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`table_id`) +) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '代码生成业务表'; + + +-- ---------------------------- +-- 19、代码生成业务表字段 +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table_column`; +CREATE TABLE `gen_table_column` ( + `column_id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `table_id` varchar(64) DEFAULT NULL COMMENT '归属表编号', + `column_name` varchar(200) DEFAULT NULL COMMENT '列名称', + `column_comment` varchar(500) DEFAULT NULL COMMENT '列描述', + `column_type` varchar(100) DEFAULT NULL COMMENT '列类型', + `java_type` varchar(500) DEFAULT NULL COMMENT 'JAVA类型', + `java_field` varchar(200) DEFAULT NULL COMMENT 'JAVA字段名', + `is_pk` char(1) DEFAULT NULL COMMENT '是否主键(1是)', + `is_increment` char(1) DEFAULT NULL COMMENT '是否自增(1是)', + `is_required` char(1) DEFAULT NULL COMMENT '是否必填(1是)', + `is_insert` char(1) DEFAULT NULL COMMENT '是否为插入字段(1是)', + `is_edit` char(1) DEFAULT NULL COMMENT '是否编辑字段(1是)', + `is_list` char(1) DEFAULT NULL COMMENT '是否列表字段(1是)', + `is_query` char(1) DEFAULT NULL COMMENT '是否查询字段(1是)', + `query_type` varchar(200) DEFAULT 'EQ' COMMENT '查询方式(等于、不等于、大于、小于、范围)', + `html_type` varchar(200) DEFAULT NULL COMMENT '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', + `dict_type` varchar(200) DEFAULT '' COMMENT '字典类型', + `sort` int DEFAULT NULL COMMENT '排序', + `sub_column_table_name` varchar(200) DEFAULT NULL COMMENT '关联表名称', + `sub_column_fk_name` varchar(200) DEFAULT NULL COMMENT '关联字段名称', + `sub_column_name` varchar(200) DEFAULT NULL COMMENT '映射字段名称', + `sub_column_java_field` varchar(200) DEFAULT NULL COMMENT '映射字段JAVA字段名', + `sub_column_java_type` varchar(255) DEFAULT NULL COMMENT '映射字段JAVA类型', + `create_by` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`column_id`) +) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '代码生成业务表字段'; + + + +DROP TABLE IF EXISTS `gen_join_table`; +CREATE TABLE `gen_join_table` ( + `table_id` bigint NOT NULL COMMENT '表编号', + `left_table_id` bigint NOT NULL COMMENT '左表名称', + `right_table_id` bigint NOT NULL COMMENT '右表编号', + `left_table_alias` varchar(200) NOT NULL COMMENT '左表别名', + `right_table_alias` varchar(200) NOT NULL COMMENT '右表别名', + `left_table_fk` varchar(200) NOT NULL COMMENT '左表关联键', + `right_table_fk` varchar(200) NOT NULL COMMENT '右表关联键', + `join_type` varchar(200) NOT NULL COMMENT '关联类型', + `join_columns` varchar(500) DEFAULT '' COMMENT '关联字段', + `order_num` varchar(64) NOT NULL COMMENT '序号', + `new_table_id` bigint NOT NULL COMMENT '新表编号', + PRIMARY KEY (`table_id`,`right_table_id`,`left_table_id`) +) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '代码生成关联表'; + + +insert into sys_menu values('116', '代码生成', '3', '2', 'gen', 'tool/gen/index', '', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', sysdate(), '', null, '代码生成菜单'); +-- 代码生成按钮 +insert into sys_menu values('1055', '生成查询', '116', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1056', '生成修改', '116', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1057', '生成删除', '116', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1058', '导入代码', '116', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1059', '预览代码', '116', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1060', '生成代码', '116', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', sysdate(), '', null, ''); diff --git a/sql/mysql/message.sql b/sql/mysql/message.sql new file mode 100644 index 0000000..c710989 --- /dev/null +++ b/sql/mysql/message.sql @@ -0,0 +1,101 @@ +-- ---------------------------- +-- 消息系统 +-- ---------------------------- +-- 消息表 +DROP TABLE IF EXISTS message_system; +CREATE TABLE message_system ( + message_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + message_title varchar(64) NULL DEFAULT NULL COMMENT '标题', + create_by varchar(64) NULL DEFAULT NULL COMMENT '创建者', + create_time datetime NULL DEFAULT NULL COMMENT '创建时间', + send_mode varchar(100) NULL DEFAULT NULL COMMENT '发送方式(0平台 1手机号 2 邮箱)', + code varchar(100) NULL DEFAULT NULL COMMENT '号码', + message_content text NULL COMMENT '消息内容', + message_recipient varchar(100) NULL DEFAULT NULL COMMENT '接收人', + message_status varchar(64) NULL DEFAULT NULL COMMENT '消息状态(0未读 1已读)', + message_type varchar(64) NULL DEFAULT NULL COMMENT '消息类型', + update_by varchar(64) NULL DEFAULT NULL COMMENT '更新者', + update_time datetime NULL DEFAULT NULL COMMENT '更新时间', + remark varchar(500) NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (message_id) USING BTREE +) ENGINE = InnoDB COMMENT = '消息表' ; + +-- 模版表 +DROP TABLE IF EXISTS message_template; +CREATE TABLE message_template ( + template_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + template_name varchar(100) NULL DEFAULT NULL COMMENT '模版名称', + template_code varchar(64) NULL DEFAULT NULL COMMENT '模版CODE', + template_type varchar(64) NULL DEFAULT NULL COMMENT '模版类型', + template_content text NULL COMMENT '模版内容', + template_variable text NULL COMMENT '变量属性', + create_by varchar(64) NULL DEFAULT NULL COMMENT '创建者', + create_time datetime NULL DEFAULT NULL COMMENT '创建时间', + update_by varchar(64) NULL DEFAULT NULL COMMENT '更新者', + update_time datetime NULL DEFAULT NULL COMMENT '更新时间', + remark varchar(500) NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (template_id) USING BTREE +) ENGINE = InnoDB COMMENT = '模版表' ; + +-- 变量表 +DROP TABLE IF EXISTS message_variable; +CREATE TABLE message_variable ( + variable_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + variable_name varchar(100) NULL DEFAULT NULL COMMENT '变量名称', + variable_type varchar(64) NULL DEFAULT NULL COMMENT '变量类型', + variable_content varchar(100) NULL DEFAULT NULL COMMENT '变量内容', + create_by varchar(64) NULL DEFAULT NULL COMMENT '创建者', + create_time datetime NULL DEFAULT NULL COMMENT '创建时间', + update_by varchar(64) NULL DEFAULT NULL COMMENT '更新者', + update_time datetime NULL DEFAULT NULL COMMENT '更新时间', + remark varchar(500) NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (variable_id) USING BTREE +) ENGINE = InnoDB COMMENT = '变量表' ; + +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '消息系统', 0, 6, 'modelMessage', NULL, NULL, '', 1, 0, 'M', '0', '0', '', 'message', 'admin', '2024-12-31 11:57:29', 'xl', '2025-01-03 15:48:44', ''); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +select @messageParentId := @parentId; +-- 消息系统菜单 +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '消息管理', @messageParentId, 0, 'messageSystem', 'modelMessage/messageSystem/index', NULL, '', 1, 0, 'C', '0', '0', 'modelMessage:messageSystem:list', '#', 'admin', '2024-12-21 15:00:31', 'admin', '2024-12-31 15:04:49', '消息管理菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '消息管理查询', @parentId, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:query', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '消息管理新增', @parentId, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:add', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '消息管理修改', @parentId, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:edit', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '消息管理删除', @parentId, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:remove', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '消息管理导出', @parentId, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:export', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); + +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '模版管理', @messageParentId, 1, 'template', 'modelMessage/template/index', NULL, '', 1, 0, 'C', '0', '0', 'modelMessage:template:list', '#', 'admin', '2024-12-31 14:59:52', '', NULL, '模版管理菜单'); + +SELECT @parentId := LAST_INSERT_ID(); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '模版管理查询', @parentId, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:query', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '模版管理新增', @parentId, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:add', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '模版管理修改', @parentId, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:edit', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '模版管理删除', @parentId, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:remove', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '模版管理导出', @parentId, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:export', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); + + +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '变量管理', @messageParentId, 2, 'variable', 'modelMessage/variable/index', NULL, '', 1, 0, 'C', '0', '0', 'modelMessage:variable:list', '#', 'admin', '2024-12-31 15:01:50', 'admin', '2024-12-31 15:04:56', '变量管理菜单'); + +SELECT @parentId := LAST_INSERT_ID(); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '变量管理查询', @parentId, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:query', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '变量管理新增', @parentId, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:add', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '变量管理修改', @parentId, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:edit', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '变量管理删除', @parentId, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:remove', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO sys_menu ( menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ( '变量管理导出', @parentId, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:export', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); + +-- 消息系统字典 +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (142, 0, '未读', '0', 'message_status', NULL, 'primary', 'N', '0', 'xl', '2024-12-21 15:13:02', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (143, 1, '已读', '1', 'message_status', NULL, 'success', 'N', '0', 'xl', '2024-12-21 15:13:15', 'xl', '2024-12-21 15:13:22', NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (144, 0, '平台', '0', 'send_mode', NULL, 'primary', 'N', '0', 'xl', '2024-12-25 09:40:01', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (145, 1, '短信', '1', 'send_mode', NULL, 'success', 'N', '0', 'xl', '2024-12-25 09:40:16', 'xl', '2025-01-01 10:12:07', NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (146, 2, '邮件', '2', 'send_mode', NULL, 'warning', 'N', '0', 'xl', '2024-12-25 09:40:28', 'xl', '2025-01-01 10:12:14', NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (147, 0, '验证码', '0', 'template_type', NULL, 'primary', 'N', '0', 'xl', '2025-01-03 09:22:52', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (148, 0, '通知', '0', 'message_type', NULL, 'primary', 'N', '0', 'xl', '2025-01-03 15:12:29', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (149, 0, '提示', '1', 'message_type', NULL, 'success', 'N', '0', 'xl', '2025-01-03 15:12:41', 'xl', '2025-01-03 15:12:45', NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (150, 1, '推广', '1', 'template_type', NULL, 'success', 'N', '0', 'xl', '2025-01-03 15:13:15', '', NULL, NULL); + diff --git a/sql/mysql/online.sql b/sql/mysql/online.sql new file mode 100644 index 0000000..df99eae --- /dev/null +++ b/sql/mysql/online.sql @@ -0,0 +1,71 @@ +DROP TABLE IF EXISTS online_mb; + +CREATE TABLE online_mb ( + mb_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + tag varchar(255) NULL COMMENT '标签名', + tag_id varchar(255) NULL COMMENT '标签id', + parameter_type varchar(255) NULL COMMENT '参数类型', + result_map varchar(255) NULL COMMENT '结果类型', + sql_text varchar(255) NULL COMMENT 'sql语句', + path varchar(255) NULL COMMENT '请求路径', + method varchar(255) NULL COMMENT '请求方式', + result_type varchar(255) NULL COMMENT '响应类型', + actuator varchar(255) NULL COMMENT '执行器', + user_id char(1) NULL COMMENT '是否需要userId', + dept_id char(1) NULL COMMENT '是否需要deptId', + permission_type varchar(255) NULL COMMENT '许可类型', + permission_value varchar(255) NULL COMMENT '许可值', + del_flag varchar(10) NOT NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + PRIMARY KEY (mb_id) +) ENGINE = InnoDB COMMENT = '在线接口'; + +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query,route_name, is_frame, is_cache, menu_type, visible, `status`, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ('Online', 0, 5, 'onlinedev', NULL, NULL,'', 1, 0, 'M', '0', '0', NULL, 'international', 'admin', '2024-03-07 19:38:34', '', NULL, ''); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component,route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('mybatis在线接口', @parentId, '1', 'mb', 'online/mb/index','', 1, 0, 'C', '0', '0', 'online:mb:list', 'code', 'admin', sysdate(), '', null, 'mybatis在线接口菜单'); + +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query,route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ('数据库', @parentId, 1, 'db', 'online/db/index', NULL,'', 1, 0, 'C', '0', '0', 'admin', 'table', 'admin', '2024-03-07 19:48:24', 'admin', '2024-03-07 19:54:46', ''); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component,route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('mybatis在线接口查询', @parentId, '1', '#', '','', 1, 0, 'F', '0', '0', 'online:mb:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component,route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('mybatis在线接口新增', @parentId, '2', '#', '','', 1, 0, 'F', '0', '0', 'online:mb:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component,route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('mybatis在线接口修改', @parentId, '3', '#', '','', 1, 0, 'F', '0', '0', 'online:mb:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('mybatis在线接口删除', @parentId, '4', '#', '','', 1, 0, 'F', '0', '0', 'online:mb:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('mybatis在线接口导出', @parentId, '5', '#', '','', 1, 0, 'F', '0', '0', 'online:mb:export', '#', 'admin', sysdate(), '', null, ''); + + +INSERT INTO sys_dict_type ( dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES ( '请求方式', 'online_api_method', '0', 'admin', '2024-02-21 18:22:03', 'admin', '2024-02-21 18:22:13', NULL); +INSERT INTO sys_dict_type ( dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES ( '标签名', 'online_api_tag', '0', 'admin', '2024-02-21 18:22:29', '', NULL, NULL); +INSERT INTO sys_dict_type ( dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES ( '响应类型', 'online_api_result', '0', 'admin', '2024-02-21 18:22:46', '', NULL, NULL); +INSERT INTO sys_dict_type ( dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES ( '执行器', 'online_api_actuator', '0', 'admin', '2024-02-21 18:23:03', '', NULL, NULL); + + +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'POST', 'POST', 'online_api_method', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:23:23', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'GET', 'GET', 'online_api_method', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:23:30', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'PUT', 'PUT', 'online_api_method', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:23:37', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'DELETE', 'DELETE', 'online_api_method', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:23:49', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'select', 'select', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:24:06', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'update', 'update', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:24:12', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'insert', 'insert', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:24:18', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'delete', 'delete', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:24:26', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'selectList', 'selectList', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:00', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'insert', 'insert', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:05', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'selectOne', 'selectOne', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:11', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'update', 'update', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:16', '', NULL, NULL); +INSERT INTO sys_dict_data ( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES ( 0, 'delete', 'delete', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:21', '', NULL, NULL); diff --git a/sql/mysql/pay.sql b/sql/mysql/pay.sql new file mode 100644 index 0000000..26fc33f --- /dev/null +++ b/sql/mysql/pay.sql @@ -0,0 +1,98 @@ +-- ---------------------------- +-- 订单表 +-- ---------------------------- +DROP TABLE IF EXISTS `pay_order`; +CREATE TABLE `pay_order` ( + order_id bigint NOT NULL AUTO_INCREMENT COMMENT '订单id', + order_number varchar(255) NULL DEFAULT NULL COMMENT '订单号', + third_number varchar(255) NULL DEFAULT NULL COMMENT '第三方订单号', + order_status varchar(255) NULL DEFAULT NULL COMMENT '订单状态', + total_amount varchar(255) NULL DEFAULT NULL COMMENT '订单总金额', + actual_amount varchar(255) NULL DEFAULT NULL COMMENT '实际支付金额', + order_content varchar(255) NULL DEFAULT NULL COMMENT '订单内容', + order_message varchar(255) NULL DEFAULT NULL COMMENT '负载信息', + pay_type varchar(255) NULL DEFAULT NULL COMMENT '支付方式', + pay_time datetime DEFAULT NULL COMMENT '支付时间', + pay_by varchar(64) DEFAULT '' COMMENT '支付人', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + PRIMARY KEY (`order_id`) +) ENGINE = InnoDB COMMENT = '订单'; + +-- ---------------------------- +-- 发票表 +-- ---------------------------- +DROP TABLE IF EXISTS `pay_invoice`; +CREATE TABLE `pay_invoice` ( + invoice_id bigint NOT NULL AUTO_INCREMENT COMMENT '发票id', + order_number varchar(255) NULL DEFAULT NULL COMMENT '订单号', + invoice_type varchar(255) NULL DEFAULT NULL COMMENT '发票类型', + invoice_header varchar(255) NULL DEFAULT NULL COMMENT '发票抬头', + invoice_number varchar(255) NULL DEFAULT NULL COMMENT '纳税人识别号', + invoice_phone varchar(255) NULL DEFAULT NULL COMMENT '收票人手机号', + invoice_email varchar(255) NULL DEFAULT NULL COMMENT '收票人邮箱', + invoice_remark varchar(255) NULL DEFAULT NULL COMMENT '发票备注', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + PRIMARY KEY (`invoice_id`) +) ENGINE = InnoDB COMMENT = '发票'; + +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query,route_name, is_frame, is_cache, menu_type, visible, `status`, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ('支付管理', 0, 4, 'pay', NULL, NULL, '',1, 0, 'M', '0', '0', NULL, 'money', 'admin', '2024-02-15 22:40:23', '', NULL, ''); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +select @payParentId := @parentId; + +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('订单', @payParentId, '1', 'order', 'pay/order/index', '',1, 0, 'C', '0', '0', 'pay:order:list', '#', 'admin', sysdate(), '', null, '订单菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('订单查询', @parentId, '1', '#', '', '',1, 0, 'F', '0', '0', 'pay:order:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('订单新增', @parentId, '2', '#', '', '',1, 0, 'F', '0', '0', 'pay:order:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('订单修改', @parentId, '3', '#', '', '',1, 0, 'F', '0', '0', 'pay:order:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('订单删除', @parentId, '4', '#', '', '',1, 0, 'F', '0', '0', 'pay:order:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('订单导出', @parentId, '5', '#', '', '',1, 0, 'F', '0', '0', 'pay:order:export', '#', 'admin', sysdate(), '', null, ''); + + +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('发票', @payParentId, '1', 'invoice', 'pay/invoice/index', '',1, 0, 'C', '0', '0', 'pay:invoice:list', '#', 'admin', sysdate(), '', null, '发票菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('发票查询', @parentId, '1', '#', '', '',1, 0, 'F', '0', '0', 'pay:invoice:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('发票新增', @parentId, '2', '#', '', '',1, 0, 'F', '0', '0', 'pay:invoice:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('发票修改', @parentId, '3', '#', '', '',1, 0, 'F', '0', '0', 'pay:invoice:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('发票删除', @parentId, '4', '#', '', '',1, 0, 'F', '0', '0', 'pay:invoice:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, route_name,is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('发票导出', @parentId, '5', '#', '', '',1, 0, 'F', '0', '0', 'pay:invoice:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/sql/mysql/quartz.sql b/sql/mysql/quartz.sql new file mode 100644 index 0000000..cee613b --- /dev/null +++ b/sql/mysql/quartz.sql @@ -0,0 +1,174 @@ +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + +-- ---------------------------- +-- 1、存储每一个已配置的 jobDetail 的详细信息 +-- ---------------------------- +create table QRTZ_JOB_DETAILS ( + sched_name varchar(120) not null comment '调度名称', + job_name varchar(200) not null comment '任务名称', + job_group varchar(200) not null comment '任务组名', + description varchar(250) null comment '相关介绍', + job_class_name varchar(250) not null comment '执行任务类名称', + is_durable varchar(1) not null comment '是否持久化', + is_nonconcurrent varchar(1) not null comment '是否并发', + is_update_data varchar(1) not null comment '是否更新数据', + requests_recovery varchar(1) not null comment '是否接受恢复执行', + job_data blob null comment '存放持久化job对象', + primary key (sched_name, job_name, job_group) +) engine=innodb comment = '任务详细信息表'; + +-- ---------------------------- +-- 2、 存储已配置的 Trigger 的信息 +-- ---------------------------- +create table QRTZ_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment '触发器的名字', + trigger_group varchar(200) not null comment '触发器所属组的名字', + job_name varchar(200) not null comment 'qrtz_job_details表job_name的外键', + job_group varchar(200) not null comment 'qrtz_job_details表job_group的外键', + description varchar(250) null comment '相关介绍', + next_fire_time bigint(13) null comment '上一次触发时间(毫秒)', + prev_fire_time bigint(13) null comment '下一次触发时间(默认为-1表示不触发)', + priority integer null comment '优先级', + trigger_state varchar(16) not null comment '触发器状态', + trigger_type varchar(8) not null comment '触发器的类型', + start_time bigint(13) not null comment '开始时间', + end_time bigint(13) null comment '结束时间', + calendar_name varchar(200) null comment '日程表名称', + misfire_instr smallint(2) null comment '补偿执行的策略', + job_data blob null comment '存放持久化job对象', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, job_name, job_group) references QRTZ_JOB_DETAILS(sched_name, job_name, job_group) +) engine=innodb comment = '触发器详细信息表'; + +-- ---------------------------- +-- 3、 存储简单的 Trigger,包括重复次数,间隔,以及已触发的次数 +-- ---------------------------- +create table QRTZ_SIMPLE_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + repeat_count bigint(7) not null comment '重复的次数统计', + repeat_interval bigint(12) not null comment '重复的间隔时间', + times_triggered bigint(10) not null comment '已经触发的次数', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +) engine=innodb comment = '简单触发器的信息表'; + +-- ---------------------------- +-- 4、 存储 Cron Trigger,包括 Cron 表达式和时区信息 +-- ---------------------------- +create table QRTZ_CRON_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + cron_expression varchar(200) not null comment 'cron表达式', + time_zone_id varchar(80) comment '时区', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +) engine=innodb comment = 'Cron类型的触发器表'; + +-- ---------------------------- +-- 5、 Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候) +-- ---------------------------- +create table QRTZ_BLOB_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + blob_data blob null comment '存放持久化Trigger对象', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +) engine=innodb comment = 'Blob类型的触发器表'; + +-- ---------------------------- +-- 6、 以 Blob 类型存储存放日历信息, quartz可配置一个日历来指定一个时间范围 +-- ---------------------------- +create table QRTZ_CALENDARS ( + sched_name varchar(120) not null comment '调度名称', + calendar_name varchar(200) not null comment '日历名称', + calendar blob not null comment '存放持久化calendar对象', + primary key (sched_name, calendar_name) +) engine=innodb comment = '日历信息表'; + +-- ---------------------------- +-- 7、 存储已暂停的 Trigger 组的信息 +-- ---------------------------- +create table QRTZ_PAUSED_TRIGGER_GRPS ( + sched_name varchar(120) not null comment '调度名称', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + primary key (sched_name, trigger_group) +) engine=innodb comment = '暂停的触发器表'; + +-- ---------------------------- +-- 8、 存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息 +-- ---------------------------- +create table QRTZ_FIRED_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + entry_id varchar(95) not null comment '调度器实例id', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + instance_name varchar(200) not null comment '调度器实例名', + fired_time bigint(13) not null comment '触发的时间', + sched_time bigint(13) not null comment '定时器制定的时间', + priority integer not null comment '优先级', + state varchar(16) not null comment '状态', + job_name varchar(200) null comment '任务名称', + job_group varchar(200) null comment '任务组名', + is_nonconcurrent varchar(1) null comment '是否并发', + requests_recovery varchar(1) null comment '是否接受恢复执行', + primary key (sched_name, entry_id) +) engine=innodb comment = '已触发的触发器表'; + +-- ---------------------------- +-- 9、 存储少量的有关 Scheduler 的状态信息,假如是用于集群中,可以看到其他的 Scheduler 实例 +-- ---------------------------- +create table QRTZ_SCHEDULER_STATE ( + sched_name varchar(120) not null comment '调度名称', + instance_name varchar(200) not null comment '实例名称', + last_checkin_time bigint(13) not null comment '上次检查时间', + checkin_interval bigint(13) not null comment '检查间隔时间', + primary key (sched_name, instance_name) +) engine=innodb comment = '调度器状态表'; + +-- ---------------------------- +-- 10、 存储程序的悲观锁的信息(假如使用了悲观锁) +-- ---------------------------- +create table QRTZ_LOCKS ( + sched_name varchar(120) not null comment '调度名称', + lock_name varchar(40) not null comment '悲观锁名称', + primary key (sched_name, lock_name) +) engine=innodb comment = '存储的悲观锁信息表'; + +-- ---------------------------- +-- 11、 Quartz集群实现同步机制的行锁表 +-- ---------------------------- +create table QRTZ_SIMPROP_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + str_prop_1 varchar(512) null comment 'String类型的trigger的第一个参数', + str_prop_2 varchar(512) null comment 'String类型的trigger的第二个参数', + str_prop_3 varchar(512) null comment 'String类型的trigger的第三个参数', + int_prop_1 int null comment 'int类型的trigger的第一个参数', + int_prop_2 int null comment 'int类型的trigger的第二个参数', + long_prop_1 bigint null comment 'long类型的trigger的第一个参数', + long_prop_2 bigint null comment 'long类型的trigger的第二个参数', + dec_prop_1 numeric(13,4) null comment 'decimal类型的trigger的第一个参数', + dec_prop_2 numeric(13,4) null comment 'decimal类型的trigger的第二个参数', + bool_prop_1 varchar(1) null comment 'Boolean类型的trigger的第一个参数', + bool_prop_2 varchar(1) null comment 'Boolean类型的trigger的第二个参数', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +) engine=innodb comment = '同步机制的行锁表'; + +commit; \ No newline at end of file diff --git a/sql/mysql/ry_20250603.sql b/sql/mysql/ry_20250603.sql new file mode 100644 index 0000000..2939b9c --- /dev/null +++ b/sql/mysql/ry_20250603.sql @@ -0,0 +1,643 @@ +-- ---------------------------- +-- 1、部门表 +-- ---------------------------- +drop table if exists sys_dept; +create table sys_dept ( + dept_id bigint(20) not null auto_increment comment '部门id', + parent_id bigint(20) default 0 comment '父部门id', + ancestors varchar(50) default '' comment '祖级列表', + dept_name varchar(30) default '' comment '部门名称', + order_num int(4) default 0 comment '显示顺序', + leader varchar(20) default null comment '负责人', + phone varchar(11) default null comment '联系电话', + email varchar(50) default null comment '邮箱', + status char(1) default '0' comment '部门状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + primary key (dept_id) +) engine=innodb auto_increment=200 comment = '部门表'; + +-- ---------------------------- +-- 初始化-部门表数据 +-- ---------------------------- +insert into sys_dept values(100, 0, '0', '若依科技', 0, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(101, 100, '0,100', '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(103, 101, '0,100,101', '研发部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(104, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(105, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(106, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(107, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(108, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(109, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); + + +-- ---------------------------- +-- 2、用户信息表 +-- ---------------------------- +drop table if exists sys_user; +create table sys_user ( + user_id bigint(20) not null auto_increment comment '用户ID', + dept_id bigint(20) default null comment '部门ID', + user_name varchar(30) not null comment '用户账号', + nick_name varchar(30) not null comment '用户昵称', + user_type varchar(2) default '00' comment '用户类型(00系统用户)', + email varchar(50) default '' comment '用户邮箱', + phonenumber varchar(11) default '' comment '手机号码', + sex char(1) default '0' comment '用户性别(0男 1女 2未知)', + avatar varchar(100) default '' comment '头像地址', + password varchar(100) default '' comment '密码', + status char(1) default '0' comment '账号状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + login_ip varchar(128) default '' comment '最后登录IP', + login_date datetime comment '最后登录时间', + pwd_update_date datetime comment '密码最后更新时间', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (user_id) +) engine=innodb auto_increment=100 comment = '用户信息表'; + +-- ---------------------------- +-- 初始化-用户信息表数据 +-- ---------------------------- +insert into sys_user values(1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), sysdate(), 'admin', sysdate(), '', null, '管理员'); +insert into sys_user values(2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), sysdate(), 'admin', sysdate(), '', null, '测试员'); + + + +-- ---------------------------- +-- 3、岗位信息表 +-- ---------------------------- +drop table if exists sys_post; +create table sys_post +( + post_id bigint(20) not null auto_increment comment '岗位ID', + post_code varchar(64) not null comment '岗位编码', + post_name varchar(50) not null comment '岗位名称', + post_sort int(4) not null comment '显示顺序', + status char(1) not null comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (post_id) +) engine=innodb comment = '岗位信息表'; + +-- ---------------------------- +-- 初始化-岗位信息表数据 +-- ---------------------------- +insert into sys_post values(1, 'ceo', '董事长', 1, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(2, 'se', '项目经理', 2, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(3, 'hr', '人力资源', 3, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(4, 'user', '普通员工', 4, '0', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 4、角色信息表 +-- ---------------------------- +drop table if exists sys_role; +create table sys_role ( + role_id bigint(20) not null auto_increment comment '角色ID', + role_name varchar(30) not null comment '角色名称', + role_key varchar(100) not null comment '角色权限字符串', + role_sort int(4) not null comment '显示顺序', + data_scope char(1) default '1' comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + menu_check_strictly tinyint(1) default 1 comment '菜单树选择项是否关联显示', + dept_check_strictly tinyint(1) default 1 comment '部门树选择项是否关联显示', + status char(1) not null comment '角色状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (role_id) +) engine=innodb auto_increment=100 comment = '角色信息表'; + +-- ---------------------------- +-- 初始化-角色信息表数据 +-- ---------------------------- +insert into sys_role values('1', '超级管理员', 'admin', 1, 1, 1, 1, '0', '0', 'admin', sysdate(), '', null, '超级管理员'); +insert into sys_role values('2', '普通角色', 'common', 2, 2, 1, 1, '0', '0', 'admin', sysdate(), '', null, '普通角色'); + + +-- ---------------------------- +-- 5、菜单权限表 +-- ---------------------------- +drop table if exists sys_menu; +create table sys_menu ( + menu_id bigint(20) not null auto_increment comment '菜单ID', + menu_name varchar(50) not null comment '菜单名称', + parent_id bigint(20) default 0 comment '父菜单ID', + order_num int(4) default 0 comment '显示顺序', + path varchar(200) default '' comment '路由地址', + component varchar(255) default null comment '组件路径', + query varchar(255) default null comment '路由参数', + route_name varchar(50) default '' comment '路由名称', + is_frame int(1) default 1 comment '是否为外链(0是 1否)', + is_cache int(1) default 0 comment '是否缓存(0缓存 1不缓存)', + menu_type char(1) default '' comment '菜单类型(M目录 C菜单 F按钮)', + visible char(1) default 0 comment '菜单状态(0显示 1隐藏)', + status char(1) default 0 comment '菜单状态(0正常 1停用)', + perms varchar(100) default null comment '权限标识', + icon varchar(100) default '#' comment '菜单图标', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default '' comment '备注', + primary key (menu_id) +) engine=innodb auto_increment=2000 comment = '菜单权限表'; + +-- ---------------------------- +-- 初始化-菜单信息表数据 +-- ---------------------------- +-- 一级菜单 +insert into sys_menu values('1', '系统管理', '0', '1', 'system', null, '', '', 1, 0, 'M', '0', '0', '', 'system', 'admin', sysdate(), '', null, '系统管理目录'); +insert into sys_menu values('2', '系统监控', '0', '2', 'monitor', null, '', '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', sysdate(), '', null, '系统监控目录'); +insert into sys_menu values('3', '系统工具', '0', '3', 'tool', null, '', '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', sysdate(), '', null, '系统工具目录'); +insert into sys_menu values('4', '若依官网', '0', '4', 'http://ruoyi.vip', null, '', '', 0, 0, 'M', '0', '0', '', 'guide', 'admin', sysdate(), '', null, '若依官网地址'); +-- 二级菜单 +insert into sys_menu values('100', '用户管理', '1', '1', 'user', 'system/user/index', '', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', sysdate(), '', null, '用户管理菜单'); +insert into sys_menu values('101', '角色管理', '1', '2', 'role', 'system/role/index', '', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', sysdate(), '', null, '角色管理菜单'); +insert into sys_menu values('102', '菜单管理', '1', '3', 'menu', 'system/menu/index', '', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', sysdate(), '', null, '菜单管理菜单'); +insert into sys_menu values('103', '部门管理', '1', '4', 'dept', 'system/dept/index', '', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', sysdate(), '', null, '部门管理菜单'); +insert into sys_menu values('104', '岗位管理', '1', '5', 'post', 'system/post/index', '', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', sysdate(), '', null, '岗位管理菜单'); +insert into sys_menu values('105', '字典管理', '1', '6', 'dict', 'system/dict/index', '', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', sysdate(), '', null, '字典管理菜单'); +insert into sys_menu values('106', '参数设置', '1', '7', 'config', 'system/config/index', '', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', sysdate(), '', null, '参数设置菜单'); +insert into sys_menu values('107', '通知公告', '1', '8', 'notice', 'system/notice/index', '', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', sysdate(), '', null, '通知公告菜单'); +insert into sys_menu values('108', '日志管理', '1', '9', 'log', '', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', sysdate(), '', null, '日志管理菜单'); +insert into sys_menu values('109', '在线用户', '2', '1', 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', sysdate(), '', null, '在线用户菜单'); +insert into sys_menu values('110', '定时任务', '2', '2', 'job', 'monitor/job/index', '', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', sysdate(), '', null, '定时任务菜单'); +insert into sys_menu values('111', '数据监控', '2', '3', 'druid', 'monitor/druid/index', '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', sysdate(), '', null, '数据监控菜单'); +insert into sys_menu values('112', '服务监控', '2', '4', 'server', 'monitor/server/index', '', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', sysdate(), '', null, '服务监控菜单'); +insert into sys_menu values('113', '缓存监控', '2', '5', 'cache', 'monitor/cache/index', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', sysdate(), '', null, '缓存监控菜单'); +insert into sys_menu values('114', '缓存列表', '2', '6', 'cacheList', 'monitor/cache/list', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', sysdate(), '', null, '缓存列表菜单'); +insert into sys_menu values('115', '表单构建', '3', '1', 'build', 'tool/build/index', '', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', sysdate(), '', null, '表单构建菜单'); +insert into sys_menu values('117', '系统接口', '3', '3', 'swagger', 'tool/swagger/index', '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', sysdate(), '', null, '系统接口菜单'); +-- 三级菜单 +insert into sys_menu values('500', '操作日志', '108', '1', 'operlog', 'monitor/operlog/index', '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', sysdate(), '', null, '操作日志菜单'); +insert into sys_menu values('501', '登录日志', '108', '2', 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', sysdate(), '', null, '登录日志菜单'); +-- 用户管理按钮 +insert into sys_menu values('1000', '用户查询', '100', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1001', '用户新增', '100', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1002', '用户修改', '100', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1003', '用户删除', '100', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1004', '用户导出', '100', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1005', '用户导入', '100', '6', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1006', '重置密码', '100', '7', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', sysdate(), '', null, ''); +-- 角色管理按钮 +insert into sys_menu values('1007', '角色查询', '101', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1008', '角色新增', '101', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1009', '角色修改', '101', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1010', '角色删除', '101', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1011', '角色导出', '101', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', sysdate(), '', null, ''); +-- 菜单管理按钮 +insert into sys_menu values('1012', '菜单查询', '102', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1013', '菜单新增', '102', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1014', '菜单修改', '102', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1015', '菜单删除', '102', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', sysdate(), '', null, ''); +-- 部门管理按钮 +insert into sys_menu values('1016', '部门查询', '103', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1017', '部门新增', '103', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1018', '部门修改', '103', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1019', '部门删除', '103', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', sysdate(), '', null, ''); +-- 岗位管理按钮 +insert into sys_menu values('1020', '岗位查询', '104', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1021', '岗位新增', '104', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1022', '岗位修改', '104', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1023', '岗位删除', '104', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1024', '岗位导出', '104', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', sysdate(), '', null, ''); +-- 字典管理按钮 +insert into sys_menu values('1025', '字典查询', '105', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1026', '字典新增', '105', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1027', '字典修改', '105', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1028', '字典删除', '105', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1029', '字典导出', '105', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', sysdate(), '', null, ''); +-- 参数设置按钮 +insert into sys_menu values('1030', '参数查询', '106', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1031', '参数新增', '106', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1032', '参数修改', '106', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1033', '参数删除', '106', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1034', '参数导出', '106', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', sysdate(), '', null, ''); +-- 通知公告按钮 +insert into sys_menu values('1035', '公告查询', '107', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1036', '公告新增', '107', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1037', '公告修改', '107', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1038', '公告删除', '107', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', sysdate(), '', null, ''); +-- 操作日志按钮 +insert into sys_menu values('1039', '操作查询', '500', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1040', '操作删除', '500', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1041', '日志导出', '500', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', sysdate(), '', null, ''); +-- 登录日志按钮 +insert into sys_menu values('1042', '登录查询', '501', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1043', '登录删除', '501', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1044', '日志导出', '501', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1045', '账户解锁', '501', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', sysdate(), '', null, ''); + +-- 在线用户按钮 +insert into sys_menu values('1046', '在线查询', '109', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1047', '批量强退', '109', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1048', '单条强退', '109', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', sysdate(), '', null, ''); +-- 定时任务按钮 +insert into sys_menu values('1049', '任务查询', '110', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1050', '任务新增', '110', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1051', '任务修改', '110', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1052', '任务删除', '110', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1053', '状态修改', '110', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1054', '任务导出', '110', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', sysdate(), '', null, ''); + + + + +-- ---------------------------- +-- 6、用户和角色关联表 用户N-1角色 +-- ---------------------------- +drop table if exists sys_user_role; +create table sys_user_role ( + user_id bigint(20) not null comment '用户ID', + role_id bigint(20) not null comment '角色ID', + primary key(user_id, role_id) +) engine=innodb comment = '用户和角色关联表'; + +-- ---------------------------- +-- 初始化-用户和角色关联表数据 +-- ---------------------------- +insert into sys_user_role values ('1', '1'); +insert into sys_user_role values ('2', '2'); + + +-- ---------------------------- +-- 7、角色和菜单关联表 角色1-N菜单 +-- ---------------------------- +drop table if exists sys_role_menu; +create table sys_role_menu ( + role_id bigint(20) not null comment '角色ID', + menu_id bigint(20) not null comment '菜单ID', + primary key(role_id, menu_id) +) engine=innodb comment = '角色和菜单关联表'; + +-- ---------------------------- +-- 初始化-角色和菜单关联表数据 +-- ---------------------------- +insert into sys_role_menu values ('2', '1'); +insert into sys_role_menu values ('2', '2'); +insert into sys_role_menu values ('2', '3'); +insert into sys_role_menu values ('2', '4'); +insert into sys_role_menu values ('2', '100'); +insert into sys_role_menu values ('2', '101'); +insert into sys_role_menu values ('2', '102'); +insert into sys_role_menu values ('2', '103'); +insert into sys_role_menu values ('2', '104'); +insert into sys_role_menu values ('2', '105'); +insert into sys_role_menu values ('2', '106'); +insert into sys_role_menu values ('2', '107'); +insert into sys_role_menu values ('2', '108'); +insert into sys_role_menu values ('2', '109'); +insert into sys_role_menu values ('2', '110'); +insert into sys_role_menu values ('2', '111'); +insert into sys_role_menu values ('2', '112'); +insert into sys_role_menu values ('2', '113'); +insert into sys_role_menu values ('2', '114'); +insert into sys_role_menu values ('2', '115'); +insert into sys_role_menu values ('2', '116'); +insert into sys_role_menu values ('2', '117'); +insert into sys_role_menu values ('2', '500'); +insert into sys_role_menu values ('2', '501'); +insert into sys_role_menu values ('2', '1000'); +insert into sys_role_menu values ('2', '1001'); +insert into sys_role_menu values ('2', '1002'); +insert into sys_role_menu values ('2', '1003'); +insert into sys_role_menu values ('2', '1004'); +insert into sys_role_menu values ('2', '1005'); +insert into sys_role_menu values ('2', '1006'); +insert into sys_role_menu values ('2', '1007'); +insert into sys_role_menu values ('2', '1008'); +insert into sys_role_menu values ('2', '1009'); +insert into sys_role_menu values ('2', '1010'); +insert into sys_role_menu values ('2', '1011'); +insert into sys_role_menu values ('2', '1012'); +insert into sys_role_menu values ('2', '1013'); +insert into sys_role_menu values ('2', '1014'); +insert into sys_role_menu values ('2', '1015'); +insert into sys_role_menu values ('2', '1016'); +insert into sys_role_menu values ('2', '1017'); +insert into sys_role_menu values ('2', '1018'); +insert into sys_role_menu values ('2', '1019'); +insert into sys_role_menu values ('2', '1020'); +insert into sys_role_menu values ('2', '1021'); +insert into sys_role_menu values ('2', '1022'); +insert into sys_role_menu values ('2', '1023'); +insert into sys_role_menu values ('2', '1024'); +insert into sys_role_menu values ('2', '1025'); +insert into sys_role_menu values ('2', '1026'); +insert into sys_role_menu values ('2', '1027'); +insert into sys_role_menu values ('2', '1028'); +insert into sys_role_menu values ('2', '1029'); +insert into sys_role_menu values ('2', '1030'); +insert into sys_role_menu values ('2', '1031'); +insert into sys_role_menu values ('2', '1032'); +insert into sys_role_menu values ('2', '1033'); +insert into sys_role_menu values ('2', '1034'); +insert into sys_role_menu values ('2', '1035'); +insert into sys_role_menu values ('2', '1036'); +insert into sys_role_menu values ('2', '1037'); +insert into sys_role_menu values ('2', '1038'); +insert into sys_role_menu values ('2', '1039'); +insert into sys_role_menu values ('2', '1040'); +insert into sys_role_menu values ('2', '1041'); +insert into sys_role_menu values ('2', '1042'); +insert into sys_role_menu values ('2', '1043'); +insert into sys_role_menu values ('2', '1044'); +insert into sys_role_menu values ('2', '1045'); +insert into sys_role_menu values ('2', '1046'); +insert into sys_role_menu values ('2', '1047'); +insert into sys_role_menu values ('2', '1048'); +insert into sys_role_menu values ('2', '1049'); +insert into sys_role_menu values ('2', '1050'); +insert into sys_role_menu values ('2', '1051'); +insert into sys_role_menu values ('2', '1052'); +insert into sys_role_menu values ('2', '1053'); +insert into sys_role_menu values ('2', '1054'); +insert into sys_role_menu values ('2', '1055'); +insert into sys_role_menu values ('2', '1056'); +insert into sys_role_menu values ('2', '1057'); +insert into sys_role_menu values ('2', '1058'); +insert into sys_role_menu values ('2', '1059'); +insert into sys_role_menu values ('2', '1060'); + +-- ---------------------------- +-- 8、角色和部门关联表 角色1-N部门 +-- ---------------------------- +drop table if exists sys_role_dept; +create table sys_role_dept ( + role_id bigint(20) not null comment '角色ID', + dept_id bigint(20) not null comment '部门ID', + primary key(role_id, dept_id) +) engine=innodb comment = '角色和部门关联表'; + +-- ---------------------------- +-- 初始化-角色和部门关联表数据 +-- ---------------------------- +insert into sys_role_dept values ('2', '100'); +insert into sys_role_dept values ('2', '101'); +insert into sys_role_dept values ('2', '105'); + + +-- ---------------------------- +-- 9、用户与岗位关联表 用户1-N岗位 +-- ---------------------------- +drop table if exists sys_user_post; +create table sys_user_post +( + user_id bigint(20) not null comment '用户ID', + post_id bigint(20) not null comment '岗位ID', + primary key (user_id, post_id) +) engine=innodb comment = '用户与岗位关联表'; + +-- ---------------------------- +-- 初始化-用户与岗位关联表数据 +-- ---------------------------- +insert into sys_user_post values ('1', '1'); +insert into sys_user_post values ('2', '2'); + + +-- ---------------------------- +-- 10、操作日志记录 +-- ---------------------------- +drop table if exists sys_oper_log; +create table sys_oper_log ( + oper_id bigint(20) not null auto_increment comment '日志主键', + title varchar(50) default '' comment '模块标题', + business_type int(2) default 0 comment '业务类型(0其它 1新增 2修改 3删除)', + method varchar(100) default '' comment '方法名称', + request_method varchar(10) default '' comment '请求方式', + operator_type int(1) default 0 comment '操作类别(0其它 1后台用户 2手机端用户)', + oper_name varchar(50) default '' comment '操作人员', + dept_name varchar(50) default '' comment '部门名称', + oper_url varchar(255) default '' comment '请求URL', + oper_ip varchar(128) default '' comment '主机地址', + oper_location varchar(255) default '' comment '操作地点', + oper_param varchar(2000) default '' comment '请求参数', + json_result varchar(2000) default '' comment '返回参数', + status int(1) default 0 comment '操作状态(0正常 1异常)', + error_msg varchar(2000) default '' comment '错误消息', + oper_time datetime comment '操作时间', + cost_time bigint(20) default 0 comment '消耗时间', + primary key (oper_id), + key idx_sys_oper_log_bt (business_type), + key idx_sys_oper_log_s (status), + key idx_sys_oper_log_ot (oper_time) +) engine=innodb auto_increment=100 comment = '操作日志记录'; + + +-- ---------------------------- +-- 11、字典类型表 +-- ---------------------------- +drop table if exists sys_dict_type; +create table sys_dict_type +( + dict_id bigint(20) not null auto_increment comment '字典主键', + dict_name varchar(100) default '' comment '字典名称', + dict_type varchar(100) default '' comment '字典类型', + status char(1) default '0' comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (dict_id), + unique (dict_type) +) engine=innodb auto_increment=100 comment = '字典类型表'; + +insert into sys_dict_type values(1, '用户性别', 'sys_user_sex', '0', 'admin', sysdate(), '', null, '用户性别列表'); +insert into sys_dict_type values(2, '菜单状态', 'sys_show_hide', '0', 'admin', sysdate(), '', null, '菜单状态列表'); +insert into sys_dict_type values(3, '系统开关', 'sys_normal_disable', '0', 'admin', sysdate(), '', null, '系统开关列表'); +insert into sys_dict_type values(4, '任务状态', 'sys_job_status', '0', 'admin', sysdate(), '', null, '任务状态列表'); +insert into sys_dict_type values(5, '任务分组', 'sys_job_group', '0', 'admin', sysdate(), '', null, '任务分组列表'); +insert into sys_dict_type values(6, '系统是否', 'sys_yes_no', '0', 'admin', sysdate(), '', null, '系统是否列表'); +insert into sys_dict_type values(7, '通知类型', 'sys_notice_type', '0', 'admin', sysdate(), '', null, '通知类型列表'); +insert into sys_dict_type values(8, '通知状态', 'sys_notice_status', '0', 'admin', sysdate(), '', null, '通知状态列表'); +insert into sys_dict_type values(9, '操作类型', 'sys_oper_type', '0', 'admin', sysdate(), '', null, '操作类型列表'); +insert into sys_dict_type values(10, '系统状态', 'sys_common_status', '0', 'admin', sysdate(), '', null, '登录状态列表'); + + +-- ---------------------------- +-- 12、字典数据表 +-- ---------------------------- +drop table if exists sys_dict_data; +create table sys_dict_data +( + dict_code bigint(20) not null auto_increment comment '字典编码', + dict_sort int(4) default 0 comment '字典排序', + dict_label varchar(100) default '' comment '字典标签', + dict_value varchar(100) default '' comment '字典键值', + dict_type varchar(100) default '' comment '字典类型', + css_class varchar(100) default null comment '样式属性(其他样式扩展)', + list_class varchar(100) default null comment '表格回显样式', + is_default char(1) default 'N' comment '是否默认(Y是 N否)', + status char(1) default '0' comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (dict_code) +) engine=innodb auto_increment=100 comment = '字典数据表'; + +insert into sys_dict_data values(1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', sysdate(), '', null, '性别男'); +insert into sys_dict_data values(2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别女'); +insert into sys_dict_data values(3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别未知'); +insert into sys_dict_data values(4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '显示菜单'); +insert into sys_dict_data values(5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '隐藏菜单'); +insert into sys_dict_data values(6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); +insert into sys_dict_data values(8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); +insert into sys_dict_data values(10, 1, '默认', 'DEFAULT', 'sys_job_group', '', '', 'Y', '0', 'admin', sysdate(), '', null, '默认分组'); +insert into sys_dict_data values(11, 2, '系统', 'SYSTEM', 'sys_job_group', '', '', 'N', '0', 'admin', sysdate(), '', null, '系统分组'); +insert into sys_dict_data values(12, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '系统默认是'); +insert into sys_dict_data values(13, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '系统默认否'); +insert into sys_dict_data values(14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', sysdate(), '', null, '通知'); +insert into sys_dict_data values(15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '公告'); +insert into sys_dict_data values(16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '关闭状态'); +insert into sys_dict_data values(18, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '其他操作'); +insert into sys_dict_data values(19, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '新增操作'); +insert into sys_dict_data values(20, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '修改操作'); +insert into sys_dict_data values(21, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '删除操作'); +insert into sys_dict_data values(22, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '授权操作'); +insert into sys_dict_data values(23, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导出操作'); +insert into sys_dict_data values(24, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导入操作'); +insert into sys_dict_data values(25, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '强退操作'); +insert into sys_dict_data values(26, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '生成操作'); +insert into sys_dict_data values(27, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '清空操作'); +insert into sys_dict_data values(28, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(29, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); + + +-- ---------------------------- +-- 13、参数配置表 +-- ---------------------------- +drop table if exists sys_config; +create table sys_config ( + config_id int(5) not null auto_increment comment '参数主键', + config_name varchar(100) default '' comment '参数名称', + config_key varchar(100) default '' comment '参数键名', + config_value varchar(500) default '' comment '参数键值', + config_type char(1) default 'N' comment '系统内置(Y是 N否)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (config_id) +) engine=innodb auto_increment=100 comment = '参数配置表'; + +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-green', 'Y', 'admin', sysdate(), 'admin', sysdate(), '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow'); +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', sysdate(), '', NULL, '初始化密码 123456'); +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-light', 'Y', 'admin', sysdate(), 'admin', sysdate(), '深色主题theme-dark,浅色主题theme-light'); +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', sysdate(), '', NULL, '是否开启验证码功能(true开启,false关闭)'); +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'true', 'Y', 'admin', sysdate(), 'admin', '2023-04-22 00:41:41', '是否开启注册用户功能(true开启,false关闭)'); +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (6, '主题颜色', 'sys.index.theme', '#11A983', 'Y', 'admin', sysdate(), 'admin', sysdate(), "主题颜色"); +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (7, '开启TopNav', 'sys.index.topNav', 'false', 'Y', 'admin', sysdate(), '', NULL, "是否开启TopNav(true开启,false关闭)"); +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (8, '开启Tags-Views', 'sys.index.tagsView', 'true', 'Y', 'admin', sysdate(), '', NULL, "开启Tags-Views功能(true开启,false关闭)"); +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (9, '显示Logo', 'sys.index.sidebarLogo', 'true', 'Y', 'admin', sysdate(), '', NULL, "是否显示Logo(true显示,false不显示)"); +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (10, '固定Header', 'sys.index.fixedHeader', 'true', 'Y', 'admin', sysdate(), '', NULL, "是否固定Header(true开启,false关闭)"); +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (11, '动态标题', 'sys.index.dynamicTitle', 'true', 'Y', 'admin', sysdate(), 'admin', sysdate(), "是否开启动态标题(true开启,false关闭),开启后浏览器标题会根据当前页面的标题动态变化,关闭后浏览器标题将一直显示为系统名称"); +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (12, '用户管理-初始密码修改策略', 'sys.account.initPasswordModify', '1', 'Y', 'admin', sysdate(), 'admin', sysdate(), "0:初始密码修改策略关闭,没有任何提示,1:提醒用户,如果未修改初始密码,则在登录时就会提醒修改密码对话框"); +INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (13, '用户管理-账号密码更新周期', 'sys.account.passwordValidateDays', '0', 'Y', 'admin', sysdate(), 'admin', sysdate(), "密码更新周期(填写数字,数据初始化值为0不限制,若修改必须为大于0小于365的正整数),如果超过这个周期登录系统时,则在登录时就会提醒修改密码对话框"); + +-- ---------------------------- +-- 14、系统访问记录 +-- ---------------------------- +drop table if exists sys_logininfor; +create table sys_logininfor ( + info_id bigint(20) not null auto_increment comment '访问ID', + user_name varchar(50) default '' comment '用户账号', + ipaddr varchar(128) default '' comment '登录IP地址', + login_location varchar(255) default '' comment '登录地点', + browser varchar(50) default '' comment '浏览器类型', + os varchar(50) default '' comment '操作系统', + status char(1) default '0' comment '登录状态(0成功 1失败)', + msg varchar(255) default '' comment '提示消息', + login_time datetime comment '访问时间', + primary key (info_id), + key idx_sys_logininfor_s (status), + key idx_sys_logininfor_lt (login_time) +) engine=innodb auto_increment=100 comment = '系统访问记录'; + + +-- ---------------------------- +-- 15、定时任务调度表 +-- ---------------------------- +drop table if exists sys_job; +create table sys_job ( + job_id bigint(20) not null auto_increment comment '任务ID', + job_name varchar(64) default '' comment '任务名称', + job_group varchar(64) default 'DEFAULT' comment '任务组名', + invoke_target varchar(500) not null comment '调用目标字符串', + cron_expression varchar(255) default '' comment 'cron执行表达式', + misfire_policy varchar(20) default '3' comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + concurrent char(1) default '1' comment '是否并发执行(0允许 1禁止)', + status char(1) default '0' comment '状态(0正常 1暂停)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default '' comment '备注信息', + primary key (job_id, job_name, job_group) +) engine=innodb auto_increment=100 comment = '定时任务调度表'; + +insert into sys_job values(1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '0/10 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); +insert into sys_job values(2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(\'ry\')', '0/15 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); +insert into sys_job values(3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(\'ry\', true, 2000L, 316.50D, 100)', '0/20 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 16、定时任务调度日志表 +-- ---------------------------- +drop table if exists sys_job_log; +create table sys_job_log ( + job_log_id bigint(20) not null auto_increment comment '任务日志ID', + job_name varchar(64) not null comment '任务名称', + job_group varchar(64) not null comment '任务组名', + invoke_target varchar(500) not null comment '调用目标字符串', + job_message varchar(500) comment '日志信息', + status char(1) default '0' comment '执行状态(0正常 1失败)', + exception_info varchar(2000) default '' comment '异常信息', + create_time datetime comment '创建时间', + primary key (job_log_id) +) engine=innodb comment = '定时任务调度日志表'; + + +-- ---------------------------- +-- 17、通知公告表 +-- ---------------------------- +drop table if exists sys_notice; +create table sys_notice ( + notice_id int(4) not null auto_increment comment '公告ID', + notice_title varchar(50) not null comment '公告标题', + notice_type char(1) not null comment '公告类型(1通知 2公告)', + notice_content longblob default null comment '公告内容', + status char(1) default '0' comment '公告状态(0正常 1关闭)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(255) default null comment '备注', + primary key (notice_id) +) engine=innodb auto_increment=10 comment = '通知公告表'; + +-- ---------------------------- +-- 初始化-公告信息表数据 +-- ---------------------------- +insert into sys_notice values('1', '温馨提醒:2018-07-01 若依新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate(), '', null, '管理员'); +insert into sys_notice values('2', '维护通知:2018-07-01 若依系统凌晨维护', '1', '维护内容', '0', 'admin', sysdate(), '', null, '管理员'); diff --git a/sql/postgresql/auth.sql b/sql/postgresql/auth.sql new file mode 100644 index 0000000..591402a --- /dev/null +++ b/sql/postgresql/auth.sql @@ -0,0 +1,78 @@ +-- ---------------------------- +-- 1、oauth_user 表 +-- ---------------------------- +DROP TABLE IF EXISTS oauth_user CASCADE; + +CREATE TABLE oauth_user ( + id bigserial NOT NULL PRIMARY KEY, + uuid VARCHAR(255) NOT NULL, + user_id BIGINT NOT NULL, + source VARCHAR(255) NOT NULL, + access_token VARCHAR(255) NOT NULL, + expire_in INT, + refresh_token VARCHAR(255), + open_id VARCHAR(255), + uid VARCHAR(255), + access_code VARCHAR(255), + union_id VARCHAR(255), + scope VARCHAR(255), + token_type VARCHAR(255), + id_token VARCHAR(255), + mac_algorithm VARCHAR(255), + mac_key VARCHAR(255), + code VARCHAR(255), + oauth_token VARCHAR(255), + oauth_token_secret VARCHAR(255) +); + +COMMENT ON TABLE oauth_user IS '第三方登录'; +COMMENT ON COLUMN oauth_user.id IS '主键'; +COMMENT ON COLUMN oauth_user.uuid IS '第三方系统的唯一ID,详细解释请参考:名词解释'; +COMMENT ON COLUMN oauth_user.user_id IS '用户ID'; +COMMENT ON COLUMN oauth_user.source IS '第三方用户来源,可选值:GITHUB、GITEE、QQ,更多请参考:AuthDefaultSource.java(opens new window)'; +COMMENT ON COLUMN oauth_user.access_token IS '用户的授权令牌'; +COMMENT ON COLUMN oauth_user.expire_in IS '第三方用户的授权令牌的有效期,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.refresh_token IS '刷新令牌,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.open_id IS '第三方用户的 open id,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.uid IS '第三方用户的 ID,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.access_code IS '个别平台的授权信息,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.union_id IS '第三方用户的 union id,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.scope IS '第三方用户授予的权限,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.token_type IS '个别平台的授权信息,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.id_token IS 'id token,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.mac_algorithm IS '小米平台用户的附带属性,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.mac_key IS '小米平台用户的附带属性,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.code IS '用户的授权code,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.oauth_token IS 'Twitter平台用户的附带属性,部分平台可能没有'; +COMMENT ON COLUMN oauth_user.oauth_token_secret IS 'Twitter平台用户的附带属性,部分平台可能没有'; + +-- ---------------------------- +-- 菜单 SQL +-- ---------------------------- +SELECT setval('sys_menu_menu_id_seq', max(menu_id)) FROM sys_menu WHERE menu_id < 100; +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES ('第三方认证', 1, 1, 'oauth', 'system/oauth/index', '', 1, 0, 'C', '0', '0', 'system:oauth:list', 'checkbox', 'admin', CURRENT_TIMESTAMP, '', NULL, '第三方认证菜单'); + +-- 按钮父菜单ID +DO $$ +DECLARE + parentId INTEGER; +BEGIN + SELECT LASTVAL() INTO parentId; + + -- 按钮 SQL + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('第三方认证查询', parentId, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:oauth:query', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('第三方认证新增', parentId, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:oauth:add', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('第三方认证修改', parentId, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:oauth:edit', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('第三方认证删除', parentId, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:oauth:remove', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('第三方认证导出', parentId, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:oauth:export', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); +END $$; \ No newline at end of file diff --git a/sql/postgresql/create_database.sql b/sql/postgresql/create_database.sql new file mode 100644 index 0000000..a0ea1ea --- /dev/null +++ b/sql/postgresql/create_database.sql @@ -0,0 +1 @@ +CREATE DATABASE ry WITH ENCODING 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'en_US.UTF-8' TEMPLATE = template0; \ No newline at end of file diff --git a/sql/postgresql/file.sql b/sql/postgresql/file.sql new file mode 100644 index 0000000..2ab3c7e --- /dev/null +++ b/sql/postgresql/file.sql @@ -0,0 +1,70 @@ +-- ---------------------------- +-- 1、sys_file_info 表 +-- ---------------------------- +DROP TABLE IF EXISTS sys_file_info CASCADE; + +CREATE TABLE sys_file_info ( + file_id BIGSERIAL NOT NULL PRIMARY KEY, + file_name VARCHAR(255) NOT NULL, + file_path VARCHAR(500) NOT NULL, + storage_type VARCHAR(32) NOT NULL, + file_type VARCHAR(50), + file_size BIGINT, + md5 VARCHAR(64), + create_by VARCHAR(64) DEFAULT '', + create_time TIMESTAMP, + update_by VARCHAR(64) DEFAULT '', + update_time TIMESTAMP, + remark VARCHAR(255), + del_flag CHAR(1) DEFAULT '0' +); + +-- 添加唯一约束 +ALTER TABLE sys_file_info ADD CONSTRAINT uk_file_path UNIQUE (file_path); +-- ALTER TABLE sys_file_info ADD CONSTRAINT uk_md5 UNIQUE (md5); + +-- 添加表和列注释 +COMMENT ON TABLE sys_file_info IS '文件信息表'; +COMMENT ON COLUMN sys_file_info.file_id IS '文件主键'; +COMMENT ON COLUMN sys_file_info.file_name IS '原始文件名'; +COMMENT ON COLUMN sys_file_info.file_path IS '统一逻辑路径(/开头)'; +COMMENT ON COLUMN sys_file_info.storage_type IS '存储类型(local/minio/oss)'; +COMMENT ON COLUMN sys_file_info.file_type IS '文件类型/后缀'; +COMMENT ON COLUMN sys_file_info.file_size IS '文件大小(字节)'; +COMMENT ON COLUMN sys_file_info.md5 IS '文件MD5'; +COMMENT ON COLUMN sys_file_info.create_by IS '创建者'; +COMMENT ON COLUMN sys_file_info.create_time IS '创建时间'; +COMMENT ON COLUMN sys_file_info.update_by IS '更新者'; +COMMENT ON COLUMN sys_file_info.update_time IS '更新时间'; +COMMENT ON COLUMN sys_file_info.remark IS '备注'; +COMMENT ON COLUMN sys_file_info.del_flag IS '删除标志(0代表存在 2代表删除)'; + +-- ---------------------------- +-- 菜单 SQL +-- ---------------------------- +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES ('文件管理', 1, 1, 'file', 'system/file/index', 1, 0, 'C', '0', '0', 'system:file:list', 'excel', 'admin', CURRENT_TIMESTAMP, '', NULL, '文件管理菜单'); + +-- 文件管理菜单ID +DO $$ +DECLARE + parentId INTEGER; +BEGIN + SELECT LASTVAL() INTO parentId; + + -- 按钮 SQL + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('文件信息查询', parentId, 1, '#', '', 1, 0, 'F', '0', '0', 'system:file:query', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('文件信息新增', parentId, 2, '#', '', 1, 0, 'F', '0', '0', 'system:file:add', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('文件信息修改', parentId, 3, '#', '', 1, 0, 'F', '0', '0', 'system:file:edit', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('文件信息删除', parentId, 4, '#', '', 1, 0, 'F', '0', '0', 'system:file:remove', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('文件信息导出', parentId, 5, '#', '', 1, 0, 'F', '0', '0', 'system:file:export', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); +END $$; \ No newline at end of file diff --git a/sql/postgresql/flowable.sql b/sql/postgresql/flowable.sql new file mode 100644 index 0000000..ad04df6 --- /dev/null +++ b/sql/postgresql/flowable.sql @@ -0,0 +1,153 @@ +-- sys_deploy_form definition +DROP TABLE IF EXISTS sys_deploy_form; +CREATE TABLE sys_deploy_form ( + id bigserial not null primary key, + form_id bigint, + deploy_id varchar(50) +); +COMMENT ON COLUMN sys_deploy_form.id IS '主键'; +COMMENT ON COLUMN sys_deploy_form.form_id IS '表单主键'; +COMMENT ON COLUMN sys_deploy_form.deploy_id IS '流程实例主键'; + +-- sys_expression definition +DROP TABLE IF EXISTS sys_expression; +CREATE TABLE sys_expression ( + id bigserial not null primary key, + name varchar(50), + expression varchar(255), + data_type varchar(255), + create_time timestamp, + update_time timestamp, + create_by bigint, + update_by bigint, + status smallint DEFAULT 0, + remark varchar(255) +); +COMMENT ON COLUMN sys_expression.id IS '表单主键'; +COMMENT ON COLUMN sys_expression.name IS '表达式名称'; +COMMENT ON COLUMN sys_expression.expression IS '表达式内容'; +COMMENT ON COLUMN sys_expression.data_type IS '表达式类型'; +COMMENT ON COLUMN sys_expression.create_time IS '创建时间'; +COMMENT ON COLUMN sys_expression.update_time IS '更新时间'; +COMMENT ON COLUMN sys_expression.create_by IS '创建人员'; +COMMENT ON COLUMN sys_expression.update_by IS '更新人员'; +COMMENT ON COLUMN sys_expression.status IS '状态'; +COMMENT ON COLUMN sys_expression.remark IS '备注'; + +-- sys_listener definition +DROP TABLE IF EXISTS sys_listener; +CREATE TABLE sys_listener ( + id bigserial not null primary key, + name varchar(128), + type char(2), + event_type varchar(32), + value_type varchar(32), + value varchar(255), + create_time timestamp, + update_time timestamp, + create_by bigint, + update_by bigint, + status smallint DEFAULT 0, + remark varchar(255) +); +COMMENT ON COLUMN sys_listener.id IS '主键'; +COMMENT ON COLUMN sys_listener.name IS '名称'; +COMMENT ON COLUMN sys_listener.type IS '监听类型'; +COMMENT ON COLUMN sys_listener.event_type IS '事件类型'; +COMMENT ON COLUMN sys_listener.value_type IS '值类型'; +COMMENT ON COLUMN sys_listener.value IS '执行内容'; +COMMENT ON COLUMN sys_listener.create_time IS '创建时间'; +COMMENT ON COLUMN sys_listener.update_time IS '更新时间'; +COMMENT ON COLUMN sys_listener.create_by IS '创建人员'; +COMMENT ON COLUMN sys_listener.update_by IS '更新人员'; +COMMENT ON COLUMN sys_listener.status IS '状态'; +COMMENT ON COLUMN sys_listener.remark IS '备注'; + +DO $$ +DECLARE + flowable_parent_id INTEGER; + task_parent_id INTEGER; + process_parent_id INTEGER; + form_parent_id INTEGER; + expression_parent_id INTEGER; + listener_parent_id INTEGER; +BEGIN + -- 流程相关菜单 + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程管理', 0, 6, 'flowable', NULL, NULL, NULL, 1, 0, 'M', '0', '0', '', 'cascader', 'tony', '2021-03-25 11:35:09', 'admin', '2022-12-29 17:39:22', '') + RETURNING menu_id INTO flowable_parent_id; + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程定义', flowable_parent_id, 2, 'definition', 'flowable/definition/index', NULL, NULL, 1, 0, 'C', '0', '0', '', 'job', 'tony', '2021-03-25 13:53:55', 'admin', '2022-12-29 17:40:39', ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('任务管理', 0, 7, 'task', NULL, NULL, NULL, 1, 0, 'M', '0', '0', '', 'dict', 'tony', '2021-03-26 10:53:10', 'admin', '2021-03-29 09:37:40', '') + RETURNING menu_id INTO task_parent_id; + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('待办任务', task_parent_id, 2, 'todo', 'flowable/task/todo/index', NULL, NULL, 1, 1, 'C', '0', '0', '', 'cascader', 'admin', '2021-03-26 10:55:52', 'admin', '2021-03-30 09:26:36', ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('已办任务', task_parent_id, 3, 'finished', 'flowable/task/finished/index', NULL, NULL, 1, 1, 'C', '0', '0', '', 'time-range', 'admin', '2021-03-26 10:57:54', 'admin', '2021-03-30 09:26:50', ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('已发任务', task_parent_id, 1, 'process', 'flowable/task/myProcess/index', NULL, NULL, 1, 1, 'C', '0', '0', '', 'guide', 'admin', '2021-03-30 09:26:23', 'admin', '2022-12-12 09:58:07', '') + RETURNING menu_id INTO process_parent_id; + + + -- These are children of '已发任务' (process_parent_id) + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('新增', process_parent_id, 1, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'system:deployment:add', '#', 'admin', '2021-07-07 14:25:22', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('编辑', process_parent_id, 2, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'system:deployment:edit', '#', 'admin', '2021-07-07 14:25:47', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('删除', process_parent_id, 3, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'system:deployment:remove', '#', 'admin', '2021-07-07 14:26:02', '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程表达式', flowable_parent_id, 3, 'expression', 'flowable/expression/index', NULL, NULL, 1, 1, 'C', '0', '0', 'system:expression:list', 'list', 'admin', '2022-12-12 17:12:19', 'admin', '2022-12-12 17:13:44', '流程达式菜单') + RETURNING menu_id INTO expression_parent_id; + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程达式查询', expression_parent_id, 1, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:query', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程达式新增', expression_parent_id, 2, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:add', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程达式修改', expression_parent_id, 3, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:edit', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程达式删除', expression_parent_id, 4, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:remove', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程达式导出', expression_parent_id, 5, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:export', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程监听', flowable_parent_id, 4, 'listener', 'flowable/listener/index', NULL, NULL, 1, 0, 'C', '0', '0', 'system:listener:list', 'monitor', 'admin', '2022-12-25 11:44:16', 'admin', '2022-12-29 08:59:21', '流程监听菜单') + RETURNING menu_id INTO listener_parent_id; + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程监听查询', listener_parent_id, 1, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:query', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程监听新增', listener_parent_id, 2, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:add', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程监听修改', listener_parent_id, 3, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:edit', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程监听删除', listener_parent_id, 4, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:remove', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('流程监听导出', listener_parent_id, 5, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:export', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +END $$; + +-- 流程相关字段表信息 + +INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES ('表达式类型', 'exp_data_type', '0', 'admin', '2024-03-12 09:03:02', '', NULL, NULL); +INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES ('监听类型', 'sys_listener_type', '0', 'admin', '2022-12-18 22:03:07', '', NULL, NULL); +INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES ('监听值类型', 'sys_listener_value_type', '0', 'admin', '2022-12-18 22:03:39', '', NULL, NULL); +INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES ('监听属性', 'sys_listener_event_type', '0', 'admin', '2022-12-18 22:04:29', '', NULL, NULL); +INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES ('流程分类', 'sys_process_category', '0', 'admin', '2024-03-12 09:08:18', '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (0, '系统指定', 'fixed', 'exp_data_type', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:04:46', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (0, '动态选择', 'dynamic', 'exp_data_type', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:05:02', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (0, '任务监听', '1', 'sys_listener_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:47:26', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (2, '执行监听', '2', 'sys_listener_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:47:37', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (0, 'JAVA类', 'classListener', 'sys_listener_value_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:48:55', 'admin', '2024-09-05 21:38:02', NULL); +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (0, '表达式', 'expressionListener', 'sys_listener_value_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:49:05', 'admin', '2024-09-05 21:38:10', NULL); +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (0, '代理表达式', 'delegateExpressionListener', 'sys_listener_value_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:49:16', 'admin', '2024-09-05 21:38:16', NULL); +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (0, '请假', 'leave', 'sys_process_category', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:08:42', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (0, '报销', 'expense', 'sys_process_category', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:09:02', '', NULL, NULL); diff --git a/sql/postgresql/form.sql b/sql/postgresql/form.sql new file mode 100644 index 0000000..da5315c --- /dev/null +++ b/sql/postgresql/form.sql @@ -0,0 +1,111 @@ +DROP TABLE IF EXISTS form_template; + +CREATE TABLE form_template ( + form_id BIGSERIAL NOT NULL PRIMARY KEY, + form_name VARCHAR(100) NOT NULL, + form_schema JSON, + form_version VARCHAR(10) DEFAULT '1.0.0', + form_status VARCHAR(2) DEFAULT '0', + create_by VARCHAR(64) DEFAULT '', + create_time TIMESTAMP DEFAULT NULL, + update_by VARCHAR(64) DEFAULT '', + update_time TIMESTAMP DEFAULT NULL, + remark VARCHAR(255) DEFAULT NULL, + del_flag CHAR(1) DEFAULT '0' +); + +COMMENT ON TABLE form_template IS '表单模板表'; +COMMENT ON COLUMN form_template.form_id IS '表单ID'; +COMMENT ON COLUMN form_template.form_name IS '表单名称'; +COMMENT ON COLUMN form_template.form_schema IS '表单JSON Schema(vForm配置)'; +COMMENT ON COLUMN form_template.form_version IS '表单版本(语义化版本)'; +COMMENT ON COLUMN form_template.form_status IS '发布状态(0: 草稿, 1: 已发布, 2: 已停用)'; +COMMENT ON COLUMN form_template.create_by IS '创建者'; +COMMENT ON COLUMN form_template.create_time IS '创建时间'; +COMMENT ON COLUMN form_template.update_by IS '更新者'; +COMMENT ON COLUMN form_template.update_time IS '更新时间'; +COMMENT ON COLUMN form_template.remark IS '备注'; +COMMENT ON COLUMN form_template.del_flag IS '删除标志(0代表存在 2代表删除)'; + +DROP TABLE IF EXISTS form_data; + +CREATE TABLE form_data ( + data_id BIGSERIAL NOT NULL PRIMARY KEY, + form_id BIGINT NOT NULL, + form_version VARCHAR(10), + data_content JSON NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'draft', + create_by VARCHAR(64) DEFAULT '', + create_time TIMESTAMP DEFAULT NULL, + update_by VARCHAR(64) DEFAULT '', + update_time TIMESTAMP DEFAULT NULL, + remark VARCHAR(255) DEFAULT NULL, + del_flag CHAR(1) DEFAULT '0' +); + +COMMENT ON TABLE form_data IS '表单数据表'; +COMMENT ON COLUMN form_data.data_id IS '数据ID'; +COMMENT ON COLUMN form_data.form_id IS '关联的表单ID'; +COMMENT ON COLUMN form_data.form_version IS '表单版本(与模板表版本一致)'; +COMMENT ON COLUMN form_data.data_content IS '表单数据内容(JSON格式)'; +COMMENT ON COLUMN form_data.status IS '数据状态(draft, submitted, approved, rejected)'; +COMMENT ON COLUMN form_data.create_by IS '创建者'; +COMMENT ON COLUMN form_data.create_time IS '创建时间'; +COMMENT ON COLUMN form_data.update_by IS '更新者'; +COMMENT ON COLUMN form_data.update_time IS '更新时间'; +COMMENT ON COLUMN form_data.remark IS '备注'; +COMMENT ON COLUMN form_data.del_flag IS '删除标志(0代表存在 2代表删除)'; + +DO $$ +DECLARE + parentId INTEGER; + fileParentId INTEGER; +BEGIN + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单管理', 0, 4, 'formManagement', NULL, NULL, '', 1, 0, 'M', '0', '0', NULL, 'form', 'admin', '2024-02-15 22:40:23', '', NULL, '') + RETURNING menu_id INTO parentId; + + fileParentId := parentId; + + -- 菜单 SQL + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单模板', fileParentId, '1', 'formtemplate', 'form/template/index', 1, 0, 'C', '0', '0', 'form:template:list', '#', 'admin', CURRENT_TIMESTAMP, '', null, '表单模板菜单') + RETURNING menu_id INTO parentId; + + -- 按钮 SQL + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单模板查询', parentId, '1', '#', '', 1, 0, 'F', '0', '0', 'form:template:query', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单模板新增', parentId, '2', '#', '', 1, 0, 'F', '0', '0', 'form:template:add', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单模板修改', parentId, '3', '#', '', 1, 0, 'F', '0', '0', 'form:template:edit', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单模板删除', parentId, '4', '#', '', 1, 0, 'F', '0', '0', 'form:template:remove', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单模板导出', parentId, '5', '#', '', 1, 0, 'F', '0', '0', 'form:template:export', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + + -- 菜单 SQL + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单数据', fileParentId, '1', 'formdata', 'form/data/index', 1, 0, 'C', '0', '0', 'form:data:list', '#', 'admin', CURRENT_TIMESTAMP, '', null, '表单数据菜单') + RETURNING menu_id INTO parentId; + + -- 按钮 SQL + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单数据查询', parentId, '1', '#', '', 1, 0, 'F', '0', '0', 'form:data:query', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单数据新增', parentId, '2', '#', '', 1, 0, 'F', '0', '0', 'form:data:add', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单数据修改', parentId, '3', '#', '', 1, 0, 'F', '0', '0', 'form:data:edit', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单数据删除', parentId, '4', '#', '', 1, 0, 'F', '0', '0', 'form:data:remove', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('表单数据导出', parentId, '5', '#', '', 1, 0, 'F', '0', '0', 'form:data:export', '#', 'admin', CURRENT_TIMESTAMP, '', null, ''); +END $$; \ No newline at end of file diff --git a/sql/postgresql/gen.sql b/sql/postgresql/gen.sql new file mode 100644 index 0000000..9b04bad --- /dev/null +++ b/sql/postgresql/gen.sql @@ -0,0 +1,169 @@ +-- ---------------------------- +-- 18、代码生成业务表 +-- ---------------------------- +DROP TABLE IF EXISTS gen_table; +CREATE TABLE gen_table ( + table_id bigserial PRIMARY KEY, + table_name varchar(200) DEFAULT '' , + table_comment varchar(500) NOT NULL , + table_alias varchar(200) NOT NULL , + have_sub_column char(1) DEFAULT '0', + sub_table_name varchar(64) DEFAULT NULL, + sub_table_fk_name varchar(64) DEFAULT NULL, + class_name varchar(100) DEFAULT '' , + tpl_category varchar(200) DEFAULT 'crud', + tpl_web_type varchar(200) DEFAULT 'element-plus', + package_name varchar(100) DEFAULT NULL, + module_name varchar(30) DEFAULT NULL, + business_name varchar(30) DEFAULT NULL, + function_name varchar(50) DEFAULT NULL, + function_author varchar(50) DEFAULT NULL, + gen_type char(1) DEFAULT '0', + gen_path varchar(200) DEFAULT '/' , + options varchar(1000) DEFAULT NULL, + create_by varchar(64) DEFAULT '' , + create_time timestamp DEFAULT CURRENT_TIMESTAMP, + update_by varchar(64) DEFAULT '' , + update_time timestamp DEFAULT CURRENT_TIMESTAMP, + remark varchar(500) DEFAULT NULL +); + +COMMENT ON TABLE gen_table IS '代码生成业务表'; +COMMENT ON COLUMN gen_table.table_id IS '编号'; +COMMENT ON COLUMN gen_table.table_name IS '表名称'; +COMMENT ON COLUMN gen_table.table_alias IS '表别名'; +COMMENT ON COLUMN gen_table.table_comment IS '表描述'; +COMMENT ON COLUMN gen_table.have_sub_column IS '是否含有关联字段'; +COMMENT ON COLUMN gen_table.sub_table_name IS '关联子表的表名'; +COMMENT ON COLUMN gen_table.sub_table_fk_name IS '子表关联的外键名'; +COMMENT ON COLUMN gen_table.class_name IS '实体类名称'; +COMMENT ON COLUMN gen_table.tpl_category IS '使用的模板(crud单表操作 tree树表操作)'; +COMMENT ON COLUMN gen_table.tpl_web_type IS '使用的模板类型'; +COMMENT ON COLUMN gen_table.package_name IS '生成包路径'; +COMMENT ON COLUMN gen_table.module_name IS '生成模块名'; +COMMENT ON COLUMN gen_table.business_name IS '生成业务名'; +COMMENT ON COLUMN gen_table.function_name IS '生成功能名'; +COMMENT ON COLUMN gen_table.function_author IS '生成功能作者'; +COMMENT ON COLUMN gen_table.gen_type IS '生成代码方式(0zip压缩包 1自定义路径)'; +COMMENT ON COLUMN gen_table.gen_path IS '生成路径(不填默认项目路径)'; +COMMENT ON COLUMN gen_table.options IS '其它生成选项'; +COMMENT ON COLUMN gen_table.create_by IS '创建者'; +COMMENT ON COLUMN gen_table.create_time IS '创建时间'; +COMMENT ON COLUMN gen_table.update_by IS '更新者'; +COMMENT ON COLUMN gen_table.update_time IS '更新时间'; +COMMENT ON COLUMN gen_table.remark IS '备注'; + +-- ---------------------------- +-- 19、代码生成业务表字段 +-- ---------------------------- +DROP TABLE IF EXISTS gen_table_column; +CREATE TABLE gen_table_column ( + column_id bigserial PRIMARY KEY, + table_id bigint DEFAULT NULL, + column_name varchar(200) DEFAULT NULL, + column_comment varchar(500) DEFAULT NULL, + column_type varchar(100) DEFAULT NULL, + java_type varchar(500) DEFAULT NULL, + java_field varchar(200) DEFAULT NULL, + is_pk char(1) DEFAULT NULL, + is_increment char(1) DEFAULT NULL, + is_required char(1) DEFAULT NULL, + is_insert char(1) DEFAULT NULL, + is_edit char(1) DEFAULT NULL, + is_list char(1) DEFAULT NULL, + is_query char(1) DEFAULT NULL, + query_type varchar(200) DEFAULT 'EQ', + html_type varchar(200) DEFAULT NULL, + dict_type varchar(200) DEFAULT '' , + sort int DEFAULT NULL, + sub_column_table_name varchar(200) DEFAULT NULL, + sub_column_fk_name varchar(200) DEFAULT NULL, + sub_column_name varchar(200) DEFAULT NULL, + sub_column_java_field varchar(200) DEFAULT NULL, + sub_column_java_type varchar(255) DEFAULT NULL, + create_by varchar(64) DEFAULT '' , + create_time timestamp DEFAULT CURRENT_TIMESTAMP, + update_by varchar(64) DEFAULT '' , + update_time timestamp DEFAULT CURRENT_TIMESTAMP +); + +COMMENT ON TABLE gen_table_column IS '代码生成业务表字段'; +COMMENT ON COLUMN gen_table_column.column_id IS '编号'; +COMMENT ON COLUMN gen_table_column.table_id IS '归属表编号'; +COMMENT ON COLUMN gen_table_column.column_name IS '列名称'; +COMMENT ON COLUMN gen_table_column.column_comment IS '列描述'; +COMMENT ON COLUMN gen_table_column.column_type IS '列类型'; +COMMENT ON COLUMN gen_table_column.java_type IS 'JAVA类型'; +COMMENT ON COLUMN gen_table_column.java_field IS 'JAVA字段名'; +COMMENT ON COLUMN gen_table_column.is_pk IS '是否主键(1是)'; +COMMENT ON COLUMN gen_table_column.is_increment IS '是否自增(1是)'; +COMMENT ON COLUMN gen_table_column.is_required IS '是否必填(1是)'; +COMMENT ON COLUMN gen_table_column.is_insert IS '是否为插入字段(1是)'; +COMMENT ON COLUMN gen_table_column.is_edit IS '是否编辑字段(1是)'; +COMMENT ON COLUMN gen_table_column.is_list IS '是否列表字段(1是)'; +COMMENT ON COLUMN gen_table_column.is_query IS '是否查询字段(1是)'; +COMMENT ON COLUMN gen_table_column.query_type IS '查询方式(等于、不等于、大于、小于、范围)'; +COMMENT ON COLUMN gen_table_column.html_type IS '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)'; +COMMENT ON COLUMN gen_table_column.dict_type IS '字典类型'; +COMMENT ON COLUMN gen_table_column.sort IS '排序'; +COMMENT ON COLUMN gen_table_column.sub_column_table_name IS '关联表名称'; +COMMENT ON COLUMN gen_table_column.sub_column_fk_name IS '关联字段名称'; +COMMENT ON COLUMN gen_table_column.sub_column_name IS '映射字段名称'; +COMMENT ON COLUMN gen_table_column.sub_column_java_field IS '映射字段JAVA字段名'; +COMMENT ON COLUMN gen_table_column.sub_column_java_type IS '映射字段JAVA类型'; +COMMENT ON COLUMN gen_table_column.create_by IS '创建者'; +COMMENT ON COLUMN gen_table_column.create_time IS '创建时间'; +COMMENT ON COLUMN gen_table_column.update_by IS '更新者'; +COMMENT ON COLUMN gen_table_column.update_time IS '更新时间'; + +DROP TABLE IF EXISTS gen_join_table; +CREATE TABLE gen_join_table ( + table_id bigserial, + left_table_id bigint DEFAULT NULL, + right_table_id bigint DEFAULT NULL, + left_table_alias varchar(200) DEFAULT NULL, + right_table_alias varchar(200) DEFAULT NULL, + left_table_fk varchar(200) DEFAULT NULL, + right_table_fk varchar(200) DEFAULT NULL, + join_type varchar(200) DEFAULT NULL, + join_columns varchar(500) DEFAULT NULL, + order_num varchar(64) DEFAULT NULL, + new_table_id bigint DEFAULT NULL, + PRIMARY KEY (table_id, right_table_id, left_table_id) +); + +COMMENT ON TABLE gen_join_table IS '代码生成关联表'; +COMMENT ON COLUMN gen_join_table.table_id IS '表编号'; +COMMENT ON COLUMN gen_join_table.left_table_id IS '左表名称'; +COMMENT ON COLUMN gen_join_table.right_table_id IS '右表编号'; +COMMENT ON COLUMN gen_join_table.left_table_alias IS '左表别名'; +COMMENT ON COLUMN gen_join_table.right_table_alias IS '右表别名'; +COMMENT ON COLUMN gen_join_table.left_table_fk IS '左表关联键'; +COMMENT ON COLUMN gen_join_table.right_table_fk IS '右表关联键'; +COMMENT ON COLUMN gen_join_table.join_type IS '关联类型'; +COMMENT ON COLUMN gen_join_table.join_columns IS '关联字段'; +COMMENT ON COLUMN gen_join_table.order_num IS '序号'; +COMMENT ON COLUMN gen_join_table.new_table_id IS '新表编号'; + +-- 插入菜单数据 +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES (116, '代码生成', 3, 2, 'gen', 'tool/gen/index', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', CURRENT_TIMESTAMP, '', NULL, '代码生成菜单'); + +-- 代码生成按钮 +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES (1055, '生成查询', 116, 1, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES (1056, '生成修改', 116, 2, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES (1057, '生成删除', 116, 3, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES (1058, '导入代码', 116, 4, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES (1059, '预览代码', 116, 5, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES (1060, '生成代码', 116, 6, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); \ No newline at end of file diff --git a/sql/postgresql/message.sql b/sql/postgresql/message.sql new file mode 100644 index 0000000..7293ee4 --- /dev/null +++ b/sql/postgresql/message.sql @@ -0,0 +1,178 @@ +-- ---------------------------- +-- 消息系统 +-- ---------------------------- +-- 消息表 +DROP TABLE IF EXISTS message_system; +CREATE TABLE message_system ( + message_id bigserial PRIMARY KEY, + message_title varchar(64) DEFAULT NULL, + create_by varchar(64) DEFAULT NULL, + create_time timestamp DEFAULT NULL, + send_mode varchar(100) DEFAULT NULL, + code varchar(100) DEFAULT NULL, + message_content text DEFAULT NULL, + message_recipient varchar(100) DEFAULT NULL, + message_status varchar(64) DEFAULT NULL, + message_type varchar(64) DEFAULT NULL, + update_by varchar(64) DEFAULT NULL, + update_time timestamp DEFAULT NULL, + remark varchar(500) DEFAULT NULL +); + +COMMENT ON TABLE message_system IS '消息表'; +COMMENT ON COLUMN message_system.message_id IS '主键'; +COMMENT ON COLUMN message_system.message_title IS '标题'; +COMMENT ON COLUMN message_system.create_by IS '创建者'; +COMMENT ON COLUMN message_system.create_time IS '创建时间'; +COMMENT ON COLUMN message_system.send_mode IS '发送方式(0平台 1手机号 2 邮箱)'; +COMMENT ON COLUMN message_system.code IS '号码'; +COMMENT ON COLUMN message_system.message_content IS '消息内容'; +COMMENT ON COLUMN message_system.message_recipient IS '接收人'; +COMMENT ON COLUMN message_system.message_status IS '消息状态(0未读 1已读)'; +COMMENT ON COLUMN message_system.message_type IS '消息类型'; +COMMENT ON COLUMN message_system.update_by IS '更新者'; +COMMENT ON COLUMN message_system.update_time IS '更新时间'; +COMMENT ON COLUMN message_system.remark IS '备注'; + +-- 模版表 +DROP TABLE IF EXISTS message_template; +CREATE TABLE message_template ( + template_id bigserial PRIMARY KEY, + template_name varchar(100) DEFAULT NULL, + template_code varchar(64) DEFAULT NULL, + template_type varchar(64) DEFAULT NULL, + template_content text DEFAULT NULL, + template_variable text DEFAULT NULL, + create_by varchar(64) DEFAULT NULL, + create_time timestamp DEFAULT NULL, + update_by varchar(64) DEFAULT NULL, + update_time timestamp DEFAULT NULL, + remark varchar(500) DEFAULT NULL +); + +COMMENT ON TABLE message_template IS '模版表'; +COMMENT ON COLUMN message_template.template_id IS '主键'; +COMMENT ON COLUMN message_template.template_name IS '模版名称'; +COMMENT ON COLUMN message_template.template_code IS '模版CODE'; +COMMENT ON COLUMN message_template.template_type IS '模版类型'; +COMMENT ON COLUMN message_template.template_content IS '模版内容'; +COMMENT ON COLUMN message_template.template_variable IS '变量属性'; +COMMENT ON COLUMN message_template.create_by IS '创建者'; +COMMENT ON COLUMN message_template.create_time IS '创建时间'; +COMMENT ON COLUMN message_template.update_by IS '更新者'; +COMMENT ON COLUMN message_template.update_time IS '更新时间'; +COMMENT ON COLUMN message_template.remark IS '备注'; + +-- 变量表 +DROP TABLE IF EXISTS message_variable; +CREATE TABLE message_variable ( + variable_id bigserial PRIMARY KEY, + variable_name varchar(100) DEFAULT NULL, + variable_type varchar(64) DEFAULT NULL, + variable_content varchar(100) DEFAULT NULL, + create_by varchar(64) DEFAULT NULL, + create_time timestamp DEFAULT NULL, + update_by varchar(64) DEFAULT NULL, + update_time timestamp DEFAULT NULL, + remark varchar(500) DEFAULT NULL +); + +COMMENT ON TABLE message_variable IS '变量表'; +COMMENT ON COLUMN message_variable.variable_id IS '主键'; +COMMENT ON COLUMN message_variable.variable_name IS '变量名称'; +COMMENT ON COLUMN message_variable.variable_type IS '变量类型'; +COMMENT ON COLUMN message_variable.variable_content IS '变量内容'; +COMMENT ON COLUMN message_variable.create_by IS '创建者'; +COMMENT ON COLUMN message_variable.create_time IS '创建时间'; +COMMENT ON COLUMN message_variable.update_by IS '更新者'; +COMMENT ON COLUMN message_variable.update_time IS '更新时间'; +COMMENT ON COLUMN message_variable.remark IS '备注'; + +-- ---------------------------- +-- 消息系统 +-- ---------------------------- +-- 消息表 +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES ('消息系统', 0, 6, 'modelMessage', NULL, NULL, '', 1, 0, 'M', '0', '0', '', 'message', 'admin', '2024-12-31 11:57:29', 'xl', '2025-01-03 15:48:44', ''); + +DO $$ +DECLARE + parentId INTEGER; + messageParentId INTEGER; +BEGIN + SELECT LASTVAL() INTO parentId; + SELECT LASTVAL() INTO messageParentId; + + + -- 消息管理菜单 + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('消息管理', messageParentId, 0, 'messageSystem', 'modelMessage/messageSystem/index', NULL, '', 1, 0, 'C', '0', '0', 'modelMessage:messageSystem:list', '#', 'admin', '2024-12-21 15:00:31', 'admin', '2024-12-31 15:04:49', '消息管理菜单') + RETURNING menu_id INTO parentId; + + -- 消息管理按钮 + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('消息管理查询', parentId, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:query', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('消息管理新增', parentId, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:add', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('消息管理修改', parentId, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:edit', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('消息管理删除', parentId, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:remove', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('消息管理导出', parentId, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:export', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); + + -- 模版管理菜单 + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('模版管理', messageParentId, 1, 'template', 'modelMessage/template/index', NULL, '', 1, 0, 'C', '0', '0', 'modelMessage:template:list', '#', 'admin', '2024-12-31 14:59:52', '', NULL, '模版管理菜单') + RETURNING menu_id INTO parentId; + + -- 模版管理按钮 + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('模版管理查询', parentId, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:query', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('模版管理新增', parentId, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:add', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('模版管理修改', parentId, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:edit', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('模版管理删除', parentId, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:remove', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('模版管理导出', parentId, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:export', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); + + -- 变量管理菜单 + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('变量管理', messageParentId, 2, 'variable', 'modelMessage/variable/index', NULL, '', 1, 0, 'C', '0', '0', 'modelMessage:variable:list', '#', 'admin', '2024-12-31 15:01:50', 'admin', '2024-12-31 15:04:56', '变量管理菜单') + RETURNING menu_id INTO parentId; + + -- 变量管理按钮 + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('变量管理查询', parentId, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:query', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('变量管理新增', parentId, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:add', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('变量管理修改', parentId, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:edit', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('变量管理删除', parentId, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:remove', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('变量管理导出', parentId, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:export', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); + +END $$; + +-- 消息系统字典 +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (142, 0, '未读', '0', 'message_status', NULL, 'primary', 'N', '0', 'xl', '2024-12-21 15:13:02', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (143, 1, '已读', '1', 'message_status', NULL, 'success', 'N', '0', 'xl', '2024-12-21 15:13:15', 'xl', '2024-12-21 15:13:22', NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (144, 0, '平台', '0', 'send_mode', NULL, 'primary', 'N', '0', 'xl', '2024-12-25 09:40:01', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (145, 1, '短信', '1', 'send_mode', NULL, 'success', 'N', '0', 'xl', '2024-12-25 09:40:16', 'xl', '2025-01-01 10:12:07', NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (146, 2, '邮件', '2', 'send_mode', NULL, 'warning', 'N', '0', 'xl', '2024-12-25 09:40:28', 'xl', '2025-01-01 10:12:14', NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (147, 0, '验证码', '0', 'template_type', NULL, 'primary', 'N', '0', 'xl', '2025-01-03 09:22:52', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (148, 0, '通知', '0', 'message_type', NULL, 'primary', 'N', '0', 'xl', '2025-01-03 15:12:29', '', NULL, NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (149, 0, '提示', '1', 'message_type', NULL, 'success', 'N', '0', 'xl', '2025-01-03 15:12:41', 'xl', '2025-01-03 15:12:45', NULL); +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (150, 1, '推广', '1', 'template_type', NULL, 'success', 'N', '0', 'xl', '2025-01-03 15:13:15', '', NULL, NULL); \ No newline at end of file diff --git a/sql/postgresql/online.sql b/sql/postgresql/online.sql new file mode 100644 index 0000000..7a86270 --- /dev/null +++ b/sql/postgresql/online.sql @@ -0,0 +1,131 @@ +-- ---------------------------- +-- 18、在线接口表 +-- ---------------------------- +DROP TABLE IF EXISTS online_mb; + +CREATE TABLE online_mb ( + mb_id bigserial PRIMARY KEY, + tag varchar(255) NULL, + tag_id varchar(255) NULL, + parameter_type varchar(255) NULL, + result_map varchar(255) NULL, + sql_text varchar(255) NULL, + path varchar(255) NULL, + method varchar(255) NULL, + result_type varchar(255) NULL, + actuator varchar(255) NULL, + user_id char(1) NULL, + dept_id char(1) NULL, + permission_type varchar(255) NULL, + permission_value varchar(255) NULL, + del_flag varchar(10) NOT NULL DEFAULT '0' +); + +COMMENT ON COLUMN online_mb.mb_id IS '主键'; +COMMENT ON COLUMN online_mb.tag IS '标签名'; +COMMENT ON COLUMN online_mb.tag_id IS '标签id'; +COMMENT ON COLUMN online_mb.parameter_type IS '参数类型'; +COMMENT ON COLUMN online_mb.result_map IS '结果类型'; +COMMENT ON COLUMN online_mb.sql_text IS 'sql语句'; +COMMENT ON COLUMN online_mb.path IS '请求路径'; +COMMENT ON COLUMN online_mb.method IS '请求方式'; +COMMENT ON COLUMN online_mb.result_type IS '响应类型'; +COMMENT ON COLUMN online_mb.actuator IS '执行器'; +COMMENT ON COLUMN online_mb.user_id IS '是否需要userId'; +COMMENT ON COLUMN online_mb.dept_id IS '是否需要deptId'; +COMMENT ON COLUMN online_mb.permission_type IS '许可类型'; +COMMENT ON COLUMN online_mb.permission_value IS '许可值'; +COMMENT ON COLUMN online_mb.del_flag IS '删除标志(0代表存在 1代表删除)'; + +-- 插入菜单数据 +SELECT setval('sys_menu_menu_id_seq', max(menu_id)) FROM sys_menu WHERE menu_id < 100; +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES ('Online', 0, 5, 'onlinedev', NULL, NULL, '', 1, 0, 'M', '0', '0', NULL, 'international', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + +-- 获取插入的父菜单ID +DO $$ +DECLARE + parentId bigint; +BEGIN + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('Online', 0, 5, 'onlinedev', NULL, NULL, '', 1, 0, 'M', '0', '0', NULL, 'international', 'admin', CURRENT_TIMESTAMP, '', NULL, '') + RETURNING menu_id INTO parentId; + + -- 菜单 SQL + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('mybatis在线接口', parentId, '1', 'mb', 'online/mb/index', '', 1, 0, 'C', '0', '0', 'online:mb:list', 'code', 'admin', CURRENT_TIMESTAMP, '', NULL, 'mybatis在线接口菜单'); + + -- 获取插入的子菜单ID + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('数据库', parentId, 1, 'db', 'online/db/index', NULL, '', 1, 0, 'C', '0', '0', 'admin', 'table', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '') + RETURNING menu_id INTO parentId; + + -- 按钮 SQL + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('mybatis在线接口查询', parentId, '1', '#', '', '', 1, 0, 'F', '0', '0', 'online:mb:query', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('mybatis在线接口新增', parentId, '2', '#', '', '', 1, 0, 'F', '0', '0', 'online:mb:add', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('mybatis在线接口修改', parentId, '3', '#', '', '', 1, 0, 'F', '0', '0', 'online:mb:edit', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('mybatis在线接口删除', parentId, '4', '#', '', '', 1, 0, 'F', '0', '0', 'online:mb:remove', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('mybatis在线接口导出', parentId, '5', '#', '', '', 1, 0, 'F', '0', '0', 'online:mb:export', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); +END $$; + +-- 插入字典类型 +INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +VALUES ('请求方式', 'online_api_method', '0', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, NULL); + +INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +VALUES ('标签名', 'online_api_tag', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +VALUES ('响应类型', 'online_api_result', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +VALUES ('执行器', 'online_api_actuator', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +-- 插入字典数据 +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'POST', 'POST', 'online_api_method', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'GET', 'GET', 'online_api_method', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'PUT', 'PUT', 'online_api_method', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'DELETE', 'DELETE', 'online_api_method', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'select', 'select', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'update', 'update', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'insert', 'insert', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'delete', 'delete', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'selectList', 'selectList', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'insert', 'insert', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'selectOne', 'selectOne', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'update', 'update', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); + +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +VALUES (0, 'delete', 'delete', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '', NULL, NULL); \ No newline at end of file diff --git a/sql/postgresql/pay.sql b/sql/postgresql/pay.sql new file mode 100644 index 0000000..6e999dd --- /dev/null +++ b/sql/postgresql/pay.sql @@ -0,0 +1,128 @@ +-- ---------------------------- +-- 订单表 +-- ---------------------------- +DROP TABLE IF EXISTS pay_order; +CREATE TABLE pay_order ( + order_id bigserial not null primary key, + order_number varchar(255) NULL DEFAULT NULL, + third_number varchar(255) NULL DEFAULT NULL, + order_status varchar(255) NULL DEFAULT NULL, + total_amount varchar(255) NULL DEFAULT NULL, + actual_amount varchar(255) NULL DEFAULT NULL, + order_content varchar(255) NULL DEFAULT NULL, + order_message varchar(255) NULL DEFAULT NULL, + create_by varchar(64) default '', + create_time timestamp default CURRENT_TIMESTAMP, + update_by varchar(64) default '', + update_time timestamp default CURRENT_TIMESTAMP, + remark varchar(500) default null, + pay_type varchar(255) NULL DEFAULT NULL, -- 支付方式 + pay_time timestamp default NULL, -- 支付时间 + pay_by varchar(64) default '' -- 支付人 +); + +COMMENT ON COLUMN pay_order.order_id IS '订单id'; +COMMENT ON COLUMN pay_order.order_number IS '订单号'; +COMMENT ON COLUMN pay_order.third_number IS '第三方订单号'; +COMMENT ON COLUMN pay_order.order_status IS '订单状态'; +COMMENT ON COLUMN pay_order.total_amount IS '订单总金额'; +COMMENT ON COLUMN pay_order.actual_amount IS '实际支付金额'; +COMMENT ON COLUMN pay_order.order_content IS '订单内容'; +COMMENT ON COLUMN pay_order.order_message IS '负载信息'; +COMMENT ON COLUMN pay_order.create_by IS '创建者'; +COMMENT ON COLUMN pay_order.create_time IS '创建时间'; +COMMENT ON COLUMN pay_order.update_by IS '更新者'; +COMMENT ON COLUMN pay_order.update_time IS '更新时间'; +COMMENT ON COLUMN pay_order.remark IS '备注'; +COMMENT ON COLUMN pay_order.pay_type IS '支付方式'; +COMMENT ON COLUMN pay_order.pay_time IS '支付时间'; +COMMENT ON COLUMN pay_order.pay_by IS '支付人'; + +-- ---------------------------- +-- 发票表 +-- ---------------------------- +DROP TABLE IF EXISTS pay_invoice; +CREATE TABLE pay_invoice ( + invoice_id bigserial not null primary key, + order_number varchar(255) NULL DEFAULT NULL, + invoice_type varchar(255) NULL DEFAULT NULL, + invoice_header varchar(255) NULL DEFAULT NULL, + invoice_number varchar(255) NULL DEFAULT NULL, + invoice_phone varchar(255) NULL DEFAULT NULL, + invoice_email varchar(255) NULL DEFAULT NULL, + invoice_remark varchar(255) NULL DEFAULT NULL, + create_by varchar(64) default '', + create_time timestamp default CURRENT_TIMESTAMP, + update_by varchar(64) default '', + update_time timestamp default CURRENT_TIMESTAMP, + remark varchar(500) default null +); + +COMMENT ON COLUMN pay_invoice.invoice_id IS '发票id'; +COMMENT ON COLUMN pay_invoice.order_number IS '订单号'; +COMMENT ON COLUMN pay_invoice.invoice_type IS '发票类型'; +COMMENT ON COLUMN pay_invoice.invoice_header IS '发票抬头'; +COMMENT ON COLUMN pay_invoice.invoice_number IS '纳税人识别号'; +COMMENT ON COLUMN pay_invoice.invoice_phone IS '收票人手机号'; +COMMENT ON COLUMN pay_invoice.invoice_email IS '收票人邮箱'; +COMMENT ON COLUMN pay_invoice.invoice_remark IS '发票备注'; +COMMENT ON COLUMN pay_invoice.create_by IS '创建者'; +COMMENT ON COLUMN pay_invoice.create_time IS '创建时间'; +COMMENT ON COLUMN pay_invoice.update_by IS '更新者'; +COMMENT ON COLUMN pay_invoice.update_time IS '更新时间'; +COMMENT ON COLUMN pay_invoice.remark IS '备注'; + +SELECT setval('sys_menu_menu_id_seq', max(menu_id)) FROM sys_menu WHERE menu_id < 100; +-- 插入支付管理菜单 +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES ('支付管理', 0, 4, 'pay', NULL, NULL, '', 1, 0, 'M', '0', '0', NULL, 'money', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); +DO $$ +DECLARE + parentId INTEGER; + payParentId INTEGER; +BEGIN + SELECT LASTVAL() INTO parentId; + SELECT LASTVAL() INTO payParentId; + + -- 插入订单菜单 + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('订单', payParentId, '1', 'order', 'pay/order/index', '', 1, 0, 'C', '0', '0', 'pay:order:list', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, '订单菜单') + RETURNING menu_id INTO parentId; + + -- 插入订单按钮 + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('订单查询', parentId, '1', '#', '', '', 1, 0, 'F', '0', '0', 'pay:order:query', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('订单新增', parentId, '2', '#', '', '', 1, 0, 'F', '0', '0', 'pay:order:add', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('订单修改', parentId, '3', '#', '', '', 1, 0, 'F', '0', '0', 'pay:order:edit', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('订单删除', parentId, '4', '#', '', '', 1, 0, 'F', '0', '0', 'pay:order:remove', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('订单导出', parentId, '5', '#', '', '', 1, 0, 'F', '0', '0', 'pay:order:export', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + -- 插入发票菜单 + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('发票', parentId, '1', 'invoice', 'pay/invoice/index', '', 1, 0, 'C', '0', '0', 'pay:invoice:list', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, '发票菜单') + RETURNING menu_id INTO parentId; + + -- 插入发票按钮 + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('发票查询', payParentId, '1', '#', '', '', 1, 0, 'F', '0', '0', 'pay:invoice:query', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('发票新增', parentId, '2', '#', '', '', 1, 0, 'F', '0', '0', 'pay:invoice:add', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('发票修改', parentId, '3', '#', '', '', 1, 0, 'F', '0', '0', 'pay:invoice:edit', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('发票删除', parentId, '4', '#', '', '', 1, 0, 'F', '0', '0', 'pay:invoice:remove', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); + + INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) + VALUES ('发票导出', parentId, '5', '#', '', '', 1, 0, 'F', '0', '0', 'pay:invoice:export', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, ''); +END $$; \ No newline at end of file diff --git a/sql/postgresql/quartz.sql b/sql/postgresql/quartz.sql new file mode 100644 index 0000000..d499e02 --- /dev/null +++ b/sql/postgresql/quartz.sql @@ -0,0 +1,263 @@ +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + +-- ---------------------------- +-- 1、存储每一个已配置的 jobDetail 的详细信息 +-- ---------------------------- + +CREATE TABLE QRTZ_JOB_DETAILS ( + sched_name varchar(120) NOT NULL, + job_name varchar(200) NOT NULL, + job_group varchar(200) NOT NULL, + description varchar(250) NULL, + job_class_name varchar(250) NOT NULL, + is_durable varchar(1) NOT NULL, + is_nonconcurrent varchar(1) NOT NULL, + is_update_data varchar(1) NOT NULL, + requests_recovery varchar(1) NOT NULL, + job_data bytea NULL, + PRIMARY KEY (sched_name, job_name, job_group) +); + +COMMENT ON COLUMN QRTZ_JOB_DETAILS.sched_name IS '调度名称'; +COMMENT ON COLUMN QRTZ_JOB_DETAILS.job_name IS '任务名称'; +COMMENT ON COLUMN QRTZ_JOB_DETAILS.job_group IS '任务组名'; +COMMENT ON COLUMN QRTZ_JOB_DETAILS.description IS '相关介绍'; +COMMENT ON COLUMN QRTZ_JOB_DETAILS.job_class_name IS '执行任务类名称'; +COMMENT ON COLUMN QRTZ_JOB_DETAILS.is_durable IS '是否持久化'; +COMMENT ON COLUMN QRTZ_JOB_DETAILS.is_nonconcurrent IS '是否并发'; +COMMENT ON COLUMN QRTZ_JOB_DETAILS.is_update_data IS '是否更新数据'; +COMMENT ON COLUMN QRTZ_JOB_DETAILS.requests_recovery IS '是否接受恢复执行'; +COMMENT ON COLUMN QRTZ_JOB_DETAILS.job_data IS '存放持久化job对象'; + +-- ---------------------------- +-- 2、 存储已配置的 Trigger 的信息 +-- ---------------------------- +CREATE TABLE QRTZ_TRIGGERS ( + sched_name varchar(120) NOT NULL, + trigger_name varchar(200) NOT NULL, + trigger_group varchar(200) NOT NULL, + job_name varchar(200) NOT NULL, + job_group varchar(200) NOT NULL, + description varchar(250) NULL, + next_fire_time bigint NULL, + prev_fire_time bigint NULL, + priority integer NULL, + trigger_state varchar(16) NOT NULL, + trigger_type varchar(8) NOT NULL, + start_time bigint NOT NULL, + end_time bigint NULL, + calendar_name varchar(200) NULL, + misfire_instr smallint NULL, + job_data bytea NULL, + PRIMARY KEY (sched_name, trigger_name, trigger_group), + FOREIGN KEY (sched_name, job_name, job_group) REFERENCES QRTZ_JOB_DETAILS(sched_name, job_name, job_group) +); + +COMMENT ON COLUMN QRTZ_TRIGGERS.sched_name IS '调度名称'; +COMMENT ON COLUMN QRTZ_TRIGGERS.trigger_name IS '触发器的名字'; +COMMENT ON COLUMN QRTZ_TRIGGERS.trigger_group IS '触发器所属组的名字'; +COMMENT ON COLUMN QRTZ_TRIGGERS.job_name IS 'qrtz_job_details表job_name的外键'; +COMMENT ON COLUMN QRTZ_TRIGGERS.job_group IS 'qrtz_job_details表job_group的外键'; +COMMENT ON COLUMN QRTZ_TRIGGERS.description IS '相关介绍'; +COMMENT ON COLUMN QRTZ_TRIGGERS.next_fire_time IS '上一次触发时间(毫秒)'; +COMMENT ON COLUMN QRTZ_TRIGGERS.prev_fire_time IS '下一次触发时间(默认为-1表示不触发)'; +COMMENT ON COLUMN QRTZ_TRIGGERS.priority IS '优先级'; +COMMENT ON COLUMN QRTZ_TRIGGERS.trigger_state IS '触发器状态'; +COMMENT ON COLUMN QRTZ_TRIGGERS.trigger_type IS '触发器的类型'; +COMMENT ON COLUMN QRTZ_TRIGGERS.start_time IS '开始时间'; +COMMENT ON COLUMN QRTZ_TRIGGERS.end_time IS '结束时间'; +COMMENT ON COLUMN QRTZ_TRIGGERS.calendar_name IS '日程表名称'; +COMMENT ON COLUMN QRTZ_TRIGGERS.misfire_instr IS '补偿执行的策略'; +COMMENT ON COLUMN QRTZ_TRIGGERS.job_data IS '存放持久化job对象'; + +-- ---------------------------- +-- 3、 存储简单的 Trigger,包括重复次数,间隔,以及已触发的次数 +-- ---------------------------- +CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( + sched_name varchar(120) NOT NULL, + trigger_name varchar(200) NOT NULL, + trigger_group varchar(200) NOT NULL, + repeat_count bigint NOT NULL, + repeat_interval bigint NOT NULL, + times_triggered bigint NOT NULL, + PRIMARY KEY (sched_name, trigger_name, trigger_group), + FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +); + +COMMENT ON COLUMN QRTZ_SIMPLE_TRIGGERS.sched_name IS '调度名称'; +COMMENT ON COLUMN QRTZ_SIMPLE_TRIGGERS.trigger_name IS 'qrtz_triggers表trigger_name的外键'; +COMMENT ON COLUMN QRTZ_SIMPLE_TRIGGERS.trigger_group IS 'qrtz_triggers表trigger_group的外键'; +COMMENT ON COLUMN QRTZ_SIMPLE_TRIGGERS.repeat_count IS '重复的次数统计'; +COMMENT ON COLUMN QRTZ_SIMPLE_TRIGGERS.repeat_interval IS '重复的间隔时间'; +COMMENT ON COLUMN QRTZ_SIMPLE_TRIGGERS.times_triggered IS '已经触发的次数'; + +-- ---------------------------- +-- 4、 存储 Cron Trigger,包括 Cron 表达式和时区信息 +-- ---------------------------- +CREATE TABLE QRTZ_CRON_TRIGGERS ( + sched_name varchar(120) NOT NULL, + trigger_name varchar(200) NOT NULL, + trigger_group varchar(200) NOT NULL, + cron_expression varchar(200) NOT NULL, + time_zone_id varchar(80) NULL, + PRIMARY KEY (sched_name, trigger_name, trigger_group), + FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +); + +COMMENT ON COLUMN QRTZ_CRON_TRIGGERS.sched_name IS '调度名称'; +COMMENT ON COLUMN QRTZ_CRON_TRIGGERS.trigger_name IS 'qrtz_triggers表trigger_name的外键'; +COMMENT ON COLUMN QRTZ_CRON_TRIGGERS.trigger_group IS 'qrtz_triggers表trigger_group的外键'; +COMMENT ON COLUMN QRTZ_CRON_TRIGGERS.cron_expression IS 'cron表达式'; +COMMENT ON COLUMN QRTZ_CRON_TRIGGERS.time_zone_id IS '时区'; + +-- ---------------------------- +-- 5、 Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候) +-- ---------------------------- +CREATE TABLE QRTZ_BLOB_TRIGGERS ( + sched_name varchar(120) NOT NULL, + trigger_name varchar(200) NOT NULL, + trigger_group varchar(200) NOT NULL, + blob_data bytea NULL, + PRIMARY KEY (sched_name, trigger_name, trigger_group), + FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +); + +COMMENT ON COLUMN QRTZ_BLOB_TRIGGERS.sched_name IS '调度名称'; +COMMENT ON COLUMN QRTZ_BLOB_TRIGGERS.trigger_name IS 'qrtz_triggers表trigger_name的外键'; +COMMENT ON COLUMN QRTZ_BLOB_TRIGGERS.trigger_group IS 'qrtz_triggers表trigger_group的外键'; +COMMENT ON COLUMN QRTZ_BLOB_TRIGGERS.blob_data IS '存放持久化Trigger对象'; + +-- ---------------------------- +-- 6、 以 Blob 类型存储存放日历信息, quartz可配置一个日历来指定一个时间范围 +-- ---------------------------- +CREATE TABLE QRTZ_CALENDARS ( + sched_name varchar(120) NOT NULL, + calendar_name varchar(200) NOT NULL, + calendar bytea NOT NULL, + PRIMARY KEY (sched_name, calendar_name) +); + +COMMENT ON COLUMN QRTZ_CALENDARS.sched_name IS '调度名称'; +COMMENT ON COLUMN QRTZ_CALENDARS.calendar_name IS '日历名称'; +COMMENT ON COLUMN QRTZ_CALENDARS.calendar IS '存放持久化calendar对象'; + +-- ---------------------------- +-- 7、 存储已暂停的 Trigger 组的信息 +-- ---------------------------- +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( + sched_name varchar(120) NOT NULL, + trigger_group varchar(200) NOT NULL, + PRIMARY KEY (sched_name, trigger_group) +); + +COMMENT ON COLUMN QRTZ_PAUSED_TRIGGER_GRPS.sched_name IS '调度名称'; +COMMENT ON COLUMN QRTZ_PAUSED_TRIGGER_GRPS.trigger_group IS 'qrtz_triggers表trigger_group的外键'; + +-- ---------------------------- +-- 8、 存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息 +-- ---------------------------- +CREATE TABLE QRTZ_FIRED_TRIGGERS ( + sched_name varchar(120) NOT NULL, + entry_id varchar(95) NOT NULL, + trigger_name varchar(200) NOT NULL, + trigger_group varchar(200) NOT NULL, + instance_name varchar(200) NOT NULL, + fired_time bigint NOT NULL, + sched_time bigint NOT NULL, + priority integer NOT NULL, + state varchar(16) NOT NULL, + job_name varchar(200) NULL, + job_group varchar(200) NULL, + is_nonconcurrent varchar(1) NULL, + requests_recovery varchar(1) NULL, + PRIMARY KEY (sched_name, entry_id) +); + +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.sched_name IS '调度名称'; +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.entry_id IS '调度器实例id'; +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.trigger_name IS 'qrtz_triggers表trigger_name的外键'; +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.trigger_group IS 'qrtz_triggers表trigger_group的外键'; +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.instance_name IS '调度器实例名'; +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.fired_time IS '触发的时间'; +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.sched_time IS '定时器制定的时间'; +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.priority IS '优先级'; +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.state IS '状态'; +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.job_name IS '任务名称'; +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.job_group IS '任务组名'; +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.is_nonconcurrent IS '是否并发'; +COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.requests_recovery IS '是否接受恢复执行'; + +-- ---------------------------- +-- 9、 存储少量的有关 Scheduler 的状态信息,假如是用于集群中,可以看到其他的 Scheduler 实例 +-- ---------------------------- +CREATE TABLE QRTZ_SCHEDULER_STATE ( + sched_name varchar(120) NOT NULL, + instance_name varchar(200) NOT NULL, + last_checkin_time bigint NOT NULL, + checkin_interval bigint NOT NULL, + PRIMARY KEY (sched_name, instance_name) +); + +COMMENT ON COLUMN QRTZ_SCHEDULER_STATE.sched_name IS '调度名称'; +COMMENT ON COLUMN QRTZ_SCHEDULER_STATE.instance_name IS '实例名称'; +COMMENT ON COLUMN QRTZ_SCHEDULER_STATE.last_checkin_time IS '上次检查时间'; +COMMENT ON COLUMN QRTZ_SCHEDULER_STATE.checkin_interval IS '检查间隔时间'; + +-- ---------------------------- +-- 10、 存储程序的悲观锁的信息(假如使用了悲观锁) +-- ---------------------------- +CREATE TABLE QRTZ_LOCKS ( + sched_name varchar(120) NOT NULL, + lock_name varchar(40) NOT NULL, + PRIMARY KEY (sched_name, lock_name) +); + +COMMENT ON COLUMN QRTZ_LOCKS.sched_name IS '调度名称'; +COMMENT ON COLUMN QRTZ_LOCKS.lock_name IS '悲观锁名称'; + +-- ---------------------------- +-- 11、 Quartz集群实现同步机制的行锁表 +-- ---------------------------- +CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( + sched_name varchar(120) NOT NULL, + trigger_name varchar(200) NOT NULL, + trigger_group varchar(200) NOT NULL, + str_prop_1 varchar(512) NULL, + str_prop_2 varchar(512) NULL, + str_prop_3 varchar(512) NULL, + int_prop_1 int NULL, + int_prop_2 int NULL, + long_prop_1 bigint NULL, + long_prop_2 bigint NULL, + dec_prop_1 numeric(13,4) NULL, + dec_prop_2 numeric(13,4) NULL, + bool_prop_1 varchar(1) NULL, + bool_prop_2 varchar(1) NULL, + PRIMARY KEY (sched_name, trigger_name, trigger_group), + FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +); + +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.sched_name IS '调度名称'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.trigger_name IS 'qrtz_triggers表trigger_name的外键'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.trigger_group IS 'qrtz_triggers表trigger_group的外键'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.str_prop_1 IS 'String类型的trigger的第一个参数'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.str_prop_2 IS 'String类型的trigger的第二个参数'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.str_prop_3 IS 'String类型的trigger的第三个参数'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.int_prop_1 IS 'int类型的trigger的第一个参数'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.int_prop_2 IS 'int类型的trigger的第二个参数'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.long_prop_1 IS 'long类型的trigger的第一个参数'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.long_prop_2 IS 'long类型的trigger的第二个参数'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.dec_prop_1 IS 'decimal类型的trigger的第一个参数'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.dec_prop_2 IS 'decimal类型的trigger的第二个参数'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.bool_prop_1 IS 'Boolean类型的trigger的第一个参数'; +COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.bool_prop_2 IS 'Boolean类型的trigger的第二个参数'; \ No newline at end of file diff --git a/sql/postgresql/ry_20250603.sql b/sql/postgresql/ry_20250603.sql new file mode 100644 index 0000000..8e7c1a5 --- /dev/null +++ b/sql/postgresql/ry_20250603.sql @@ -0,0 +1,1019 @@ +-- ---------------------------- +-- 1、部门表 +-- ---------------------------- +drop table if exists sys_dept cascade; + +create table sys_dept ( + dept_id bigserial not null primary key, + parent_id bigint default 0 , + ancestors varchar(50) default '' , + dept_name varchar(30) default '' , + order_num integer default 0 , + leader varchar(20) default null, + phone varchar(11) default null, + email varchar(50) default null, + status char(1) default '0' , + del_flag char(1) default '0' , + create_by varchar(64) default '', + create_time timestamp default current_timestamp, + update_by varchar(64) default '', + update_time timestamp default current_timestamp +); + +comment on column sys_dept.dept_id is '部门id'; +comment on column sys_dept.parent_id is '父部门id'; +comment on column sys_dept.ancestors is '祖级列表'; +comment on column sys_dept.dept_name is '部门名称'; +comment on column sys_dept.order_num is '显示顺序'; +comment on column sys_dept.leader is '负责人'; +comment on column sys_dept.phone is '联系电话'; +comment on column sys_dept.email is '邮箱'; +comment on column sys_dept.status is '部门状态(0正常 1停用)'; +comment on column sys_dept.del_flag is '删除标志(0代表存在 2代表删除)'; +comment on column sys_dept.create_by is '创建者'; +comment on column sys_dept.create_time is '创建时间'; +comment on column sys_dept.update_by is '更新者'; +comment on column sys_dept.update_time is '更新时间'; + +comment on table sys_dept is '部门表'; + +-- ---------------------------- +-- 初始化-部门表数据 +-- ---------------------------- +insert into sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader, phone, email, status, del_flag, create_by, create_time, update_by, update_time) +values(100, 0, '0', '若依科技', 0, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader, phone, email, status, del_flag, create_by, create_time, update_by, update_time) +values(101, 100, '0,100', '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader, phone, email, status, del_flag, create_by, create_time, update_by, update_time) +values(102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader, phone, email, status, del_flag, create_by, create_time, update_by, update_time) +values(103, 101, '0,100,101', '研发部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader, phone, email, status, del_flag, create_by, create_time, update_by, update_time) +values(104, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader, phone, email, status, del_flag, create_by, create_time, update_by, update_time) +values(105, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader, phone, email, status, del_flag, create_by, create_time, update_by, update_time) +values(106, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader, phone, email, status, del_flag, create_by, create_time, update_by, update_time) +values(107, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader, phone, email, status, del_flag, create_by, create_time, update_by, update_time) +values(108, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', current_timestamp, '', null); +insert into sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader, phone, email, status, del_flag, create_by, create_time, update_by, update_time) +values(109, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', current_timestamp, '', null); + +-- ---------------------------- +-- 2、用户信息表 +-- ---------------------------- +drop table if exists sys_user cascade; + +create table sys_user ( + user_id bigserial not null primary key, + dept_id bigint default null, + user_name varchar(30) not null, + nick_name varchar(30) not null, + user_type varchar(2) default '00', + email varchar(50) default '', + phonenumber varchar(11) default '', + sex char(1) default '0', + avatar varchar(100) default '', + password varchar(100) default '', + status char(1) default '0', + del_flag char(1) default '0', + login_ip varchar(128) default '', + login_date timestamp, + pwd_update_date timestamp default current_timestamp, + create_by varchar(64) default '', + create_time timestamp default current_timestamp, + update_by varchar(64) default '', + update_time timestamp default current_timestamp, + remark varchar(500) default null +); + +comment on column sys_user.user_id is '用户ID'; +comment on column sys_user.dept_id is '部门ID'; +comment on column sys_user.user_name is '用户账号'; +comment on column sys_user.nick_name is '用户昵称'; +comment on column sys_user.user_type is '用户类型(00系统用户)'; +comment on column sys_user.email is '用户邮箱'; +comment on column sys_user.phonenumber is '手机号码'; +comment on column sys_user.sex is '用户性别(0男 1女 2未知)'; +comment on column sys_user.avatar is '头像地址'; +comment on column sys_user.password is '密码'; +comment on column sys_user.status is '账号状态(0正常 1停用)'; +comment on column sys_user.del_flag is '删除标志(0代表存在 2代表删除)'; +comment on column sys_user.login_ip is '最后登录IP'; +comment on column sys_user.login_date is '最后登录时间'; +comment on column sys_user.pwd_update_date is '密码最后更新时间'; + +comment on column sys_user.create_by is '创建者'; +comment on column sys_user.create_time is '创建时间'; +comment on column sys_user.update_by is '更新者'; +comment on column sys_user.update_time is '更新时间'; +comment on column sys_user.remark is '备注'; + +comment on table sys_user is '用户信息表'; + +-- ---------------------------- +-- 初始化-用户信息表数据 +-- ---------------------------- +insert into sys_user (user_id, dept_id, user_name, nick_name, user_type, email, phonenumber, sex, avatar, password, status, del_flag, login_ip, login_date, create_by, create_time, update_by, update_time,pwd_update_date, remark) +values(1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', current_timestamp, 'admin', current_timestamp, '', null,current_timestamp, '管理员'); +insert into sys_user (user_id, dept_id, user_name, nick_name, user_type, email, phonenumber, sex, avatar, password, status, del_flag, login_ip, login_date, create_by, create_time, update_by, update_time,pwd_update_date, remark) +values(2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', current_timestamp, 'admin', current_timestamp, '', null,current_timestamp, '测试员'); + +-- ---------------------------- +-- 3、岗位信息表 +-- ---------------------------- +drop table if exists sys_post cascade; + +create table sys_post ( + post_id bigserial not null primary key, + post_code varchar(64) not null, + post_name varchar(50) not null, + post_sort integer not null, + status char(1) not null, + create_by varchar(64) default '', + create_time timestamp default current_timestamp, + update_by varchar(64) default '', + update_time timestamp default current_timestamp, + remark varchar(500) default null +); + +comment on column sys_post.post_id is '岗位ID'; +comment on column sys_post.post_code is '岗位编码'; +comment on column sys_post.post_name is '岗位名称'; +comment on column sys_post.post_sort is '显示顺序'; +comment on column sys_post.status is '状态(0正常 1停用)'; +comment on column sys_post.create_by is '创建者'; +comment on column sys_post.create_time is '创建时间'; +comment on column sys_post.update_by is '更新者'; +comment on column sys_post.update_time is '更新时间'; +comment on column sys_post.remark is '备注'; + +comment on table sys_post is '岗位信息表'; + +-- ---------------------------- +-- 初始化-岗位信息表数据 +-- ---------------------------- +insert into sys_post (post_id, post_code, post_name, post_sort, status, create_by, create_time, update_by, update_time, remark) +values(1, 'ceo', '董事长', 1, '0', 'admin', current_timestamp, '', null, ''); +insert into sys_post (post_id, post_code, post_name, post_sort, status, create_by, create_time, update_by, update_time, remark) +values(2, 'se', '项目经理', 2, '0', 'admin', current_timestamp, '', null, ''); +insert into sys_post (post_id, post_code, post_name, post_sort, status, create_by, create_time, update_by, update_time, remark) +values(3, 'hr', '人力资源', 3, '0', 'admin', current_timestamp, '', null, ''); +insert into sys_post (post_id, post_code, post_name, post_sort, status, create_by, create_time, update_by, update_time, remark) +values(4, 'user', '普通员工', 4, '0', 'admin', current_timestamp, '', null, ''); + + +-- ---------------------------- +-- 4、角色信息表 +-- ---------------------------- +drop table if exists sys_role cascade; + +create table sys_role ( + role_id bigserial not null primary key, + role_name varchar(30) not null, + role_key varchar(100) not null, + role_sort integer not null, + data_scope char(1) default '1', + menu_check_strictly boolean default true, + dept_check_strictly boolean default true, + status char(1) not null, + del_flag char(1) default '0', + create_by varchar(64) default '', + create_time timestamp default current_timestamp, + update_by varchar(64) default '', + update_time timestamp default current_timestamp, + remark varchar(500) default null +); + +comment on column sys_role.role_id is '角色ID'; +comment on column sys_role.role_name is '角色名称'; +comment on column sys_role.role_key is '角色权限字符串'; +comment on column sys_role.role_sort is '显示顺序'; +comment on column sys_role.data_scope is '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)'; +comment on column sys_role.menu_check_strictly is '菜单树选择项是否关联显示'; +comment on column sys_role.dept_check_strictly is '部门树选择项是否关联显示'; +comment on column sys_role.status is '角色状态(0正常 1停用)'; +comment on column sys_role.del_flag is '删除标志(0代表存在 2代表删除)'; +comment on column sys_role.create_by is '创建者'; +comment on column sys_role.create_time is '创建时间'; +comment on column sys_role.update_by is '更新者'; +comment on column sys_role.update_time is '更新时间'; +comment on column sys_role.remark is '备注'; + +comment on table sys_role is '角色信息表'; + +-- ---------------------------- +-- 初始化-角色信息表数据 +-- ---------------------------- +insert into sys_role (role_id, role_name, role_key, role_sort, data_scope, menu_check_strictly, dept_check_strictly, status, del_flag, create_by, create_time, update_by, update_time, remark) +values(1, '超级管理员', 'admin', 1, '1', true, true, '0', '0', 'admin', current_timestamp, '', null, '超级管理员'); +insert into sys_role (role_id, role_name, role_key, role_sort, data_scope, menu_check_strictly, dept_check_strictly, status, del_flag, create_by, create_time, update_by, update_time, remark) +values(2, '普通角色', 'common', 2, '2', true, true, '0', '0', 'admin', current_timestamp, '', null, '普通角色'); + + +-- ---------------------------- +-- 5、菜单权限表 +-- ---------------------------- +drop table if exists sys_menu cascade; + +create table sys_menu ( + menu_id bigserial not null primary key, + menu_name varchar(50) not null, + parent_id bigint default 0, + order_num integer default 0, + path varchar(200) default '', + component varchar(255) default null, + query varchar(255) default null, + route_name varchar(50) default '', + is_frame char(1) default '1', + is_cache char(1) default '0', + menu_type char(1) default '', + visible char(1) default '0', + status char(1) default '0', + perms varchar(100) default null, + icon varchar(100) default '#', + create_by varchar(64) default '', + create_time timestamp default current_timestamp, + update_by varchar(64) default '', + update_time timestamp default current_timestamp, + remark varchar(500) default '' +); + +comment on column sys_menu.menu_id is '菜单ID'; +comment on column sys_menu.menu_name is '菜单名称'; +comment on column sys_menu.parent_id is '父菜单ID'; +comment on column sys_menu.order_num is '显示顺序'; +comment on column sys_menu.path is '路由地址'; +comment on column sys_menu.component is '组件路径'; +comment on column sys_menu.query is '路由参数'; +comment on column sys_menu.route_name is '路由名称'; +comment on column sys_menu.is_frame is '是否为外链(0是 1否)'; +comment on column sys_menu.is_cache is '是否缓存(0缓存 1不缓存)'; +comment on column sys_menu.menu_type is '菜单类型(M目录 C菜单 F按钮)'; +comment on column sys_menu.visible is '菜单状态(0显示 1隐藏)'; +comment on column sys_menu.status is '菜单状态(0正常 1停用)'; +comment on column sys_menu.perms is '权限标识'; +comment on column sys_menu.icon is '菜单图标'; +comment on column sys_menu.create_by is '创建者'; +comment on column sys_menu.create_time is '创建时间'; +comment on column sys_menu.update_by is '更新者'; +comment on column sys_menu.update_time is '更新时间'; +comment on column sys_menu.remark is '备注'; + +comment on table sys_menu is '菜单权限表'; + +-- ---------------------------- +-- 初始化-菜单信息表数据 +-- ---------------------------- +-- 一级菜单 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1, '系统管理', 0, 1, 'system', null, '', '', 1, 0, 'M', '0', '0', '', 'system', 'admin', current_timestamp, '', null, '系统管理目录'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(2, '系统监控', 0, 2, 'monitor', null, '', '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', current_timestamp, '', null, '系统监控目录'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(3, '系统工具', 0, 3, 'tool', null, '', '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', current_timestamp, '', null, '系统工具目录'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(4, '若依官网', 0, 4, 'http://ruoyi.vip', null, '', '', 0, 0, 'M', '0', '0', '', 'guide', 'admin', current_timestamp, '', null, '若依官网地址'); + +-- 二级菜单 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(100, '用户管理', 1, 1, 'user', 'system/user/index', '', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', current_timestamp, '', null, '用户管理菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(101, '角色管理', 1, 2, 'role', 'system/role/index', '', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', current_timestamp, '', null, '角色管理菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', current_timestamp, '', null, '菜单管理菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(103, '部门管理', 1, 4, 'dept', 'system/dept/index', '', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', current_timestamp, '', null, '部门管理菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(104, '岗位管理', 1, 5, 'post', 'system/post/index', '', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', current_timestamp, '', null, '岗位管理菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(105, '字典管理', 1, 6, 'dict', 'system/dict/index', '', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', current_timestamp, '', null, '字典管理菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(106, '参数设置', 1, 7, 'config', 'system/config/index', '', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', current_timestamp, '', null, '参数设置菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(107, '通知公告', 1, 8, 'notice', 'system/notice/index', '', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', current_timestamp, '', null, '通知公告菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(108, '日志管理', 1, 9, 'log', '', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', current_timestamp, '', null, '日志管理菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(109, '在线用户', 2, 1, 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', current_timestamp, '', null, '在线用户菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(110, '定时任务', 2, 2, 'job', 'monitor/job/index', '', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', current_timestamp, '', null, '定时任务菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(111, '数据监控', 2, 3, 'druid', 'monitor/druid/index', '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', current_timestamp, '', null, '数据监控菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(112, '服务监控', 2, 4, 'server', 'monitor/server/index', '', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', current_timestamp, '', null, '服务监控菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(113, '缓存监控', 2, 5, 'cache', 'monitor/cache/index', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', current_timestamp, '', null, '缓存监控菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(114, '缓存列表', 2, 6, 'cacheList', 'monitor/cache/list', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', current_timestamp, '', null, '缓存列表菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(115, '表单构建', 3, 1, 'build', 'tool/build/index', '', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', current_timestamp, '', null, '表单构建菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(117, '系统接口', 3, 3, 'swagger', 'tool/swagger/index', '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', current_timestamp, '', null, '系统接口菜单'); +-- 三级菜单 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', current_timestamp, '', null, '操作日志菜单'); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', current_timestamp, '', null, '登录日志菜单'); + +-- 用户管理按钮 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1000, '用户查询', 100, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1001, '用户新增', 100, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1002, '用户修改', 100, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1003, '用户删除', 100, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1004, '用户导出', 100, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1005, '用户导入', 100, 6, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1006, '重置密码', 100, 7, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', current_timestamp, '', null, ''); + +-- 角色管理按钮 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1007, '角色查询', 101, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1008, '角色新增', 101, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1009, '角色修改', 101, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1010, '角色删除', 101, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1011, '角色导出', 101, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', current_timestamp, '', null, ''); + +-- 菜单管理按钮 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1012, '菜单查询', 102, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1013, '菜单新增', 102, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1014, '菜单修改', 102, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1015, '菜单删除', 102, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', current_timestamp, '', null, ''); + +-- 部门管理按钮 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1016, '部门查询', 103, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1017, '部门新增', 103, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1018, '部门修改', 103, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1019, '部门删除', 103, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', current_timestamp, '', null, ''); +-- 岗位管理按钮 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1020, '岗位查询', 104, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1021, '岗位新增', 104, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1022, '岗位修改', 104, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1023, '岗位删除', 104, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1024, '岗位导出', 104, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', current_timestamp, '', null, ''); + +-- 字典管理按钮 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1025, '字典查询', 105, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1026, '字典新增', 105, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1027, '字典修改', 105, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1028, '字典删除', 105, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1029, '字典导出', 105, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', current_timestamp, '', null, ''); + +-- 参数设置按钮 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1030, '参数查询', 106, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1031, '参数新增', 106, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1032, '参数修改', 106, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1033, '参数删除', 106, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1034, '参数导出', 106, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', current_timestamp, '', null, ''); + +-- 通知公告按钮 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1035, '公告查询', 107, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1036, '公告新增', 107, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1037, '公告修改', 107, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1038, '公告删除', 107, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', current_timestamp, '', null, ''); + +-- 操作日志按钮 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1039, '操作查询', 500, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1040, '操作删除', 500, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1041, '日志导出', 500, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', current_timestamp, '', null, ''); + +-- 登录日志按钮 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1042, '登录查询', 501, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1043, '登录删除', 501, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1044, '日志导出', 501, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', current_timestamp, '', null, ''); + +-- 在线用户按钮 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1046, '在线查询', 109, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1047, '批量强退', 109, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1048, '单条强退', 109, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', current_timestamp, '', null, ''); + +-- 定时任务按钮 +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1049, '任务查询', 110, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1050, '任务新增', 110, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1051, '任务修改', 110, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1052, '任务删除', 110, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1053, '状态修改', 110, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', current_timestamp, '', null, ''); +insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values(1054, '任务导出', 110, 6, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', current_timestamp, '', null, ''); + + + +-- ---------------------------- +-- 6、用户和角色关联表 用户N-1角色 +-- ---------------------------- +drop table if exists sys_user_role cascade; + +create table sys_user_role ( + user_id bigint not null, + role_id bigint not null, + primary key(user_id, role_id) +); + +comment on table sys_user_role is '用户和角色关联表'; +comment on column sys_user_role.user_id is '用户ID'; +comment on column sys_user_role.role_id is '角色ID'; + +-- ---------------------------- +-- 初始化-用户和角色关联表数据 +-- ---------------------------- +insert into sys_user_role (user_id, role_id) values (1, 1); +insert into sys_user_role (user_id, role_id) values (2, 2); + +-- ---------------------------- +-- 7、角色和菜单关联表 角色1-N菜单 +-- ---------------------------- +drop table if exists sys_role_menu cascade; + +create table sys_role_menu ( + role_id bigint not null, + menu_id bigint not null, + primary key(role_id, menu_id) +); + +comment on table sys_role_menu is '角色和菜单关联表'; +comment on column sys_role_menu.role_id is '角色ID'; +comment on column sys_role_menu.menu_id is '菜单ID'; + +-- ---------------------------- +-- 初始化-角色和菜单关联表数据 +-- ---------------------------- +insert into sys_role_menu (role_id, menu_id) values (2, 1); +insert into sys_role_menu (role_id, menu_id) values (2, 2); +insert into sys_role_menu (role_id, menu_id) values (2, 3); +insert into sys_role_menu (role_id, menu_id) values (2, 4); +insert into sys_role_menu (role_id, menu_id) values (2, 100); +insert into sys_role_menu (role_id, menu_id) values (2, 101); +insert into sys_role_menu (role_id, menu_id) values (2, 102); +insert into sys_role_menu (role_id, menu_id) values (2, 103); +insert into sys_role_menu (role_id, menu_id) values (2, 104); +insert into sys_role_menu (role_id, menu_id) values (2, 105); +insert into sys_role_menu (role_id, menu_id) values (2, 106); +insert into sys_role_menu (role_id, menu_id) values (2, 107); +insert into sys_role_menu (role_id, menu_id) values (2, 108); +insert into sys_role_menu (role_id, menu_id) values (2, 109); +insert into sys_role_menu (role_id, menu_id) values (2, 110); +insert into sys_role_menu (role_id, menu_id) values (2, 111); +insert into sys_role_menu (role_id, menu_id) values (2, 112); +insert into sys_role_menu (role_id, menu_id) values (2, 113); +insert into sys_role_menu (role_id, menu_id) values (2, 114); +insert into sys_role_menu (role_id, menu_id) values (2, 115); +insert into sys_role_menu (role_id, menu_id) values (2, 116); +insert into sys_role_menu (role_id, menu_id) values (2, 117); +insert into sys_role_menu (role_id, menu_id) values (2, 500); +insert into sys_role_menu (role_id, menu_id) values (2, 501); +insert into sys_role_menu (role_id, menu_id) values (2, 1000); +insert into sys_role_menu (role_id, menu_id) values (2, 1001); +insert into sys_role_menu (role_id, menu_id) values (2, 1002); +insert into sys_role_menu (role_id, menu_id) values (2, 1003); +insert into sys_role_menu (role_id, menu_id) values (2, 1004); +insert into sys_role_menu (role_id, menu_id) values (2, 1005); +insert into sys_role_menu (role_id, menu_id) values (2, 1006); +insert into sys_role_menu (role_id, menu_id) values (2, 1007); +insert into sys_role_menu (role_id, menu_id) values (2, 1008); +insert into sys_role_menu (role_id, menu_id) values (2, 1009); +insert into sys_role_menu (role_id, menu_id) values (2, 1010); +insert into sys_role_menu (role_id, menu_id) values (2, 1011); +insert into sys_role_menu (role_id, menu_id) values (2, 1012); +insert into sys_role_menu (role_id, menu_id) values (2, 1013); +insert into sys_role_menu (role_id, menu_id) values (2, 1014); +insert into sys_role_menu (role_id, menu_id) values (2, 1015); +insert into sys_role_menu (role_id, menu_id) values (2, 1016); +insert into sys_role_menu (role_id, menu_id) values (2, 1017); +insert into sys_role_menu (role_id, menu_id) values (2, 1018); +insert into sys_role_menu (role_id, menu_id) values (2, 1019); +insert into sys_role_menu (role_id, menu_id) values (2, 1020); +insert into sys_role_menu (role_id, menu_id) values (2, 1021); +insert into sys_role_menu (role_id, menu_id) values (2, 1022); +insert into sys_role_menu (role_id, menu_id) values (2, 1023); +insert into sys_role_menu (role_id, menu_id) values (2, 1024); +insert into sys_role_menu (role_id, menu_id) values (2, 1025); +insert into sys_role_menu (role_id, menu_id) values (2, 1026); +insert into sys_role_menu (role_id, menu_id) values (2, 1027); +insert into sys_role_menu (role_id, menu_id) values (2, 1028); +insert into sys_role_menu (role_id, menu_id) values (2, 1029); +insert into sys_role_menu (role_id, menu_id) values (2, 1030); +insert into sys_role_menu (role_id, menu_id) values (2, 1031); +insert into sys_role_menu (role_id, menu_id) values (2, 1032); +insert into sys_role_menu (role_id, menu_id) values (2, 1033); +insert into sys_role_menu (role_id, menu_id) values (2, 1034); +insert into sys_role_menu (role_id, menu_id) values (2, 1035); +insert into sys_role_menu (role_id, menu_id) values (2, 1036); +insert into sys_role_menu (role_id, menu_id) values (2, 1037); +insert into sys_role_menu (role_id, menu_id) values (2, 1038); +insert into sys_role_menu (role_id, menu_id) values (2, 1039); +insert into sys_role_menu (role_id, menu_id) values (2, 1040); +insert into sys_role_menu (role_id, menu_id) values (2, 1041); +insert into sys_role_menu (role_id, menu_id) values (2, 1042); +insert into sys_role_menu (role_id, menu_id) values (2, 1043); +insert into sys_role_menu (role_id, menu_id) values (2, 1044); +insert into sys_role_menu (role_id, menu_id) values (2, 1045); +insert into sys_role_menu (role_id, menu_id) values (2, 1046); +insert into sys_role_menu (role_id, menu_id) values (2, 1047); +insert into sys_role_menu (role_id, menu_id) values (2, 1048); +insert into sys_role_menu (role_id, menu_id) values (2, 1049); +insert into sys_role_menu (role_id, menu_id) values (2, 1050); +insert into sys_role_menu (role_id, menu_id) values (2, 1051); +insert into sys_role_menu (role_id, menu_id) values (2, 1052); +insert into sys_role_menu (role_id, menu_id) values (2, 1053); +insert into sys_role_menu (role_id, menu_id) values (2, 1054); +insert into sys_role_menu (role_id, menu_id) values (2, 1055); +insert into sys_role_menu (role_id, menu_id) values (2, 1056); +insert into sys_role_menu (role_id, menu_id) values (2, 1057); +insert into sys_role_menu (role_id, menu_id) values (2, 1058); +insert into sys_role_menu (role_id, menu_id) values (2, 1059); +insert into sys_role_menu (role_id, menu_id) values (2, 1060); +-- ---------------------------- +-- 8、角色和部门关联表 角色1-N部门 +-- ---------------------------- +drop table if exists sys_role_dept cascade; + +create table sys_role_dept ( + role_id bigint not null, + dept_id bigint not null, + primary key(role_id, dept_id) +); + +comment on table sys_role_dept is '角色和部门关联表'; +comment on column sys_role_dept.role_id is '角色ID'; +comment on column sys_role_dept.dept_id is '部门ID'; + +-- ---------------------------- +-- 初始化-角色和部门关联表数据 +-- ---------------------------- +insert into sys_role_dept (role_id, dept_id) values (2, 100); +insert into sys_role_dept (role_id, dept_id) values (2, 101); +insert into sys_role_dept (role_id, dept_id) values (2, 105); + +-- ---------------------------- +-- 9、用户与岗位关联表 用户1-N岗位 +-- ---------------------------- +drop table if exists sys_user_post cascade; + +create table sys_user_post ( + user_id bigint not null, + post_id bigint not null, + primary key (user_id, post_id) +); + +comment on table sys_user_post is '用户与岗位关联表'; +comment on column sys_user_post.user_id is '用户ID'; +comment on column sys_user_post.post_id is '岗位ID'; + +-- ---------------------------- +-- 初始化-用户与岗位关联表数据 +-- ---------------------------- +insert into sys_user_post (user_id, post_id) values (1, 1); +insert into sys_user_post (user_id, post_id) values (2, 2); + +-- ---------------------------- +-- 10、操作日志记录 +-- ---------------------------- +drop table if exists sys_oper_log cascade; + +create table sys_oper_log ( + oper_id bigserial not null primary key, + title varchar(50) default '' , + business_type smallint default 0 , + method varchar(100) default '' , + request_method varchar(10) default '' , + operator_type smallint default 0 , + oper_name varchar(50) default '' , + dept_name varchar(50) default '' , + oper_url varchar(255) default '' , + oper_ip varchar(128) default '' , + oper_location varchar(255) default '' , + oper_param varchar(2000) default '' , + json_result varchar(2000) default '' , + status smallint default 0 , + error_msg varchar(2000) default '' , + oper_time timestamp default current_timestamp , + cost_time bigint default 0 +); + +create index idx_sys_oper_log_bt on sys_oper_log (business_type); +create index idx_sys_oper_log_s on sys_oper_log (status); +create index idx_sys_oper_log_ot on sys_oper_log (oper_time); + +comment on table sys_oper_log is '操作日志记录'; +comment on column sys_oper_log.oper_id is '日志主键'; +comment on column sys_oper_log.title is '模块标题'; +comment on column sys_oper_log.business_type is '业务类型(0其它 1新增 2修改 3删除)'; +comment on column sys_oper_log.method is '方法名称'; +comment on column sys_oper_log.request_method is '请求方式'; +comment on column sys_oper_log.operator_type is '操作类别(0其它 1后台用户 2手机端用户)'; +comment on column sys_oper_log.oper_name is '操作人员'; +comment on column sys_oper_log.dept_name is '部门名称'; +comment on column sys_oper_log.oper_url is '请求URL'; +comment on column sys_oper_log.oper_ip is '主机地址'; +comment on column sys_oper_log.oper_location is '操作地点'; +comment on column sys_oper_log.oper_param is '请求参数'; +comment on column sys_oper_log.json_result is '返回参数'; +comment on column sys_oper_log.status is '操作状态(0正常 1异常)'; +comment on column sys_oper_log.error_msg is '错误消息'; +comment on column sys_oper_log.oper_time is '操作时间'; +comment on column sys_oper_log.cost_time is '消耗时间'; + +-- ---------------------------- +-- 11、字典类型表 +-- ---------------------------- +drop table if exists sys_dict_type cascade; + +create table sys_dict_type ( + dict_id bigserial not null primary key, + dict_name varchar(100) default '' not null, + dict_type varchar(100) default '' not null, + status char(1) default '0' not null, + create_by varchar(64) default '' , + create_time timestamp default current_timestamp , + update_by varchar(64) default '' , + update_time timestamp default current_timestamp , + remark varchar(500) default null +); + +create unique index idx_sys_dict_type_dict_type on sys_dict_type (dict_type); + +comment on table sys_dict_type is '字典类型表'; +comment on column sys_dict_type.dict_id is '字典主键'; +comment on column sys_dict_type.dict_name is '字典名称'; +comment on column sys_dict_type.dict_type is '字典类型'; +comment on column sys_dict_type.status is '状态(0正常 1停用)'; +comment on column sys_dict_type.create_by is '创建者'; +comment on column sys_dict_type.create_time is '创建时间'; +comment on column sys_dict_type.update_by is '更新者'; +comment on column sys_dict_type.update_time is '更新时间'; +comment on column sys_dict_type.remark is '备注'; + +-- 初始化-字典类型表数据 +insert into sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +values('用户性别', 'sys_user_sex', '0', 'admin', current_timestamp, '', null, '用户性别列表'); +insert into sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +values('菜单状态', 'sys_show_hide', '0', 'admin', current_timestamp, '', null, '菜单状态列表'); +insert into sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +values('系统开关', 'sys_normal_disable', '0', 'admin', current_timestamp, '', null, '系统开关列表'); +insert into sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +values('任务状态', 'sys_job_status', '0', 'admin', current_timestamp, '', null, '任务状态列表'); +insert into sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +values('任务分组', 'sys_job_group', '0', 'admin', current_timestamp, '', null, '任务分组列表'); +insert into sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +values('系统是否', 'sys_yes_no', '0', 'admin', current_timestamp, '', null, '系统是否列表'); +insert into sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +values('通知类型', 'sys_notice_type', '0', 'admin', current_timestamp, '', null, '通知类型列表'); +insert into sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +values('通知状态', 'sys_notice_status', '0', 'admin', current_timestamp, '', null, '通知状态列表'); +insert into sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +values('操作类型', 'sys_oper_type', '0', 'admin', current_timestamp, '', null, '操作类型列表'); +insert into sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) +values('系统状态', 'sys_common_status', '0', 'admin', current_timestamp, '', null, '登录状态列表'); + +-- ---------------------------- +-- 12、字典数据表 +-- ---------------------------- +drop table if exists sys_dict_data cascade; + +create table sys_dict_data ( + dict_code bigserial not null primary key, + dict_sort integer default 0 not null, + dict_label varchar(100) default '' not null, + dict_value varchar(100) default '' not null, + dict_type varchar(100) default '' not null, + css_class varchar(100) default null, + list_class varchar(100) default null, + is_default char(1) default 'N' not null, + status char(1) default '0' not null, + create_by varchar(64) default '' , + create_time timestamp default current_timestamp , + update_by varchar(64) default '' , + update_time timestamp default current_timestamp , + remark varchar(500) default null +); + +comment on table sys_dict_data is '字典数据表'; +comment on column sys_dict_data.dict_code is '字典编码'; +comment on column sys_dict_data.dict_sort is '字典排序'; +comment on column sys_dict_data.dict_label is '字典标签'; +comment on column sys_dict_data.dict_value is '字典键值'; +comment on column sys_dict_data.dict_type is '字典类型'; +comment on column sys_dict_data.css_class is '样式属性(其他样式扩展)'; +comment on column sys_dict_data.list_class is '表格回显样式'; +comment on column sys_dict_data.is_default is '是否默认(Y是 N否)'; +comment on column sys_dict_data.status is '状态(0正常 1停用)'; +comment on column sys_dict_data.create_by is '创建者'; +comment on column sys_dict_data.create_time is '创建时间'; +comment on column sys_dict_data.update_by is '更新者'; +comment on column sys_dict_data.update_time is '更新时间'; +comment on column sys_dict_data.remark is '备注'; + +-- 初始化-字典数据表数据 +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', current_timestamp, '', null, '性别男'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', current_timestamp, '', null, '性别女'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', current_timestamp, '', null, '性别未知'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', current_timestamp, '', null, '显示菜单'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '隐藏菜单'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', current_timestamp, '', null, '正常状态'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '停用状态'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', current_timestamp, '', null, '正常状态'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '停用状态'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(1, '默认', 'DEFAULT', 'sys_job_group', '', '', 'Y', '0', 'admin', current_timestamp, '', null, '默认分组'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(2, '系统', 'SYSTEM', 'sys_job_group', '', '', 'N', '0', 'admin', current_timestamp, '', null, '系统分组'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', current_timestamp, '', null, '系统默认是'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '系统默认否'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', current_timestamp, '', null, '通知'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', current_timestamp, '', null, '公告'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', current_timestamp, '', null, '正常状态'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '关闭状态'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', current_timestamp, '', null, '其他操作'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', current_timestamp, '', null, '新增操作'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', current_timestamp, '', null, '修改操作'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '删除操作'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', current_timestamp, '', null, '授权操作'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', current_timestamp, '', null, '导出操作'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', current_timestamp, '', null, '导入操作'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '强退操作'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', current_timestamp, '', null, '生成操作'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '清空操作'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', current_timestamp, '', null, '正常状态'); +insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) +values(2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', current_timestamp, '', null, '停用状态'); + +-- ---------------------------- +-- 13、参数配置表 +-- ---------------------------- +drop table if exists sys_config cascade; + +create table sys_config ( + config_id serial not null primary key, + config_name varchar(100) default '' not null, + config_key varchar(100) default '' not null, + config_value varchar(500) default '' not null, + config_type char(1) default 'N' not null, + create_by varchar(64) default '', + create_time timestamp default current_timestamp, + update_by varchar(64) default '', + update_time timestamp default current_timestamp, + remark varchar(500) default null +); + +comment on table sys_config is '参数配置表'; +comment on column sys_config.config_id is '参数主键'; +comment on column sys_config.config_name is '参数名称'; +comment on column sys_config.config_key is '参数键名'; +comment on column sys_config.config_value is '参数键值'; +comment on column sys_config.config_type is '系统内置(Y是 N否)'; +comment on column sys_config.create_by is '创建者'; +comment on column sys_config.create_time is '创建时间'; +comment on column sys_config.update_by is '更新者'; +comment on column sys_config.update_time is '更新时间'; +comment on column sys_config.remark is '备注'; + +-- 初始化-参数配置表数据 +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-green', 'Y', 'admin', current_timestamp, 'admin', current_timestamp, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow'); +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', current_timestamp, '', null, '初始化密码 123456'); +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-light', 'Y', 'admin', current_timestamp, 'admin', current_timestamp, '深色主题theme-dark,浅色主题theme-light'); +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', current_timestamp, '', null, '是否开启验证码功能(true开启,false关闭)'); +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'true', 'Y', 'admin', current_timestamp, 'admin', current_timestamp, '是否开启注册用户功能(true开启,false关闭)'); +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('主题颜色', 'sys.index.theme', '#11A983', 'Y', 'admin', current_timestamp, 'admin', current_timestamp, '主题颜色'); +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('开启TopNav', 'sys.index.topNav', 'false', 'Y', 'admin', current_timestamp, '', null, '是否开启TopNav(true开启,false关闭)'); +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('开启Tags-Views', 'sys.index.tagsView', 'true', 'Y', 'admin', current_timestamp, '', null, '开启Tags-Views功能(true开启,false关闭)'); +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('显示Logo', 'sys.index.sidebarLogo', 'true', 'Y', 'admin', current_timestamp, '', null, '是否显示Logo(true显示,false不显示)'); +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('固定Header', 'sys.index.fixedHeader', 'true', 'Y', 'admin', current_timestamp, '', null, '是否固定Header(true开启,false关闭)'); +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('动态标题', 'sys.index.dynamicTitle', 'true', 'Y', 'admin', current_timestamp, 'admin', current_timestamp, '是否开启动态标题(true开启,false关闭),开启后浏览器标题会根据当前页面的标题动态变化,关闭后浏览器标题将一直显示为系统名称'); +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('用户管理-初始密码修改策略', 'sys.index.dynamicTitle', 'true', 'Y', 'admin', current_timestamp, 'admin', current_timestamp, '0:初始密码修改策略关闭,没有任何提示,1:提醒用户,如果未修改初始密码,则在登录时就会提醒修改密码对话框'); +insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) +values('用户管理-账号密码更新周期', 'sys.account.passwordValidateDays', 'true', 'Y', 'admin', current_timestamp, 'admin', current_timestamp, '密码更新周期(填写数字,数据初始化值为0不限制,若修改必须为大于0小于365的正整数),如果超过这个周期登录系统时,则在登录时就会提醒修改密码对话框'); + + +-- ---------------------------- +-- 14、系统访问记录 +-- ---------------------------- +drop table if exists sys_logininfor cascade; + +create table sys_logininfor ( + info_id bigserial not null primary key, + user_name varchar(50) default '' not null, + ipaddr varchar(128) default '' not null, + login_location varchar(255) default '' not null, + browser varchar(50) default '' not null, + os varchar(50) default '' not null, + status char(1) default '0' not null, + msg varchar(255) default '' not null, + login_time timestamp default current_timestamp not null +); + +create index idx_sys_logininfor_s on sys_logininfor (status); +create index idx_sys_logininfor_lt on sys_logininfor (login_time); + +comment on table sys_logininfor is '系统访问记录'; +comment on column sys_logininfor.info_id is '访问ID'; +comment on column sys_logininfor.user_name is '用户账号'; +comment on column sys_logininfor.ipaddr is '登录IP地址'; +comment on column sys_logininfor.login_location is '登录地点'; +comment on column sys_logininfor.browser is '浏览器类型'; +comment on column sys_logininfor.os is '操作系统'; +comment on column sys_logininfor.status is '登录状态(0成功 1失败)'; +comment on column sys_logininfor.msg is '提示消息'; +comment on column sys_logininfor.login_time is '访问时间'; + +-- ---------------------------- +-- 15、定时任务调度表 +-- ---------------------------- +drop table if exists sys_job cascade; + +create table sys_job ( + job_id bigserial not null, + job_name varchar(64) default '' not null, + job_group varchar(64) default 'DEFAULT' not null, + invoke_target varchar(500) default '' not null, + cron_expression varchar(255) default '' not null, + misfire_policy varchar(20) default '3' not null, + concurrent char(1) default '1' not null, + status char(1) default '0' not null, + create_by varchar(64) default '' , + create_time timestamp default current_timestamp , + update_by varchar(64) default '' , + update_time timestamp default current_timestamp , + remark varchar(500) default '' , + primary key (job_id, job_name, job_group) +); + +comment on table sys_job is '定时任务调度表'; +comment on column sys_job.job_id is '任务ID'; +comment on column sys_job.job_name is '任务名称'; +comment on column sys_job.job_group is '任务组名'; +comment on column sys_job.invoke_target is '调用目标字符串'; +comment on column sys_job.cron_expression is 'cron执行表达式'; +comment on column sys_job.misfire_policy is '计划执行错误策略(1立即执行 2执行一次 3放弃执行)'; +comment on column sys_job.concurrent is '是否并发执行(0允许 1禁止)'; +comment on column sys_job.status is '状态(0正常 1暂停)'; +comment on column sys_job.create_by is '创建者'; +comment on column sys_job.create_time is '创建时间'; +comment on column sys_job.update_by is '更新者'; +comment on column sys_job.update_time is '更新时间'; +comment on column sys_job.remark is '备注信息'; + +-- 初始化-定时任务调度表数据 +insert into sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, update_by, update_time, remark) +values('系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '0/10 * * * * ?', '3', '1', '1', 'admin', current_timestamp, '', null, ''); +insert into sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, update_by, update_time, remark) +values('系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(''ry'')', '0/15 * * * * ?', '3', '1', '1', 'admin', current_timestamp, '', null, ''); +insert into sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, update_by, update_time, remark) +values('系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(''ry'', true, 2000, 316.50, 100)', '0/20 * * * * ?', '3', '1', '1', 'admin', current_timestamp, '', null, ''); + +-- ---------------------------- +-- 16、定时任务调度日志表 +-- ---------------------------- +drop table if exists sys_job_log cascade; + +create table sys_job_log ( + job_log_id bigserial not null primary key, + job_name varchar(64) not null, + job_group varchar(64) not null, + invoke_target varchar(500) not null, + job_message varchar(500) default null, + status char(1) default '0' not null, + exception_info varchar(2000) default '' not null, + create_time timestamp default current_timestamp not null +); + +comment on table sys_job_log is '定时任务调度日志表'; +comment on column sys_job_log.job_log_id is '任务日志ID'; +comment on column sys_job_log.job_name is '任务名称'; +comment on column sys_job_log.job_group is '任务组名'; +comment on column sys_job_log.invoke_target is '调用目标字符串'; +comment on column sys_job_log.job_message is '日志信息'; +comment on column sys_job_log.status is '执行状态(0正常 1失败)'; +comment on column sys_job_log.exception_info is '异常信息'; +comment on column sys_job_log.create_time is '创建时间'; + +-- ---------------------------- +-- 17、通知公告表 +-- ---------------------------- +drop table if exists sys_notice cascade; + +create table sys_notice ( + notice_id serial not null primary key, + notice_title varchar(50) not null, + notice_type char(1) not null, + notice_content bytea default null, + status char(1) default '0' not null, + create_by varchar(64) default '', + create_time timestamp default current_timestamp, + update_by varchar(64) default '', + update_time timestamp default current_timestamp, + remark varchar(255) default null +); + +comment on table sys_notice is '通知公告表'; +comment on column sys_notice.notice_id is '公告ID'; +comment on column sys_notice.notice_title is '公告标题'; +comment on column sys_notice.notice_type is '公告类型(1通知 2公告)'; +comment on column sys_notice.notice_content is '公告内容'; +comment on column sys_notice.status is '公告状态(0正常 1关闭)'; +comment on column sys_notice.create_by is '创建者'; +comment on column sys_notice.create_time is '创建时间'; +comment on column sys_notice.update_by is '更新者'; +comment on column sys_notice.update_time is '更新时间'; +comment on column sys_notice.remark is '备注'; + +-- ---------------------------- +-- 初始化-公告信息表数据 +-- ---------------------------- +insert into sys_notice (notice_title, notice_type, notice_content, status, create_by, create_time, update_by, update_time, remark) +values('温馨提醒:2018-07-01 若依新版本发布啦', '2', decode('新版本内容', 'escape'), '0', 'admin', current_timestamp, '', null, '管理员'); +insert into sys_notice (notice_title, notice_type, notice_content, status, create_by, create_time, update_by, update_time, remark) +values('维护通知:2018-07-01 若依系统凌晨维护', '1', decode('维护内容', 'escape'), '0', 'admin', current_timestamp, '', null, '管理员'); + + +SELECT setval('sys_menu_menu_id_seq', max(menu_id)) FROM sys_menu WHERE menu_id < 100; \ No newline at end of file