mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2025-12-08 17:12:28 +08:00
Compare commits
62 Commits
v3.7.3spri
...
v3.7.0last
| Author | SHA1 | Date | |
|---|---|---|---|
| 65d737db6d | |||
| f04f7f9abf | |||
| 935e118d15 | |||
| e218367332 | |||
| 3a3f3cf367 | |||
| 0e762b4157 | |||
| f4712baa39 | |||
| 7d8b653d6e | |||
| cf7f3f94be | |||
| 49f63b92ac | |||
| 5670a15b20 | |||
| 9e9ef20b7c | |||
| 0c034031d1 | |||
| 491a038b5a | |||
| 8a4fcb0023 | |||
| e93dcc1a7e | |||
| 383cbf250f | |||
| 9fe1450ac9 | |||
| 88b9b12998 | |||
| 9e25566271 | |||
| 8e54e06978 | |||
| e5c082ae13 | |||
| 96ab98ac3e | |||
| 1632c241ee | |||
| e9d05b0e75 | |||
| 6ade7e22f8 | |||
| 43d47c08cb | |||
| e616c5d8fe | |||
| cddf23c787 | |||
| 70a37309dd | |||
| 48555b5219 | |||
| 06d58f202f | |||
| 628870af9b | |||
| b46a6438e6 | |||
| 5488f99723 | |||
| 6bc1fe8d21 | |||
| 7cac16320c | |||
| 24dbd1db39 | |||
| 46b026b989 | |||
| 94c45f5e0f | |||
| 8950e19d4e | |||
| 99eb88f71c | |||
| 824d7839d8 | |||
| c88f9d95d4 | |||
| beb0bc2f64 | |||
| f741db874c | |||
| d684c09392 | |||
| 364be22dd0 | |||
| 20efa3bf9a | |||
| c7977dda3d | |||
| c27c5a9a9b | |||
| 0ab280f812 | |||
| c3066dac17 | |||
| b650d512b3 | |||
| 925ec9447d | |||
| 411a73c1bf | |||
| 84077e6e24 | |||
| 184cf97304 | |||
| 5f425b49b2 | |||
| 3ac8ee304a | |||
| 0faac01bb7 | |||
| 74d88a8fcc |
5
.gitattributes
vendored
5
.gitattributes
vendored
@ -1,6 +1,5 @@
|
||||
*.js linguist-language=Java
|
||||
*.css linguist-language=Java
|
||||
*.ts linguist-language=vue
|
||||
*.html linguist-language=vue
|
||||
*.html linguist-language=Java
|
||||
*.vue linguist-language=Java
|
||||
*.sql linguist-language=Java
|
||||
*.ftl linguist-language=Java
|
||||
13
.github/ISSUE_TEMPLATE.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
##### 版本号:
|
||||
|
||||
##### 问题描述:
|
||||
|
||||
##### 错误截图:
|
||||
|
||||
|
||||
|
||||
|
||||
#### 友情提示:
|
||||
- 未按格式要求发帖、描述过于简抽象的,会被直接删掉;
|
||||
- 请确保问题描述清楚,方便我们理解并一次性调查解决问题;
|
||||
- 如果使用的不是master,请说明你使用的那个分支
|
||||
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,25 +0,0 @@
|
||||
---
|
||||
name: 提交 Bug
|
||||
about: 请告诉我框架存在的问题,我会协助你解决此问题!
|
||||
labels: bug
|
||||
assignees: getActivity
|
||||
|
||||
---
|
||||
|
||||
|
||||
##### 版本号:
|
||||
|
||||
|
||||
##### 问题描述:
|
||||
|
||||
|
||||
|
||||
##### 错误截图:
|
||||
|
||||
|
||||
|
||||
|
||||
#### 友情提示:
|
||||
- 未按格式要求发帖、描述过于简单的,会被直接删掉;
|
||||
- 描述问题请图文并茂,方便我们理解并快速定位问题;
|
||||
- 如果使用的不是master,请说明你使用的分支;
|
||||
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,25 +0,0 @@
|
||||
---
|
||||
name: 提交建议
|
||||
about: 请告诉我框架的不足之处,让我做得更好!
|
||||
labels: help wanted
|
||||
assignees: getActivity
|
||||
|
||||
---
|
||||
|
||||
|
||||
##### 版本号:
|
||||
|
||||
|
||||
##### 问题描述:
|
||||
|
||||
|
||||
|
||||
##### 错误截图:
|
||||
|
||||
|
||||
|
||||
|
||||
#### 友情提示:
|
||||
- 未按格式要求发帖、描述过于简单的,会被直接删掉;
|
||||
- 描述问题请图文并茂,方便我们理解并快速定位问题;
|
||||
- 如果使用的不是master,请说明你使用的分支;
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -13,3 +13,4 @@ os_del.cmd
|
||||
os_del_doc.cmd
|
||||
.svn
|
||||
derby.log
|
||||
*.log
|
||||
23
LICENSE
23
LICENSE
@ -201,13 +201,16 @@
|
||||
limitations under the License.
|
||||
|
||||
In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software.
|
||||
|
||||
JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京,地址:中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱:jeecgos@163.com
|
||||
本软件受适用的国家软件著作权法(包括国际条约)和开源协议 双重保护许可。
|
||||
|
||||
开源协议中文释意如下:
|
||||
1.JeecgBoot开源版本无任何限制,在遵循本开源协议条款下,允许商用使用,不会造成侵权行为。
|
||||
2.允许基于本平台软件开展业务系统开发。
|
||||
3.在任何情况下,您不得使用本软件开发可能被认为与本软件竞争的软件。
|
||||
|
||||
最终解释权归:http://www.jeecg.com
|
||||
|
||||
开源协议补充
|
||||
JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京,地址:中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱:jeecgos@163.com
|
||||
本软件受适用的国家软件著作权法(包括国际条约)和双重保护许可。
|
||||
|
||||
1.允许基于本平台软件开展业务系统开发。
|
||||
2.JeecgBoot底层依赖的非开源功能:online lib依赖、仪表盘lib依赖等,统一采用LGPL开源协议(不二次改造、不拆分出jeecgboot之外使用,就不产生侵权)
|
||||
3.不得基于该平台软件的基础,修改包装成一个与JeecgBoot平台软件功能类似的产品进行发布、销售,或与JeecgBoot参与同类软件产品市场的竞争。
|
||||
违反此条款属于侵权行为,须赔偿侵权经济损失,同时立即停止著作权侵权行为。
|
||||
|
||||
总结:在遵循Apache开源协议和开源协议补充条款下,允许商用使用,不会造成侵权行为!
|
||||
解释权归:http://www.jeecg.com
|
||||
|
||||
286
README-EN.md
286
README-EN.md
@ -4,15 +4,16 @@
|
||||
|
||||
|
||||
|
||||
JEECG BOOT AI Low Code Platform
|
||||
JEECG BOOT Low Code Development Platform
|
||||
===============
|
||||
|
||||
Current version: 3.7.3 (Release date: 2025-02-10)
|
||||
当前最新版本: 3.7.0(发布日期:2024-06-17)
|
||||
|
||||
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||
[](http://www.jeecg.com)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://jeecg.blog.csdn.net)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
|
||||
@ -21,9 +22,9 @@ Current version: 3.7.3 (Release date: 2025-02-10)
|
||||
Project introduction
|
||||
-----------------------------------
|
||||
|
||||
<h3 align="center">Java AI Low Code Platform for Enterprise web applications</h3>
|
||||
<h3 align="center">Java Low Code Platform for Enterprise web applications</h3>
|
||||
|
||||
JeecgBoot is a `AI low code platform` based on code `generators`! Front and back end separation architecture SpringBoot2.x, SpringCloud, Ant Design&Vue, Mybatis plus, Shiro, JWT, support for microservices. The powerful code generator makes the front and back end of the code generation, low code development! JeecgBoot leads a new low-code development paradigm (OnlineCoding-> Code Generator -> Manual MERGE) that helps resolve 70% of the duplication in Java projects and makes development more business-focused. Not only can quickly improve efficiency, save research and development costs, but also do not lose flexibility!
|
||||
JeecgBoot is a `low code development platform` based on code `generators`! Front and back end separation architecture SpringBoot2.x, SpringCloud, Ant Design&Vue, Mybatis plus, Shiro, JWT, support for microservices. The powerful code generator makes the front and back end of the code generation, low code development! JeecgBoot leads a new low-code development paradigm (OnlineCoding-> Code Generator -> Manual MERGE) that helps resolve 70% of the duplication in Java projects and makes development more business-focused. Not only can quickly improve efficiency, save research and development costs, but also do not lose flexibility!
|
||||
|
||||
JeecgBoot provides a series of low code modules to make Online development truly zero code: Online form development, online reports, report configuration capabilities, online chart design, large screen design, mobile configuration capabilities, form designer, online design flow, process automation configuration, plug-in capabilities (pluggable) and more!
|
||||
|
||||
@ -32,13 +33,15 @@ The purpose of JEECG is: simple functions are implemented by OnlineCoding config
|
||||
|
||||
JEECG Business process: Using workflow to implement and extend the task interface for developing and writing business logic, forms provides a variety of solutions: form designer, online configuration form, and coding form. At the same time, the separation design of process and form (loose coupling) is realized, and the flexible configuration of task nodes is supported, which not only ensures the confidentiality of the company's process, but also reduces the workload of developers.
|
||||
|
||||
AI Empowering Low-Code: Currently, JeecgBoot supports AI large models such as ChatGPT and DeepSeek. The latest version defaults to using DeepSeek, which offers faster speed and higher quality. It now provides features such as AI chat assistant, AI table creation, and AI report generation.
|
||||
|
||||
Technical support
|
||||
-----------------------------------
|
||||
|
||||
Problems or bugs in use can be found in [Making on the Issues](https://github.com/jeecgboot/JeecgBoot/issues/new)
|
||||
|
||||
Official Support: http://jeecg.com/doc/help
|
||||
|
||||
|
||||
|
||||
##### Project description
|
||||
|
||||
@ -58,14 +61,17 @@ Download other source code
|
||||
|
||||
For the project
|
||||
-----------------------------------
|
||||
Jeecg-Boot AI low code platform can be applied in the development of any J2EE project, especially for SAAS projects, enterprise information management system (MIS), internal office system (OA), enterprise resource planning system (ERP), customer relationship management system (CRM), etc. Its semi-intelligent manual Merge development method, Can significantly improve the development efficiency of more than 70%, greatly reduce the development cost.
|
||||
Jeecg-Boot low code development platform can be applied in the development of any J2EE project, especially for SAAS projects, enterprise information management system (MIS), internal office system (OA), enterprise resource planning system (ERP), customer relationship management system (CRM), etc. Its semi-intelligent manual Merge development method, Can significantly improve the development efficiency of more than 70%, greatly reduce the development cost.
|
||||
|
||||
|
||||
Starts the project
|
||||
|
||||
Docker starts the project
|
||||
-----------------------------------
|
||||
|
||||
- [IDEA Quick start](https://help.jeecg.com/java/setup/idea/startup.html)
|
||||
- [Docker Quick start](https://help.jeecg.com/java/docker/quick.html)
|
||||
- [Docker starts the monomer background](https://help.jeecg.com/java/setup/docker/up.html)
|
||||
- [Docker starts the front-end](http://help.jeecg.com/publish/docker.html)
|
||||
- [Docker starts the micro-service background](https://help.jeecg.com/java/springcloud/docker.html)
|
||||
- [ChatGPT AI Config](https://help.jeecg.com/java/chatgpt.html)
|
||||
|
||||
|
||||
|
||||
@ -73,14 +79,12 @@ Technical documentation
|
||||
-----------------------------------
|
||||
|
||||
- Website: [http://www.jeecg.com](http://www.jeecg.com)
|
||||
- Doc: [http://help.jeecg.com](http://help.jeecg.com)
|
||||
- Newbie guide: [Quick start](http://www.jeecg.com/doc/quickstart) | [video](https://space.bilibili.com/454617261/channel/series) | [Q&A ](http://www.jeecg.com/doc/qa) | [help](http://jeecg.com/doc/help) | [1 minute experience](https://my.oschina.net/jeecg/blog/3083313)
|
||||
- Microservice Development: [Monomer upgrade to microservice](https://help.jeecg.com/java/springcloud/switchcloud/monomer.html)
|
||||
- QQ group : ⑨808791225、⑧825232878、⑦791696430、⑥730954414(full)、683903138(full)、⑤860162132(full)、④774126647(full)、③816531124(full)、②769925425(full)、①284271917(full)
|
||||
- Demo : [OnlineDemo](http://boot3.jeecg.com) | [APP](http://jeecg.com/appIndex)
|
||||
- Doc: [DocumentCenter](http://help.jeecg.com) | [AI Config](https://help.jeecg.com/java/ai/aichat.html)
|
||||
- Newbie guide: [Quick start](http://www.jeecg.com/doc/quickstart) | [Q&A ](http://www.jeecg.com/doc/qa) | [1 minute experience](https://my.oschina.net/jeecg/blog/3083313)
|
||||
- QQ group : ⑩716488839、⑨808791225、⑧825232878、⑦791696430、⑥730954414(full)、683903138(full)、⑤860162132(full)、④774126647(full)、③816531124(full)、②769925425(full)、①284271917(full)
|
||||
|
||||
|
||||
|
||||
|
||||
> [please click obtain account password to obtain](http://jeecg.com/doc/demo)
|
||||
|
||||
|
||||
Star charts
|
||||
@ -176,7 +180,7 @@ Technical Architecture:
|
||||
|
||||
#### Development Environment
|
||||
|
||||
- Language: Java 8+ (17)
|
||||
- Language: Java 8+ (less than 17)
|
||||
|
||||
- IDE(JAVA) : IDEA (lombok plug-in must be installed)
|
||||
|
||||
@ -186,20 +190,20 @@ Technical Architecture:
|
||||
|
||||
- Cache: Redis
|
||||
|
||||
- Database: MySQL5.7 + [More Databases](https://my.oschina.net/jeecg/blog/4905722)
|
||||
- Database: MySQL5.7 + & Oracle 11 g & Sqlserver2017 [More Databases](https://my.oschina.net/jeecg/blog/4905722)
|
||||
|
||||
|
||||
#### backend
|
||||
|
||||
- Basic framework: Spring Boot 2.7.18
|
||||
- Basic framework: Spring Boot 2.6.14
|
||||
|
||||
- Microservice framework: Spring Cloud Alibaba 2021.0.1.0
|
||||
|
||||
- Persistence layer framework: MybatisPlus 3.5.3.2
|
||||
- Persistence layer framework: MybatisPlus 3.5.1
|
||||
|
||||
- Report tool: JimuReport 1.9.3
|
||||
- Report tool: JimuReport 1.5.8
|
||||
|
||||
- Security framework: Apache Shiro 1.12.0, Jwt 3.11.0
|
||||
- Security framework: Apache Shiro 1.10.0, Jwt 3.11.0
|
||||
|
||||
- Microservice technology stack: Spring Cloud Alibaba, Nacos, Gateway, Sentinel, Skywalking
|
||||
|
||||
@ -214,14 +218,6 @@ Technical Architecture:
|
||||
|
||||
- TechnologyStack:`Vue3.0+TypeScript+Vite+AntDesignVue+pinia+echarts`
|
||||
|
||||
#### Front-end environment requirements
|
||||
|
||||
* `Node.js 、npm 、pnpm`
|
||||
* pnpm `v9+` is now required.
|
||||
* Node.js Version suggestion: `v20.15.0`
|
||||
` ( Since Vite6 Node.js 18/20 + is now required )`
|
||||
|
||||
|
||||
#### Support library
|
||||
|
||||
| database | support |
|
||||
@ -231,38 +227,44 @@ Technical Architecture:
|
||||
| Sqlserver2017 | √ |
|
||||
| PostgreSQL | √ |
|
||||
| MariaDB | √ |
|
||||
| 达梦 | √ |
|
||||
| 人大金仓 | √ |
|
||||
| TiDB | √ |
|
||||
| 达梦、人大金仓 | √ |
|
||||
|
||||
|
||||
#### AI Support
|
||||
|
||||
| AI Model | Supported |
|
||||
| --- | --- |
|
||||
| DeepSeek | √ |
|
||||
| ChatGPT | √ |
|
||||
|
||||
AI Config: https://help.jeecg.com/java/ai/aichat.html
|
||||
|
||||
## Microservice solutions
|
||||
|
||||
- 1. Service registration and discovery Nacos √
|
||||
- 2. Nacos √
|
||||
- 3. Route gateway gateway(Three loading modes) √
|
||||
- 4. Distributed http feign √
|
||||
- 5. fuse degrade current limiting Sentinel √
|
||||
- 6. Distributed files Minio and Alioss √
|
||||
- 7. Unified permission control
|
||||
- 8. Service monitoring SpringBootAdmin√
|
||||
- 9. link tracking Skywalking [reference document](https://help.jeecg.com/java/springcloud/super/skywarking.html)
|
||||
- 10. Messaging middleware RabbitMQ √
|
||||
- 11. Distributed task xxl-job √
|
||||
- 12. Distributed Transaction Seata
|
||||
- 13. Distributed log Loki+grafana
|
||||
- 14. Support docker-compose, k8s, jenkins
|
||||
- 15. CAS SSO √
|
||||
- 16. Route traffic limiting √
|
||||
|
||||
1. Service registration and discovery Nacos √
|
||||
|
||||
2. Nacos √
|
||||
|
||||
3. Route gateway gateway(Three loading modes) √
|
||||
|
||||
4. Distributed http feign √
|
||||
|
||||
5. fuse degrade current limiting Sentinel √
|
||||
|
||||
6. Distributed files Minio and Alioss √
|
||||
|
||||
7. Unified permission control
|
||||
|
||||
8. Service monitoring SpringBootAdmin√
|
||||
|
||||
9. link tracking Skywalking [reference document](https://help.jeecg.com/java/springcloud/super/skywarking.html)
|
||||
|
||||
10. Messaging middleware RabbitMQ √
|
||||
|
||||
11. Distributed task xxl-job √
|
||||
|
||||
12. Distributed Transaction Seata
|
||||
|
||||
13. Distributed log elk + kafka
|
||||
|
||||
14. Support docker-compose, k8s, jenkins
|
||||
|
||||
15. CAS SSO √
|
||||
|
||||
16. Route traffic limiting √
|
||||
|
||||
|
||||
#### Microservice architecture diagram
|
||||
@ -271,9 +273,157 @@ AI Config: https://help.jeecg.com/java/ai/aichat.html
|
||||
### Jeecg Boot product functionality blueprint
|
||||

|
||||
|
||||
### quick start
|
||||
- Microservice Development: [Monomer upgrade to microservice](https://help.jeecg.com/java/springcloud/switchcloud/monomer.html)
|
||||
- [Docker starts the micro-service background](https://help.jeecg.com/java/docker/springcloud.html)
|
||||
|
||||
|
||||
|
||||
### Function module
|
||||
```
|
||||
├─系统管理
|
||||
│ ├─用户管理
|
||||
│ ├─角色管理
|
||||
│ ├─菜单管理
|
||||
│ ├─权限设置(支持按钮权限、数据权限)
|
||||
│ ├─表单权限(控制字段禁用、隐藏)
|
||||
│ ├─部门管理
|
||||
│ ├─我的部门(二级管理员)
|
||||
│ └─字典管理
|
||||
│ └─分类字典
|
||||
│ └─系统公告
|
||||
│ └─职务管理
|
||||
│ └─通讯录
|
||||
│ └─多租户管理
|
||||
├─消息中心
|
||||
│ ├─消息管理
|
||||
│ ├─模板管理
|
||||
├─代码生成器(低代码)
|
||||
│ ├─代码生成器功能(一键生成前后端代码,生成后无需修改直接用,绝对是后端开发福音)
|
||||
│ ├─代码生成器模板(提供4套模板,分别支持单表和一对多模型,不同风格选择)
|
||||
│ ├─代码生成器模板(生成代码,自带excel导入导出)
|
||||
│ ├─查询过滤器(查询逻辑无需编码,系统根据页面配置自动生成)
|
||||
│ ├─高级查询器(弹窗自动组合查询条件)
|
||||
│ ├─Excel导入导出工具集成(支持单表,一对多 导入导出)
|
||||
│ ├─平台移动自适应支持
|
||||
├─系统监控
|
||||
│ ├─Gateway路由网关
|
||||
│ ├─性能扫描监控
|
||||
│ │ ├─监控 Redis
|
||||
│ │ ├─Tomcat
|
||||
│ │ ├─jvm
|
||||
│ │ ├─服务器信息
|
||||
│ │ ├─请求追踪
|
||||
│ │ ├─磁盘监控
|
||||
│ ├─定时任务
|
||||
│ ├─系统日志
|
||||
│ ├─消息中心(支持短信、邮件、微信推送等等)
|
||||
│ ├─数据日志(记录数据快照,可对比快照,查看数据变更情况)
|
||||
│ ├─系统通知
|
||||
│ ├─SQL监控
|
||||
│ ├─swagger-ui(在线接口文档)
|
||||
│─报表示例
|
||||
│ ├─曲线图
|
||||
│ └─饼状图
|
||||
│ └─柱状图
|
||||
│ └─折线图
|
||||
│ └─面积图
|
||||
│ └─雷达图
|
||||
│ └─仪表图
|
||||
│ └─进度条
|
||||
│ └─排名列表
|
||||
│ └─等等
|
||||
│─大屏模板
|
||||
│ ├─作战指挥中心大屏
|
||||
│ └─物流服务中心大屏
|
||||
│─常用示例
|
||||
│ ├─自定义组件
|
||||
│ ├─对象存储(对接阿里云)
|
||||
│ ├─JVXETable示例(各种复杂ERP布局示例)
|
||||
│ ├─单表模型例子
|
||||
│ └─一对多模型例子
|
||||
│ └─打印例子
|
||||
│ └─一对多TAB例子
|
||||
│ └─内嵌table例子
|
||||
│ └─常用选择组件
|
||||
│ └─异步树table
|
||||
│ └─接口模拟测试
|
||||
│ └─表格合计示例
|
||||
│ └─异步树列表示例
|
||||
│ └─一对多JEditable
|
||||
│ └─JEditable组件示例
|
||||
│ └─图片拖拽排序
|
||||
│ └─图片翻页
|
||||
│ └─图片预览
|
||||
│ └─PDF预览
|
||||
│ └─分屏功能
|
||||
│─封装通用组件
|
||||
│ ├─行编辑表格JEditableTable
|
||||
│ └─省略显示组件
|
||||
│ └─时间控件
|
||||
│ └─高级查询
|
||||
│ └─用户选择组件
|
||||
│ └─报表组件封装
|
||||
│ └─字典组件
|
||||
│ └─下拉多选组件
|
||||
│ └─选人组件
|
||||
│ └─选部门组件
|
||||
│ └─通过部门选人组件
|
||||
│ └─封装曲线、柱状图、饼状图、折线图等等报表的组件(经过封装,使用简单)
|
||||
│ └─在线code编辑器
|
||||
│ └─上传文件组件
|
||||
│ └─验证码组件
|
||||
│ └─树列表组件
|
||||
│ └─表单禁用组件
|
||||
│ └─等等
|
||||
│─更多页面模板
|
||||
│ ├─各种高级表单
|
||||
│ ├─各种列表效果
|
||||
│ └─结果页面
|
||||
│ └─异常页面
|
||||
│ └─个人页面
|
||||
├─高级功能
|
||||
│ ├─系统编码规则
|
||||
│ ├─提供单点登录CAS集成方案
|
||||
│ ├─提供APP发布方案
|
||||
│ ├─集成Websocket消息通知机制
|
||||
├─Online在线开发(低代码)
|
||||
│ ├─Online在线表单 - 功能已开放
|
||||
│ ├─Online代码生成器 - 功能已开放
|
||||
│ ├─Online在线报表 - 功能已开放
|
||||
│ ├─Online在线图表(未开源)
|
||||
│ ├─Online图表模板配置(未开源)
|
||||
│ ├─Online布局设计(未开源)
|
||||
│ ├─多数据源管理 - 功能已开放
|
||||
├─积木报表设计器(低代码)
|
||||
│ ├─打印设计器
|
||||
│ ├─数据报表设计
|
||||
│ ├─图形报表设计(支持echart)
|
||||
│ ├─大屏设计器(未开源)
|
||||
│─流程模块功能 (未开源)
|
||||
│ ├─流程设计器
|
||||
│ ├─表单设计器
|
||||
├─大屏设计器
|
||||
├─门户设计/仪表盘设计器
|
||||
│ └─我的任务
|
||||
│ └─历史流程
|
||||
│ └─历史流程
|
||||
│ └─流程实例管理
|
||||
│ └─流程监听管理
|
||||
│ └─流程表达式
|
||||
│ └─我发起的流程
|
||||
│ └─我的抄送
|
||||
│ └─流程委派、抄送、跳转
|
||||
│ └─。。。
|
||||
│─OA办公组件 (未开源)
|
||||
│ ├─更多功能
|
||||
│ └─。。。
|
||||
└─其他模块
|
||||
└─更多功能开发中。。
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Effect of system
|
||||
@ -320,22 +470,10 @@ AI Config: https://help.jeecg.com/java/ai/aichat.html
|
||||

|
||||
|
||||
##### dashboard Designer
|
||||
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
##### report Designer
|
||||

|
||||
|
||||
|
||||
408
README.md
408
README.md
@ -1,202 +1,106 @@
|
||||
|
||||
JeecgBoot AI低代码平台
|
||||
JeecgBoot 低代码开发平台
|
||||
===============
|
||||
|
||||
当前最新版本: 3.7.3(发布日期:2025-02-10)
|
||||
当前最新版本: 3.7.0(发布日期:2024-06-17)
|
||||
|
||||
|
||||
[](https://github.com/jeecgboot/JeecgBoot/blob/master/LICENSE)
|
||||
[](http://guojusoft.com)
|
||||
[](https://github.com/jeecgboot/JeecgBoot)
|
||||
[](https://github.com/jeecgboot/JeecgBoot)
|
||||
[](https://github.com/jeecgboot/JeecgBoot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||
[](http://jeecg.com/aboutusIndex)
|
||||
[](https://jeecg.blog.csdn.net)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
|
||||
|
||||
|
||||
项目介绍
|
||||
-----------------------------------
|
||||
|
||||
<h3 align="center">Java AI Low Code Platform for Enterprise web applications</h3>
|
||||
<h3 align="center">Java Low Code Platform for Enterprise web applications</h3>
|
||||
|
||||
JeecgBoot 是一款基于`BPM`和`代码生成器`的 AI低代码平台!前后端分离架构 SpringBoot2.x/3.x,SpringCloud,Ant Design Vue3,Mybatis-plus,Shiro,JWT,支持微服务、多租户;支持 AI 大模型 DeepSeek 和 ChatGPT、Ollama本地模型; 强大的代码生成器让前后端代码一键生成,无需写任何代码! JeecgBoot 引领 AI 低代码开发模式(AI生成-> OnlineCoding-> 代码生成器-> 手工MERGE), 帮助解决Java项目80%的重复工作,让开发更多关注业务。既能快速提高效率,节省成本,同时又不失灵活性!AIGC能力:AI对话助手、AI建表、AI写文章、AI流程编排、AI知识库问答等等.
|
||||
JeecgBoot 是一款基于代码生成器的`低代码开发平台`!前后端分离架构 SpringBoot2.x和3.x,SpringCloud,Ant Design&Vue,Mybatis-plus,Shiro,JWT,支持微服务。强大的代码生成器让前后端代码一键生成,实现低代码开发! JeecgBoot 引领新的低代码开发模式(OnlineCoding-> 代码生成器-> 手工MERGE), 帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高效率,节省研发成本,同时又不失灵活性!
|
||||
|
||||
JeecgBoot 提供了一系列 `AI能力` `低代码模块`,实现在线开发`真正的零代码`:Online表单开发、Online报表、报表配置能力、在线图表设计、仪表盘设计、大屏设计、移动配置能力、表单设计器、在线设计流程、流程自动化配置、插件能力(可插拔)、AI对话助手,AI建表、AI写文章、AI流程编排、AI知识库问答、AI赋能低代码等等!
|
||||
JeecgBoot 提供了一系列`低代码模块`,实现在线开发`真正的零代码`:Online表单开发、Online报表、报表配置能力、在线图表设计、仪表盘设计、大屏设计、移动配置能力、表单设计器、在线设计流程、流程自动化配置、插件能力(可插拔)等等!
|
||||
|
||||
|
||||
`JEECG宗旨是:` 简单功能由OnlineCoding配置实现,做到`零代码开发`;复杂功能由代码生成器生成进行手工Merge 实现`低代码开发`,既保证了`智能`又兼顾`灵活`;实现了低代码开发的同时又支持灵活编码,解决了当前低代码产品普遍不灵活的弊端!
|
||||
|
||||
`JEECG业务流程:` 采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计(松耦合)、并支持任务节点灵活配置,既保证了公司流程的保密性,又减少了开发人员的工作量。
|
||||
|
||||
`AI赋能低代码:` 目前JeecgBoot支持AI大模型`ChatGPT`和`DeepSeek`,现在最新版默认使用`DeepSeek`,速度更快质量更高。目前提供了AI对话助手、AI建表、AI报表、AI写文章、AI流程编排、AI知识库问答等功能。
|
||||
|
||||
|
||||
适用项目
|
||||
-----------------------------------
|
||||
JeecgBoot AI低代码平台,可以应用在任何J2EE项目的开发中,支持信创国产化(默认适配达梦和人大金仓)。尤其适合SAAS项目、企业信息管理系统(MIS)、内部办公系统(OA)、企业资源计划系统(ERP)、客户关系管理系统(CRM)等,其半智能手工Merge的开发方式,可以显著提高开发效率70%以上,极大降低开发成本。
|
||||
Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,支持信创国产化(默认适配达梦和人大金仓)。尤其适合SAAS项目、企业信息管理系统(MIS)、内部办公系统(OA)、企业资源计划系统(ERP)、客户关系管理系统(CRM)等,其半智能手工Merge的开发方式,可以显著提高开发效率70%以上,极大降低开发成本。
|
||||
|
||||
|
||||
|
||||
### 视频介绍
|
||||
|
||||
[](https://www.bilibili.com/video/BV1Nk4y1o7Qc)
|
||||
|
||||
|
||||
#### 项目说明
|
||||
|
||||
| 项目名 | 说明 |
|
||||
|--------------------|------------------------|
|
||||
| `jeecg-boot` | 后端源码JAVA(SpringBoot微服务架构) |
|
||||
| `jeecgboot-vue3` | 前端源码VUE3(vue3+vite6+ts最新技术栈) |
|
||||
| `jeecg-uniapp` | [配套APP框架](https://github.com/jeecgboot/jeecg-uniapp) 适配多个终端,支持APP、小程序、H5 |
|
||||
| `jeecgboot-vue3` | 前端源码VUE3(vue3+vite5+ts最新技术栈) |
|
||||
| `jeecg-uniapp` | APP框架,一份代码多终端适配,支持APP、小程序、H5 |
|
||||
|
||||
|
||||
其他源码
|
||||
-----------------------------------
|
||||
- APP源码地址:https://github.com/jeecgboot/jeecg-uniapp
|
||||
|
||||
|
||||
技术支持
|
||||
-----------------------------------
|
||||
|
||||
关闭gitee的issue通道,使用中遇到问题或者BUG可以在 [Github上提Issues](https://github.com/jeecgboot/JeecgBoot/issues/new)
|
||||
|
||||
|
||||
快速启动项目
|
||||
-----------------------------------
|
||||
|
||||
- [前端项目快速启动](http://help.jeecg.com/setup/startup.html)
|
||||
- [通过IDEA启动前后端项目](https://help.jeecg.com/java/setup/idea/startup.html)
|
||||
|
||||
|
||||
|
||||
Docker启动项目
|
||||
-----------------------------------
|
||||
|
||||
- [Docker启动前端](http://help.jeecg.com/publish/docker.html)
|
||||
- [Docker启动后台](https://help.jeecg.com/java/setup/docker/up.html)
|
||||
|
||||
|
||||
微服务方式启动
|
||||
-----------------------------------
|
||||
|
||||
- [单体快速切换微服务](https://help.jeecg.com/java/springcloud/switchcloud/monomer.html)
|
||||
- [Docker启动微服务后台](https://help.jeecg.com/java/springcloud/docker.html)
|
||||
|
||||
|
||||
技术文档
|
||||
-----------------------------------
|
||||
|
||||
- 官方网站: [http://www.jeecg.com](http://www.jeecg.com)
|
||||
- 产品官网: [http://www.jeecg.com](http://www.jeecg.com)
|
||||
- 开发文档: [https://help.jeecg.com](https://help.jeecg.com)
|
||||
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [常见问题 ](http://www.jeecg.com/doc/qa) | [视频教程](https://space.bilibili.com/454617261/channel/series) | [1分钟低代码体验](https://my.oschina.net/jeecg/blog/3083313)
|
||||
- AI助手配置: https://help.jeecg.com/java/chatgpt.html
|
||||
|
||||
- 在线演示 : [在线演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex)
|
||||
- 快速体验: [一分钟体验低代码](https://jeecg.blog.csdn.net/article/details/106079007?spm=1001.2014.3001.5502 "一分钟体验零代码") | [在线体验零代码](https://app.qiaoqiaoyun.com/myapps/index "在线体验零代码")
|
||||
- 开发文档: [文档中心](https://help.jeecg.com) | [AI集成配置(支持DeepSeek)](https://help.jeecg.com/java/ai/aichat.html)
|
||||
- 反馈问题: [在Github上提Issues](https://github.com/jeecgboot/JeecgBoot/issues/new)
|
||||
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [入门视频](http://jeecg.com/doc/video)
|
||||
- QQ交流群 : ⑩716488839、⑨808791225(满)、其他(满)
|
||||
|
||||
|
||||
|
||||
|
||||
启动项目
|
||||
-----------------------------------
|
||||
|
||||
- [IDEA启动前后端项目](https://help.jeecg.com/java/setup/idea/startup.html)
|
||||
- [Docker一键启动前后端](https://help.jeecg.com/java/docker/quick.html)
|
||||
|
||||
|
||||
AIGC功能清单
|
||||
-----------------------------------
|
||||
|
||||
- AI对聊天助手
|
||||
- AI建表(Online表单)
|
||||
- AI写文章(CMS)
|
||||
- AI表单建议(表单设计器)
|
||||
- AI流程编排(研发中)
|
||||
- AI知识库问答系统(研发中)
|
||||
- AI应用开发平台(研发中)
|
||||
- AI聊天窗口支持嵌入第三方(研发中)
|
||||
|
||||
|
||||
<b>关注公众号了解官方动态</b>
|
||||
|
||||

|
||||
|
||||
|
||||
技术架构:
|
||||
-----------------------------------
|
||||
|
||||
#### 后端
|
||||
|
||||
- IDE建议: IDEA (必须安装lombok插件 )
|
||||
- 语言:Java 8+ (支持17)
|
||||
- 依赖管理:Maven
|
||||
- 基础框架:Spring Boot 2.7.18
|
||||
- 微服务框架: Spring Cloud Alibaba 2021.0.1.0
|
||||
- 持久层框架:MybatisPlus 3.5.3.2
|
||||
- 报表工具: JimuReport 1.9.3
|
||||
- 安全框架:Apache Shiro 1.12.0,Jwt 3.11.0
|
||||
- 微服务技术栈:Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywalking
|
||||
- 数据库连接池:阿里巴巴Druid 1.1.22
|
||||
- AI大模型:支持 `ChatGPT` `DeepSeek`切换
|
||||
- 日志打印:logback
|
||||
- 缓存:Redis
|
||||
- 其他:autopoi, fastjson,poi,Swagger-ui,quartz, lombok(简化代码)等。
|
||||
- 默认数据库脚本:MySQL5.7+
|
||||
- [其他数据库,需要自己转](https://my.oschina.net/jeecg/blog/4905722)
|
||||
|
||||
|
||||
#### 前端
|
||||
|
||||
- 前端IDE建议:WebStorm、Vscode
|
||||
- 采用 Vue3.0+TypeScript+Vite6+Ant-Design-Vue等新技术方案,包括二次封装组件、utils、hooks、动态菜单、权限校验、按钮级别权限控制等功能
|
||||
- 最新技术栈:Vue3.0 + TypeScript + Vite6 + ant-design-vue4 + pinia + echarts + unocss + vxe-table + qiankun + es6
|
||||
- 依赖管理:node、npm、pnpm
|
||||
|
||||
|
||||
#### 前端环境要求
|
||||
|
||||
* 本地环境安装 `Node.js 、npm 、pnpm`
|
||||
* pnpm 要求`9+` 版本以上
|
||||
* Node.js 版本建议`v20.15.0`,要求`Node 20+` 版本以上
|
||||
|
||||
` ( 因为Vite6 需要 Node.js 18 / 20+ )`
|
||||
|
||||
|
||||
#### 支持库
|
||||
|
||||
| 数据库 | 支持 |
|
||||
| --- | --- |
|
||||
| MySQL | √ |
|
||||
| Oracle11g | √ |
|
||||
| Sqlserver2017 | √ |
|
||||
| PostgreSQL | √ |
|
||||
| MariaDB | √ |
|
||||
| MariaDB | √ |
|
||||
| 达梦 | √ |
|
||||
| 人大金仓 | √ |
|
||||
| TiDB | √ |
|
||||
|
||||
#### 支持AI大模型
|
||||
|
||||
| AI大模型 | 支持 |
|
||||
| --- | --- |
|
||||
| DeepSeek | √ |
|
||||
| ChatGTP | √ |
|
||||
| Ollama本地搭建大模型 | √ |
|
||||
|
||||
AI集成文档: https://help.jeecg.com/java/ai/aichat.html
|
||||
|
||||
|
||||
## 微服务解决方案
|
||||
|
||||
|
||||
- 1、服务注册和发现 Nacos √
|
||||
- 2、统一配置中心 Nacos √
|
||||
- 3、路由网关 gateway(三种加载方式) √
|
||||
- 4、分布式 http feign √
|
||||
- 5、熔断降级限流 Sentinel √
|
||||
- 6、分布式文件 Minio、阿里OSS √
|
||||
- 7、统一权限控制 JWT + Shiro √
|
||||
- 8、服务监控 SpringBootAdmin√
|
||||
- 9、链路跟踪 Skywalking [参考文档](https://help.jeecg.com/java/springcloud/super/skywarking.html)
|
||||
- 10、消息中间件 RabbitMQ √
|
||||
- 11、分布式任务 xxl-job √
|
||||
- 12、分布式事务 Seata
|
||||
- 13、轻量分布式日志 Loki+grafana套件
|
||||
- 14、支持 docker-compose、k8s、jenkins
|
||||
- 15、CAS 单点登录 √
|
||||
- 16、路由限流 √
|
||||
|
||||
|
||||
#### 微服务方式启动
|
||||
|
||||
- [单体快速切换微服务](https://help.jeecg.com/java/springcloud/switchcloud/monomer.html)
|
||||
- [Docker一键启动微服务前后端](https://help.jeecg.com/java/docker/quickcloud.html)
|
||||
|
||||
|
||||
#### 微服务架构图
|
||||

|
||||
|
||||
> 演示系统的登录账号密码,请点击 [获取账号密码](http://jeecg.com/doc/demo) 获取
|
||||
>
|
||||
- QQ交流群 : ⑨808791225、⑧825232878、⑦791696430(满)、⑥730954414(满)、683903138(满)、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
|
||||
|
||||
|
||||
为什么选择JeecgBoot?
|
||||
-----------------------------------
|
||||
* 1.采用最新主流前后分离框架(Springboot+Mybatis+antd+vue3),容易上手; 代码生成器依赖性低,灵活的扩展能力,可快速实现二次开发;
|
||||
* 1.采用最新主流前后分离框架(Springboot+Mybatis+antd),容易上手; 代码生成器依赖性低,灵活的扩展能力,可快速实现二次开发;
|
||||
* 2.支持微服务SpringCloud Alibaba(Nacos、Gateway、Sentinel、Skywalking),提供切换机制支持单体和微服务自由切换
|
||||
* 3.开发效率高,采用代码生成器,单表、树列表、一对多、一对一等数据模型,增删改查功能一键生成,菜单配置直接使用;引入AI能力,支持自动建表等功能;
|
||||
* 3.开发效率高,采用代码生成器,单表、树列表、一对多、一对一等数据模型,增删改查功能一键生成,菜单配置直接使用;
|
||||
* 4.代码生成器提供强大模板机制,支持自定义模板,目前提供四套风格模板(单表两套、树模型一套、一对多三套)
|
||||
* 5.代码生成器非常智能,在线业务建模、在线配置、所见即所得支持23种类控件,一键生成前后端代码,大幅度提升开发效率,不再为重复工作发愁。
|
||||
* 6.AI能力:目前JeecgBoot支持AI大模型chatgpt和deepseek,现在最新版默认使用deepseek,速度更快质量更高。目前提供了AI对话助手、AI建表、AI报表等功能。
|
||||
* 6.低代码能力:Online在线表单(无需编码,通过在线配置表单,实现表单的增删改查,支持单表、树、一对多、一对一等模型,实现人人皆可编码)
|
||||
* 7.低代码能力:Online在线报表、Online在线图表(无需编码,通过在线配置方式,实现数据报表和图形报表,可以快速抽取数据,减轻开发压力,实现人人皆可编码)
|
||||
* 7.低代码能力:Online在线报表(无需编码,通过在线配置方式,实现数据报表,可以快速抽取数据,减轻开发压力,实现人人皆可编码)
|
||||
* 8.低代码能力:Online在线图表(无需编码,通过在线配置方式,实现曲线图,柱状图,数据报表等,支持自定义排版布局,实现人人皆可编码)
|
||||
* 9.封装完善的用户、角色、菜单、组织机构、数据字典、在线定时任务等基础功能,支持访问授权、按钮权限、数据权限等功能
|
||||
* 10.常用共通封装,各种工具类(定时任务,短信接口,邮件发送,Excel导入导出等),基本满足80%项目需求
|
||||
* 11.简易Excel导入导出,支持单表导出和一对多表模式导出,生成的代码自带导入导出功能
|
||||
@ -207,7 +111,7 @@ AI集成文档: https://help.jeecg.com/java/ai/aichat.html
|
||||
* 16.页面校验自动生成(必须输入、数字校验、金额校验、时间空间等);
|
||||
* 17.支持SAAS服务模式,提供SaaS多租户架构方案。
|
||||
* 18.分布式文件服务,集成minio、阿里OSS等优秀的第三方,提供便捷的文件上传与管理,同时也支持本地存储。
|
||||
* 19.主流数据库兼容,一套代码完全兼容Mysql、Postgresql、Oracle、Sqlserver、MariaDB、达梦、人大金仓等主流数据库。
|
||||
* 19.主流数据库兼容,一套代码完全兼容Mysql、Postgresql、Oracle、Sqlserver、MariaDB、达梦等主流数据库。
|
||||
* 20.集成工作流flowable,并实现了只需在页面配置流程转向,可极大的简化bpm工作流的开发;用bpm的流程设计器画出了流程走向,一个工作流基本就完成了,只需写很少量的java代码;
|
||||
* 21.低代码能力:在线流程设计,采用开源flowable流程引擎,实现在线画流程,自定义表单,表单挂靠,业务流转
|
||||
* 22.多数据源:及其简易的使用方式,在线配置数据源配置,便捷的从其他数据抓取数据;
|
||||
@ -230,8 +134,103 @@ AI集成文档: https://help.jeecg.com/java/ai/aichat.html
|
||||
* 39.支持菜单动态路由
|
||||
* 40.权限控制采用 RBAC(Role-Based Access Control,基于角色的访问控制)
|
||||
* 41.提供新行编辑表格JVXETable,轻松满足各种复杂ERP布局,拥有更高的性能、更灵活的扩展、更强大的功能
|
||||
* 42.提供仪表盘设计器,类大屏设计支持移动端,免费的数据可视化设计工具,支持丰富的数据源连接,能够通过拖拉拽方式快速制作图表和门户设计;目前支持多种图表类型:柱形图、折线图、散点图、饼图、环形图、面积图、漏斗图、进度图、仪表盘、雷达图、地图等等;
|
||||
|
||||
|
||||
|
||||
|
||||
技术架构:
|
||||
-----------------------------------
|
||||
#### 开发环境
|
||||
|
||||
- 语言:Java 8+ (小于17)
|
||||
|
||||
- IDE(JAVA): IDEA (必须安装lombok插件 )
|
||||
|
||||
- IDE(前端): Vscode、WebStorm、IDEA
|
||||
|
||||
- 依赖管理:Maven
|
||||
|
||||
- 缓存:Redis
|
||||
|
||||
- 数据库脚本:MySQL5.7+ (其他数据库,[需要自己转](https://my.oschina.net/jeecg/blog/4905722))
|
||||
|
||||
|
||||
#### 后端
|
||||
|
||||
- 基础框架:Spring Boot 2.6.14
|
||||
|
||||
- 微服务框架: Spring Cloud Alibaba 2021.0.1.0
|
||||
|
||||
- 持久层框架:MybatisPlus 3.5.1
|
||||
|
||||
- 报表工具: JimuReport 1.5.8
|
||||
|
||||
- 安全框架:Apache Shiro 1.10.0,Jwt 3.11.0
|
||||
|
||||
- 微服务技术栈:Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywalking
|
||||
|
||||
- 数据库连接池:阿里巴巴Druid 1.1.22
|
||||
|
||||
- 日志打印:logback
|
||||
|
||||
- 其他:autopoi, fastjson,poi,Swagger-ui,quartz, lombok(简化代码)等。
|
||||
|
||||
|
||||
#### 前端
|
||||
|
||||
- 技术栈:`Vue3.0 + TypeScript + Vite5 + ant-design-vue4 + pinia + echarts + unocss + vxe-table + qiankun + es6` 等最新技术栈
|
||||
|
||||
#### 支持库
|
||||
|
||||
| 数据库 | 支持 |
|
||||
| --- | --- |
|
||||
| MySQL | √ |
|
||||
| Oracle11g | √ |
|
||||
| Sqlserver2017 | √ |
|
||||
| PostgreSQL | √ |
|
||||
| MariaDB | √ |
|
||||
| 达梦、人大金仓 | √ |
|
||||
|
||||
|
||||
|
||||
## 微服务解决方案
|
||||
|
||||
|
||||
1、服务注册和发现 Nacos √
|
||||
|
||||
2、统一配置中心 Nacos √
|
||||
|
||||
3、路由网关 gateway(三种加载方式) √
|
||||
|
||||
4、分布式 http feign √
|
||||
|
||||
5、熔断降级限流 Sentinel √
|
||||
|
||||
6、分布式文件 Minio、阿里OSS √
|
||||
|
||||
7、统一权限控制 JWT + Shiro √
|
||||
|
||||
8、服务监控 SpringBootAdmin√
|
||||
|
||||
9、链路跟踪 Skywalking [参考文档](https://help.jeecg.com/java/springcloud/super/skywarking.html)
|
||||
|
||||
10、消息中间件 RabbitMQ √
|
||||
|
||||
11、分布式任务 xxl-job √
|
||||
|
||||
12、分布式事务 Seata
|
||||
|
||||
13、分布式日志 elk + kafka
|
||||
|
||||
14、支持 docker-compose、k8s、jenkins
|
||||
|
||||
15、CAS 单点登录 √
|
||||
|
||||
16、路由限流 √
|
||||
|
||||
|
||||
#### 微服务架构图
|
||||

|
||||
|
||||
### Jeecg Boot 产品功能蓝图
|
||||

|
||||
@ -239,36 +238,14 @@ AI集成文档: https://help.jeecg.com/java/ai/aichat.html
|
||||
|
||||
|
||||
|
||||
### 分支说明
|
||||
|
||||
> 主干master更稳定,如果你对最新技术栈无要求,建议采用主干
|
||||
|
||||
#### springboot3分支
|
||||
- 源码地址:https://github.com/jeecgboot/JeecgBoot/tree/springboot3
|
||||
- 架构说明:升级Spring Boot3 & JDK 17 + Undertow + springdoc + fastjson2
|
||||
|
||||
#### springboot3_sas分支
|
||||
- 源码地址:https://github.com/jeecgboot/JeecgBoot/tree/springboot3_sas
|
||||
- 架构说明:在springboot3分支基础上,采用SpringAuthorizationServer替换Shiro
|
||||
|
||||
|
||||
|
||||
### 功能模块
|
||||
```
|
||||
├─AI开发
|
||||
│ ├─支持AI大模型ChatGPT和DeepSeek
|
||||
│ ├─AI对话助手
|
||||
│ ├─AI建表
|
||||
│ ├─AI写文章
|
||||
│ ├─AI流程编排(研发中)
|
||||
│ ├─AI知识库问答系统(研发中)
|
||||
│ ├─AI应用开发平台(研发中)
|
||||
│ ├─AI聊天窗口支持嵌入第三方(研发中)
|
||||
├─Online在线开发(低代码)
|
||||
│ ├─Online在线表单
|
||||
│ ├─Online代码生成器
|
||||
│ ├─Online在线报表
|
||||
│ ├─仪表盘设计器
|
||||
│ ├─AI助手
|
||||
│ ├─系统编码规则
|
||||
│ ├─系统校验规则
|
||||
├─积木报表设计器
|
||||
@ -401,26 +378,40 @@ AI集成文档: https://help.jeecg.com/java/ai/aichat.html
|
||||
|
||||
|
||||
|
||||
后台目录结构
|
||||
-----------------------------------
|
||||
```
|
||||
项目结构
|
||||
├─jeecg-boot-parent(父POM: 项目依赖、modules组织)
|
||||
│ ├─jeecg-boot-base-core(共通模块: 工具类、config、权限、查询过滤器、注解等)
|
||||
│ ├─jeecg-module-demo 示例代码
|
||||
│ ├─jeecg-module-system System系统管理目录
|
||||
│ │ ├─jeecg-system-biz System系统管理权限等功能
|
||||
│ │ ├─jeecg-system-start System单体启动项目(8080)
|
||||
│ │ ├─jeecg-system-api System系统管理模块对外api
|
||||
│ │ │ ├─jeecg-system-cloud-api System模块对外提供的微服务接口
|
||||
│ │ │ ├─jeecg-system-local-api System模块对外提供的单体接口
|
||||
│ ├─jeecg-server-cloud --微服务模块
|
||||
├─jeecg-cloud-gateway --微服务网关模块(9999)
|
||||
├─jeecg-cloud-nacos --Nacos服务模块(8848)
|
||||
├─jeecg-system-cloud-start --System微服务启动项目(7001)
|
||||
├─jeecg-demo-cloud-start --Demo微服务启动项目(7002)
|
||||
├─jeecg-visual
|
||||
├─jeecg-cloud-monitor --微服务监控模块 (9111)
|
||||
├─jeecg-cloud-xxljob --微服务xxljob定时任务服务端 (9080)
|
||||
├─jeecg-cloud-sentinel --sentinel服务端 (9000)
|
||||
├─jeecg-cloud-test -- 微服务测试示例(各种例子)
|
||||
├─jeecg-cloud-test-more -- 微服务测试示例(feign、熔断降级、xxljob、分布式锁)
|
||||
├─jeecg-cloud-test-rabbitmq -- 微服务测试示例(rabbitmq)
|
||||
├─jeecg-cloud-test-seata -- 微服务测试示例(seata分布式事务)
|
||||
├─jeecg-cloud-test-shardingsphere -- 微服务测试示例(分库分表)
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### 系统效果
|
||||
|
||||
|
||||
##### AI功能
|
||||
|
||||
AI聊天助手
|
||||
|
||||

|
||||
|
||||
AI建表
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
AI写文章
|
||||
|
||||

|
||||
|
||||
|
||||
##### PC端
|
||||

|
||||
|
||||
@ -439,23 +430,15 @@ AI写文章
|
||||
|
||||

|
||||
|
||||
##### AI助手
|
||||

|
||||
|
||||
|
||||
##### 仪表盘设计器
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
##### 报表设计器
|
||||

|
||||
|
||||
@ -538,4 +521,11 @@ AI写文章
|
||||
|
||||
如果觉得还不错,请作者喝杯咖啡吧 ☺
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### 流程引擎推荐
|
||||
|
||||
大家在使用本开源项目时,如果想进一步集成流程引擎,推荐结合贺波老师的书 [《深入Activiti流程引擎:核心原理与高阶实战》](https://item.m.jd.com/product/13928958.html?gx=RnAomTM2bmCImZxDqYAkVCoIHuIYVqc)
|
||||
|
||||
<img src="https://jeecgos.oss-cn-beijing.aliyuncs.com/files/tuijian20231220161656.png" width="25%" height="auto">
|
||||
@ -1,135 +0,0 @@
|
||||
version: '2'
|
||||
services:
|
||||
jeecg-boot-mysql:
|
||||
build:
|
||||
context: ./jeecg-boot/db
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_ROOT_HOST: '%'
|
||||
TZ: Asia/Shanghai
|
||||
restart: always
|
||||
container_name: jeecg-boot-mysql
|
||||
image: jeecg-boot-mysql
|
||||
command:
|
||||
--character-set-server=utf8mb4
|
||||
--collation-server=utf8mb4_general_ci
|
||||
--explicit_defaults_for_timestamp=true
|
||||
--lower_case_table_names=1
|
||||
--max_allowed_packet=128M
|
||||
--default-authentication-plugin=caching_sha2_password
|
||||
ports:
|
||||
- 3306:3306
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-redis:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/jeecgdocker/redis:5.0
|
||||
ports:
|
||||
- 6379:6379
|
||||
restart: always
|
||||
hostname: jeecg-boot-redis
|
||||
container_name: jeecg-boot-redis
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-nacos:
|
||||
restart: always
|
||||
build:
|
||||
context: ./jeecg-boot/jeecg-server-cloud/jeecg-cloud-nacos
|
||||
ports:
|
||||
- 8848:8848
|
||||
container_name: jeecg-boot-nacos
|
||||
depends_on:
|
||||
- jeecg-boot-mysql
|
||||
hostname: jeecg-boot-nacos
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-system:
|
||||
depends_on:
|
||||
- jeecg-boot-nacos
|
||||
build:
|
||||
context: ./jeecg-boot/jeecg-server-cloud/jeecg-system-cloud-start
|
||||
container_name: jeecg-system-start
|
||||
hostname: jeecg-boot-system
|
||||
restart: on-failure
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-demo:
|
||||
depends_on:
|
||||
- jeecg-boot-nacos
|
||||
build:
|
||||
context: ./jeecg-boot/jeecg-server-cloud/jeecg-demo-cloud-start
|
||||
container_name: jeecg-demo-start
|
||||
hostname: jeecg-boot-demo
|
||||
restart: on-failure
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-gateway:
|
||||
restart: on-failure
|
||||
build:
|
||||
context: ./jeecg-boot/jeecg-server-cloud/jeecg-cloud-gateway
|
||||
ports:
|
||||
- 9999:9999
|
||||
depends_on:
|
||||
- jeecg-boot-nacos
|
||||
- jeecg-boot-system
|
||||
container_name: jeecg-boot-gateway
|
||||
hostname: jeecg-boot-gateway
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
# jeecg-boot-rabbitmq:
|
||||
# image: rabbitmq:3.7.7-management
|
||||
# ports:
|
||||
# - 5672:5672
|
||||
# - 15672:15672
|
||||
# restart: always
|
||||
# container_name: jeecg-boot-rabbitmq
|
||||
# hostname: jeecg-boot-rabbitmq
|
||||
# environment:
|
||||
# RABBITMQ_DEFAULT_USER: guest
|
||||
# RABBITMQ_DEFAULT_PASS: guest
|
||||
# jeecg-boot-sentinel:
|
||||
# restart: on-failure
|
||||
# build:
|
||||
# context: ./jeecg-visual/jeecg-cloud-sentinel
|
||||
# ports:
|
||||
# - 9000:9000
|
||||
# depends_on:
|
||||
# - jeecg-boot-nacos
|
||||
# - jeecg-boot-demo
|
||||
# - jeecg-boot-system
|
||||
# - jeecg-boot-gateway
|
||||
# container_name: jeecg-boot-sentinel
|
||||
# hostname: jeecg-boot-sentinel
|
||||
#
|
||||
# jeecg-boot-xxljob:
|
||||
# build:
|
||||
# context: ./jeecg-visual/jeecg-cloud-xxljob
|
||||
# ports:
|
||||
# - 9080:9080
|
||||
# container_name: jeecg-boot-xxljob
|
||||
# hostname: jeecg-boot-xxljob
|
||||
|
||||
jeecg-vue:
|
||||
build:
|
||||
context: ./jeecgboot-vue3
|
||||
container_name: jeecgboot-vue3-nginx
|
||||
image: jeecgboot-vue3
|
||||
depends_on:
|
||||
- jeecg-boot-system
|
||||
networks:
|
||||
- jeecg-boot
|
||||
ports:
|
||||
- 80:80
|
||||
|
||||
networks:
|
||||
jeecg-boot:
|
||||
name: jeecg_boot
|
||||
1
jeecg-boot/.gitignore
vendored
1
jeecg-boot/.gitignore
vendored
@ -13,3 +13,4 @@ os_del.cmd
|
||||
os_del_doc.cmd
|
||||
.svn
|
||||
derby.log
|
||||
*.log
|
||||
@ -201,13 +201,16 @@
|
||||
limitations under the License.
|
||||
|
||||
In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software.
|
||||
|
||||
JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京,地址:中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱:jeecgos@163.com
|
||||
本软件受适用的国家软件著作权法(包括国际条约)和开源协议 双重保护许可。
|
||||
|
||||
开源协议中文释意如下:
|
||||
1.JeecgBoot开源版本无任何限制,在遵循本开源协议条款下,允许商用使用,不会造成侵权行为。
|
||||
2.允许基于本平台软件开展业务系统开发。
|
||||
3.在任何情况下,您不得使用本软件开发可能被认为与本软件竞争的软件。
|
||||
|
||||
最终解释权归:http://www.jeecg.com
|
||||
|
||||
开源协议补充
|
||||
JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京,地址:中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱:jeecgos@163.com
|
||||
本软件受适用的国家软件著作权法(包括国际条约)和双重保护许可。
|
||||
|
||||
1.允许基于本平台软件开展业务系统开发。
|
||||
2.JeecgBoot底层依赖的非开源功能:online lib依赖、仪表盘lib依赖等,统一采用LGPL开源协议(不二次改造、不拆分出jeecgboot之外使用,就不产生侵权)
|
||||
3.不得基于该平台软件的基础,修改包装成一个与JeecgBoot平台软件功能类似的产品进行发布、销售,或与JeecgBoot参与同类软件产品市场的竞争。
|
||||
违反此条款属于侵权行为,须赔偿侵权经济损失,同时立即停止著作权侵权行为。
|
||||
|
||||
总结:在遵循Apache开源协议和开源协议补充条款下,允许商用使用,不会造成侵权行为!
|
||||
解释权归:http://www.jeecg.com
|
||||
|
||||
@ -1,165 +0,0 @@
|
||||
|
||||
JeecgBoot 低代码开发平台
|
||||
===============
|
||||
|
||||
当前最新版本: 3.7.3(发布日期:2025-02-10)
|
||||
|
||||
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||
[](http://jeecg.com/aboutusIndex)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
|
||||
|
||||
|
||||
项目介绍
|
||||
-----------------------------------
|
||||
|
||||
<h3 align="center">Java Low Code Platform for Enterprise web applications</h3>
|
||||
|
||||
JeecgBoot 是一款基于代码生成器的`低代码开发平台`!前后端分离架构 SpringBoot2.x和3.x,SpringCloud,Ant Design Vue3,Mybatis-plus,Shiro,JWT,支持微服务。强大的代码生成器让前后端代码一键生成,实现低代码开发! JeecgBoot 引领新的低代码开发模式(OnlineCoding-> 代码生成器-> 手工MERGE), 帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高效率,节省研发成本,同时又不失灵活性!
|
||||
|
||||
|
||||
#### 项目说明
|
||||
|
||||
| 项目名 | 说明 |
|
||||
|--------------------|------------------------|
|
||||
| `jeecg-boot` | 后端源码JAVA(SpringBoot微服务架构) |
|
||||
| `jeecgboot-vue3` | 前端源码VUE3(vue3+vite5+ts最新技术栈) |
|
||||
|
||||
|
||||
|
||||
技术文档
|
||||
-----------------------------------
|
||||
|
||||
- 官方网站: [http://www.jeecg.com](http://www.jeecg.com)
|
||||
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart)
|
||||
- QQ交流群 : ⑩716488839、⑨808791225、其他(满)
|
||||
- 在线演示 : [在线演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex)
|
||||
> 演示系统的登录账号密码,请点击 [获取账号密码](http://jeecg.com/doc/demo) 获取
|
||||
|
||||
|
||||
|
||||
启动项目
|
||||
-----------------------------------
|
||||
|
||||
- [IDEA启动前后端项目](https://help.jeecg.com/java/setup/idea/startup.html)
|
||||
- [Docker一键启动前后端](https://help.jeecg.com/java/docker/quick.html)
|
||||
|
||||
|
||||
微服务启动
|
||||
-----------------------------------
|
||||
- [单体快速切换微服务](https://help.jeecg.com/java/springcloud/switchcloud/monomer.html)
|
||||
- [Docker启动微服务后台](https://help.jeecg.com/java/docker/springcloud.html)
|
||||
|
||||
|
||||
|
||||
技术架构:
|
||||
-----------------------------------
|
||||
|
||||
#### 后端
|
||||
|
||||
- IDE建议: IDEA (必须安装lombok插件 )
|
||||
- 语言:Java 8+ (支持17)
|
||||
- 依赖管理:Maven
|
||||
- 基础框架:Spring Boot 2.7.18
|
||||
- 微服务框架: Spring Cloud Alibaba 2021.0.1.0
|
||||
- 持久层框架:MybatisPlus 3.5.3.2
|
||||
- 报表工具: JimuReport 1.8.1
|
||||
- 安全框架:Apache Shiro 1.12.0,Jwt 3.11.0
|
||||
- 微服务技术栈:Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywalking
|
||||
- 数据库连接池:阿里巴巴Druid 1.1.22
|
||||
- 日志打印:logback
|
||||
- 缓存:Redis
|
||||
- 其他:autopoi, fastjson,poi,Swagger-ui,quartz, lombok(简化代码)等。
|
||||
- 默认数据库脚本:MySQL5.7+
|
||||
- [其他数据库,需要自己转](https://my.oschina.net/jeecg/blog/4905722)
|
||||
|
||||
|
||||
#### 前端
|
||||
|
||||
- 前端IDE建议:WebStorm、Vscode
|
||||
- 采用 Vue3.0+TypeScript+Vite+Ant-Design-Vue等新技术方案,包括二次封装组件、utils、hooks、动态菜单、权限校验、按钮级别权限控制等功能
|
||||
- 最新技术栈:Vue3.0 + TypeScript + Vite5 + ant-design-vue4 + pinia + echarts + unocss + vxe-table + qiankun + es6
|
||||
- 依赖管理:node、npm、pnpm
|
||||
|
||||
|
||||
|
||||
#### 支持库
|
||||
|
||||
| 数据库 | 支持 |
|
||||
| --- | --- |
|
||||
| MySQL | √ |
|
||||
| Oracle11g | √ |
|
||||
| Sqlserver2017 | √ |
|
||||
| PostgreSQL | √ |
|
||||
| MariaDB | √ |
|
||||
| 达梦 | √ |
|
||||
| 人大金仓 | √ |
|
||||
| TiDB | √ |
|
||||
|
||||
|
||||
|
||||
|
||||
## 微服务解决方案
|
||||
|
||||
|
||||
- 1、服务注册和发现 Nacos √
|
||||
- 2、统一配置中心 Nacos √
|
||||
- 3、路由网关 gateway(三种加载方式) √
|
||||
- 4、分布式 http feign √
|
||||
- 5、熔断降级限流 Sentinel √
|
||||
- 6、分布式文件 Minio、阿里OSS √
|
||||
- 7、统一权限控制 JWT + Shiro √
|
||||
- 8、服务监控 SpringBootAdmin√
|
||||
- 9、链路跟踪 Skywalking [参考文档](https://help.jeecg.com/java/springcloud/super/skywarking.html)
|
||||
- 10、消息中间件 RabbitMQ √
|
||||
- 11、分布式任务 xxl-job √
|
||||
- 12、分布式事务 Seata
|
||||
- 13、轻量分布式日志 Loki+grafana套件
|
||||
- 14、支持 docker-compose、k8s、jenkins
|
||||
- 15、CAS 单点登录 √
|
||||
- 16、路由限流 √
|
||||
|
||||
|
||||
|
||||
后台目录结构
|
||||
-----------------------------------
|
||||
```
|
||||
项目结构
|
||||
├─jeecg-boot-parent(父POM: 项目依赖、modules组织)
|
||||
│ ├─jeecg-boot-base-core(共通模块: 工具类、config、权限、查询过滤器、注解等)
|
||||
│ ├─jeecg-module-demo 示例代码
|
||||
│ ├─jeecg-module-system System系统管理目录
|
||||
│ │ ├─jeecg-system-biz System系统管理权限等功能
|
||||
│ │ ├─jeecg-system-start System单体启动项目(8080)
|
||||
│ │ ├─jeecg-system-api System系统管理模块对外api
|
||||
│ │ │ ├─jeecg-system-cloud-api System模块对外提供的微服务接口
|
||||
│ │ │ ├─jeecg-system-local-api System模块对外提供的单体接口
|
||||
│ ├─jeecg-server-cloud --微服务模块
|
||||
├─jeecg-cloud-gateway --微服务网关模块(9999)
|
||||
├─jeecg-cloud-nacos --Nacos服务模块(8848)
|
||||
├─jeecg-system-cloud-start --System微服务启动项目(7001)
|
||||
├─jeecg-demo-cloud-start --Demo微服务启动项目(7002)
|
||||
├─jeecg-visual
|
||||
├─jeecg-cloud-monitor --微服务监控模块 (9111)
|
||||
├─jeecg-cloud-xxljob --微服务xxljob定时任务服务端 (9080)
|
||||
├─jeecg-cloud-sentinel --sentinel服务端 (9000)
|
||||
├─jeecg-cloud-test -- 微服务测试示例(各种例子)
|
||||
├─jeecg-cloud-test-more -- 微服务测试示例(feign、熔断降级、xxljob、分布式锁)
|
||||
├─jeecg-cloud-test-rabbitmq -- 微服务测试示例(rabbitmq)
|
||||
├─jeecg-cloud-test-seata -- 微服务测试示例(seata分布式事务)
|
||||
├─jeecg-cloud-test-shardingsphere -- 微服务测试示例(分库分表)
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
#### 微服务架构图
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM registry.cn-hangzhou.aliyuncs.com/jeecgdocker/mysql:8.0.19
|
||||
FROM mysql:8.0.19
|
||||
|
||||
MAINTAINER jeecgos@163.com
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -5,356 +5,115 @@
|
||||
CREATE database if NOT EXISTS `xxl_job` default character set utf8mb4 collate utf8mb4_general_ci;
|
||||
use `xxl_job`;
|
||||
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : mysql5.7
|
||||
Source Server Type : MySQL
|
||||
Source Server Version : 50738 (5.7.38)
|
||||
Source Host : 127.0.0.1:3306
|
||||
Source Schema : xxl_job
|
||||
|
||||
Target Server Type : MySQL
|
||||
Target Server Version : 50738 (5.7.38)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 10/02/2025 13:49:31
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for xxl_job_group
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `xxl_job_group`;
|
||||
CREATE TABLE `xxl_job_group` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`app_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '执行器AppName',
|
||||
`title` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '执行器名称',
|
||||
`address_type` tinyint(4) NOT NULL DEFAULT 0 COMMENT '执行器地址类型:0=自动注册、1=手动录入',
|
||||
`address_list` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '执行器地址列表,多地址逗号分隔',
|
||||
`update_time` datetime NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of xxl_job_group
|
||||
-- ----------------------------
|
||||
INSERT INTO `xxl_job_group` VALUES (1, 'xxl-job-executor-sample', '示例执行器', 0, NULL, '2025-02-10 13:49:04');
|
||||
INSERT INTO `xxl_job_group` VALUES (2, 'jeecg-demo', '测试Demo模块', 0, NULL, '2025-02-10 13:49:04');
|
||||
INSERT INTO `xxl_job_group` VALUES (3, 'jeecg-system', '系统System模块', 0, NULL, '2025-02-10 13:49:04');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for xxl_job_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `xxl_job_info`;
|
||||
CREATE TABLE `xxl_job_info` (
|
||||
CREATE TABLE `xxl_job_info` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
|
||||
`job_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`add_time` datetime NULL DEFAULT NULL,
|
||||
`update_time` datetime NULL DEFAULT NULL,
|
||||
`author` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '作者',
|
||||
`alarm_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '报警邮件',
|
||||
`schedule_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NONE' COMMENT '调度类型',
|
||||
`schedule_conf` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '调度配置,值含义取决于调度类型',
|
||||
`misfire_strategy` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'DO_NOTHING' COMMENT '调度过期策略',
|
||||
`executor_route_strategy` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器路由策略',
|
||||
`executor_handler` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器任务handler',
|
||||
`executor_param` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器任务参数',
|
||||
`executor_block_strategy` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '阻塞处理策略',
|
||||
`executor_timeout` int(11) NOT NULL DEFAULT 0 COMMENT '任务执行超时时间,单位秒',
|
||||
`executor_fail_retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '失败重试次数',
|
||||
`glue_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'GLUE类型',
|
||||
`glue_source` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'GLUE源代码',
|
||||
`glue_remark` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'GLUE备注',
|
||||
`glue_updatetime` datetime NULL DEFAULT NULL COMMENT 'GLUE更新时间',
|
||||
`child_jobid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
|
||||
`trigger_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '调度状态:0-停止,1-运行',
|
||||
`trigger_last_time` bigint(13) NOT NULL DEFAULT 0 COMMENT '上次调度时间',
|
||||
`trigger_next_time` bigint(13) NOT NULL DEFAULT 0 COMMENT '下次调度时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
|
||||
`job_desc` varchar(255) NOT NULL,
|
||||
`add_time` datetime DEFAULT NULL,
|
||||
`update_time` datetime DEFAULT NULL,
|
||||
`author` varchar(64) DEFAULT NULL COMMENT '作者',
|
||||
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件',
|
||||
`executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略',
|
||||
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
|
||||
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
|
||||
`executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略',
|
||||
`executor_timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒',
|
||||
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
|
||||
`glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型',
|
||||
`glue_source` mediumtext COMMENT 'GLUE源代码',
|
||||
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
|
||||
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
|
||||
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
|
||||
`trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
|
||||
`trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
|
||||
`trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of xxl_job_info
|
||||
-- ----------------------------
|
||||
INSERT INTO `xxl_job_info` VALUES (1, 1, '测试任务1', '2018-11-03 22:21:31', '2024-08-21 22:30:30', 'XXL', '', 'CRON', '0 0 0 * * ? *', 'DO_NOTHING', 'FIRST', 'demoJob', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '', 1, 1729353600000, 1739203200000);
|
||||
INSERT INTO `xxl_job_info` VALUES (2, 3, '测试jeecg xxljob', '2024-08-21 22:41:10', '2024-08-21 22:41:30', 'JEECG', '', 'CRON', '* * * * * ?', 'DO_NOTHING', 'FIRST', 'demoJob', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2024-08-21 22:41:10', '', 1, 1739166572000, 1739166573000);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for xxl_job_lock
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `xxl_job_lock`;
|
||||
CREATE TABLE `xxl_job_lock` (
|
||||
`lock_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '锁名称',
|
||||
PRIMARY KEY (`lock_name`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of xxl_job_lock
|
||||
-- ----------------------------
|
||||
INSERT INTO `xxl_job_lock` VALUES ('schedule_lock');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for xxl_job_log
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `xxl_job_log`;
|
||||
CREATE TABLE `xxl_job_log` (
|
||||
CREATE TABLE `xxl_job_log` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
|
||||
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
|
||||
`executor_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
|
||||
`executor_handler` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器任务handler',
|
||||
`executor_param` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器任务参数',
|
||||
`executor_sharding_param` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器任务分片参数,格式如 1/2',
|
||||
`executor_fail_retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '失败重试次数',
|
||||
`trigger_time` datetime NULL DEFAULT NULL COMMENT '调度-时间',
|
||||
`executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
|
||||
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
|
||||
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
|
||||
`executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '执行器任务分片参数,格式如 1/2',
|
||||
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
|
||||
`trigger_time` datetime DEFAULT NULL COMMENT '调度-时间',
|
||||
`trigger_code` int(11) NOT NULL COMMENT '调度-结果',
|
||||
`trigger_msg` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '调度-日志',
|
||||
`handle_time` datetime NULL DEFAULT NULL COMMENT '执行-时间',
|
||||
`trigger_msg` text COMMENT '调度-日志',
|
||||
`handle_time` datetime DEFAULT NULL COMMENT '执行-时间',
|
||||
`handle_code` int(11) NOT NULL COMMENT '执行-状态',
|
||||
`handle_msg` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '执行-日志',
|
||||
`alarm_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '告警状态:0-默认、1-无需告警、2-告警成功、3-告警失败',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `I_trigger_time`(`trigger_time`) USING BTREE,
|
||||
INDEX `I_handle_code`(`handle_code`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 6761 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
`handle_msg` text COMMENT '执行-日志',
|
||||
`alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警状态:0-默认、1-无需告警、2-告警成功、3-告警失败',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_trigger_time` (`trigger_time`),
|
||||
KEY `I_handle_code` (`handle_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of xxl_job_log
|
||||
-- ----------------------------
|
||||
INSERT INTO `xxl_job_log` VALUES (6618, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:09', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6619, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:10', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6620, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:11', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6621, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:12', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6622, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:13', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6623, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:14', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6624, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:15', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6625, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:16', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6626, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:17', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6627, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:18', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6628, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:19', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6629, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:20', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6630, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:21', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6631, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:22', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6632, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:23', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6633, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:24', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6634, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:25', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6635, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:26', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6636, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:27', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6637, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:28', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6638, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:29', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6639, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:30', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6640, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:31', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6641, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:32', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6642, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:33', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6643, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:34', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6644, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:35', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6645, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:36', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6646, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:37', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6647, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:38', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6648, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:39', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6649, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:40', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6650, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:41', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6651, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:42', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6652, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:43', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6653, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:44', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6654, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:45', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6655, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:46', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6656, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:47', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6657, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:48', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6658, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:49', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6659, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:50', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6660, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:51', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6661, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:52', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6662, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:53', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6663, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:54', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6664, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:55', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6665, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:56', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6666, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:57', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6667, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:58', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6668, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:59', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6669, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:00', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6670, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:01', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6671, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:02', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6672, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:03', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6673, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:04', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6674, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:05', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6675, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:06', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6676, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:07', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6677, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:08', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6678, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:09', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6679, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:10', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6680, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:11', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6681, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:12', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6682, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:13', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6683, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:14', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6684, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:15', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6685, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:16', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6686, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:17', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6687, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:18', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6688, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:19', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6689, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:20', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6690, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:21', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6691, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:22', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6692, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:23', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6693, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:24', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6694, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:25', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6695, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:26', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6696, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:27', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6697, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:28', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6698, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:29', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6699, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:30', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6700, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:31', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6701, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:32', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6702, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:33', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6703, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:34', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6704, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:35', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6705, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:36', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6706, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:37', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6707, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:38', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6708, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:39', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6709, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:40', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6710, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:41', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6711, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:42', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6712, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:43', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6713, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:44', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6714, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:45', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6715, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:46', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6716, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:47', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6717, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:48', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6718, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:49', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6719, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:50', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6720, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:51', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6721, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:52', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6722, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:53', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6723, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:54', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6724, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:55', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6725, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:56', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6726, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:57', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6727, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:58', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6728, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:59', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6729, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:00', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6730, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:01', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6731, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:02', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6732, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:03', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6733, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:04', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6734, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:05', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6735, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:06', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6736, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:07', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6737, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:08', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6738, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:09', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6739, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:10', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6740, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:11', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6741, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:12', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6742, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:13', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6743, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:14', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6744, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:15', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6745, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:16', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6746, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:17', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6747, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:18', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6748, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:19', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6749, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:20', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6750, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:21', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6751, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:22', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6752, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:23', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6753, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:24', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
|
||||
INSERT INTO `xxl_job_log` VALUES (6754, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:25', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
|
||||
INSERT INTO `xxl_job_log` VALUES (6755, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:26', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
|
||||
INSERT INTO `xxl_job_log` VALUES (6756, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:27', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
|
||||
INSERT INTO `xxl_job_log` VALUES (6757, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:28', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
|
||||
INSERT INTO `xxl_job_log` VALUES (6758, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:29', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
|
||||
INSERT INTO `xxl_job_log` VALUES (6759, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:30', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
|
||||
INSERT INTO `xxl_job_log` VALUES (6760, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:31', 500, '任务触发类型:Cron触发<br>调度机器:192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表:null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间:0<br>失败重试次数:0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for xxl_job_log_report
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `xxl_job_log_report`;
|
||||
CREATE TABLE `xxl_job_log_report` (
|
||||
CREATE TABLE `xxl_job_log_report` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`trigger_day` datetime NULL DEFAULT NULL COMMENT '调度-时间',
|
||||
`running_count` int(11) NOT NULL DEFAULT 0 COMMENT '运行中-日志数量',
|
||||
`suc_count` int(11) NOT NULL DEFAULT 0 COMMENT '执行成功-日志数量',
|
||||
`fail_count` int(11) NOT NULL DEFAULT 0 COMMENT '执行失败-日志数量',
|
||||
`update_time` datetime NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `i_trigger_day`(`trigger_day`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
`trigger_day` datetime DEFAULT NULL COMMENT '调度-时间',
|
||||
`running_count` int(11) NOT NULL DEFAULT '0' COMMENT '运行中-日志数量',
|
||||
`suc_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行成功-日志数量',
|
||||
`fail_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行失败-日志数量',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `i_trigger_day` (`trigger_day`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of xxl_job_log_report
|
||||
-- ----------------------------
|
||||
INSERT INTO `xxl_job_log_report` VALUES (1, '2024-08-21 00:00:00', 70, 0, 5, NULL);
|
||||
INSERT INTO `xxl_job_log_report` VALUES (2, '2024-08-20 00:00:00', 0, 0, 0, NULL);
|
||||
INSERT INTO `xxl_job_log_report` VALUES (3, '2024-08-19 00:00:00', 0, 0, 0, NULL);
|
||||
INSERT INTO `xxl_job_log_report` VALUES (4, '2024-09-10 00:00:00', 0, 0, 56, NULL);
|
||||
INSERT INTO `xxl_job_log_report` VALUES (5, '2024-09-09 00:00:00', 0, 0, 0, NULL);
|
||||
INSERT INTO `xxl_job_log_report` VALUES (6, '2024-09-08 00:00:00', 0, 0, 0, NULL);
|
||||
INSERT INTO `xxl_job_log_report` VALUES (7, '2024-10-19 00:00:00', 0, 0, 6391, NULL);
|
||||
INSERT INTO `xxl_job_log_report` VALUES (8, '2024-10-18 00:00:00', 0, 0, 0, NULL);
|
||||
INSERT INTO `xxl_job_log_report` VALUES (9, '2024-10-17 00:00:00', 0, 0, 0, NULL);
|
||||
INSERT INTO `xxl_job_log_report` VALUES (10, '2025-02-10 00:00:00', 0, 0, 116, NULL);
|
||||
INSERT INTO `xxl_job_log_report` VALUES (11, '2025-02-09 00:00:00', 0, 0, 0, NULL);
|
||||
INSERT INTO `xxl_job_log_report` VALUES (12, '2025-02-08 00:00:00', 0, 0, 0, NULL);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for xxl_job_logglue
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `xxl_job_logglue`;
|
||||
CREATE TABLE `xxl_job_logglue` (
|
||||
CREATE TABLE `xxl_job_logglue` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
|
||||
`glue_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'GLUE类型',
|
||||
`glue_source` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'GLUE源代码',
|
||||
`glue_remark` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'GLUE备注',
|
||||
`add_time` datetime NULL DEFAULT NULL,
|
||||
`update_time` datetime NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
|
||||
`glue_source` mediumtext COMMENT 'GLUE源代码',
|
||||
`glue_remark` varchar(128) NOT NULL COMMENT 'GLUE备注',
|
||||
`add_time` datetime DEFAULT NULL,
|
||||
`update_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of xxl_job_logglue
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for xxl_job_registry
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `xxl_job_registry`;
|
||||
CREATE TABLE `xxl_job_registry` (
|
||||
CREATE TABLE `xxl_job_registry` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`registry_group` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`registry_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`registry_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`update_time` datetime NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `i_g_k_v`(`registry_group`, `registry_key`, `registry_value`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
`registry_group` varchar(50) NOT NULL,
|
||||
`registry_key` varchar(255) NOT NULL,
|
||||
`registry_value` varchar(255) NOT NULL,
|
||||
`update_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of xxl_job_registry
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for xxl_job_user
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `xxl_job_user`;
|
||||
CREATE TABLE `xxl_job_user` (
|
||||
CREATE TABLE `xxl_job_group` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账号',
|
||||
`password` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
|
||||
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
|
||||
`title` varchar(12) NOT NULL COMMENT '执行器名称',
|
||||
`address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型:0=自动注册、1=手动录入',
|
||||
`address_list` varchar(512) DEFAULT NULL COMMENT '执行器地址列表,多地址逗号分隔',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `xxl_job_user` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(50) NOT NULL COMMENT '账号',
|
||||
`password` varchar(50) NOT NULL COMMENT '密码',
|
||||
`role` tinyint(4) NOT NULL COMMENT '角色:0-普通用户、1-管理员',
|
||||
`permission` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限:执行器ID列表,多个逗号分割',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `i_username`(`username`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
`permission` varchar(255) DEFAULT NULL COMMENT '权限:执行器ID列表,多个逗号分割',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `i_username` (`username`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of xxl_job_user
|
||||
-- ----------------------------
|
||||
INSERT INTO `xxl_job_user` VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
|
||||
CREATE TABLE `xxl_job_lock` (
|
||||
`lock_name` varchar(50) NOT NULL COMMENT '锁名称',
|
||||
PRIMARY KEY (`lock_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
INSERT INTO `xxl_job_group`(`id`, `app_name`, `title`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 0, NULL);
|
||||
INSERT INTO `xxl_job_info`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
|
||||
INSERT INTO `xxl_job_user`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
|
||||
INSERT INTO `xxl_job_lock` ( `lock_name`) VALUES ( 'schedule_lock');
|
||||
|
||||
commit;
|
||||
|
||||
|
||||
45
jeecg-boot/db/增量SQL/sas升级脚本.sql
Normal file
45
jeecg-boot/db/增量SQL/sas升级脚本.sql
Normal file
@ -0,0 +1,45 @@
|
||||
CREATE TABLE `oauth2_registered_client` (
|
||||
`id` varchar(100) NOT NULL,
|
||||
`client_id` varchar(100) NOT NULL,
|
||||
`client_id_issued_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`client_secret` varchar(200) DEFAULT NULL,
|
||||
`client_secret_expires_at` timestamp NULL DEFAULT NULL,
|
||||
`client_name` varchar(200) NOT NULL,
|
||||
`client_authentication_methods` varchar(1000) NOT NULL,
|
||||
`authorization_grant_types` varchar(1000) NOT NULL,
|
||||
`redirect_uris` varchar(1000) DEFAULT NULL,
|
||||
`post_logout_redirect_uris` varchar(1000) DEFAULT NULL,
|
||||
`scopes` varchar(1000) NOT NULL,
|
||||
`client_settings` varchar(2000) NOT NULL,
|
||||
`token_settings` varchar(2000) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
INSERT INTO `oauth2_registered_client`
|
||||
(`id`,
|
||||
`client_id`,
|
||||
`client_id_issued_at`,
|
||||
`client_secret`,
|
||||
`client_secret_expires_at`,
|
||||
`client_name`,
|
||||
`client_authentication_methods`,
|
||||
`authorization_grant_types`,
|
||||
`redirect_uris`,
|
||||
`post_logout_redirect_uris`,
|
||||
`scopes`,
|
||||
`client_settings`,
|
||||
`token_settings`)
|
||||
VALUES
|
||||
('3eacac0e-0de9-4727-9a64-6bdd4be2ee1f',
|
||||
'jeecg-client',
|
||||
now(),
|
||||
'secret',
|
||||
null,
|
||||
'3eacac0e-0de9-4727-9a64-6bdd4be2ee1f',
|
||||
'client_secret_basic',
|
||||
'refresh_token,authorization_code,password,app,phone,social',
|
||||
'http://127.0.0.1:8080/jeecg-',
|
||||
'http://127.0.0.1:8080/',
|
||||
'*',
|
||||
'{"@class":"java.util.Collections$UnmodifiableMap","settings.client.require-proof-key":false,"settings.client.require-authorization-consent":true}',
|
||||
'{"@class":"java.util.Collections$UnmodifiableMap","settings.token.reuse-refresh-tokens":true,"settings.token.id-token-signature-algorithm":["org.springframework.security.oauth2.jose.jws.SignatureAlgorithm","RS256"],"settings.token.access-token-time-to-live":["java.time.Duration",300000.000000000],"settings.token.access-token-format":{"@class":"org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat","value":"self-contained"},"settings.token.refresh-token-time-to-live":["java.time.Duration",3600.000000000],"settings.token.authorization-code-time-to-live":["java.time.Duration",300000.000000000],"settings.token.device-code-time-to-live":["java.time.Duration",300000.000000000]}');
|
||||
@ -3,7 +3,6 @@
|
||||
> JeecgBoot属于平台级产品,每次升级改动较大,目前做不到平滑升级。
|
||||
|
||||
### 增量升级方案
|
||||
|
||||
#### 1.代码合并
|
||||
本地通过svn或git做好主干,在分支上做业务开发,jeecg每次版本发布,可以手工覆盖主干的代码,对比合并代码;
|
||||
|
||||
@ -12,12 +11,5 @@
|
||||
- 其他库请手工执行SQL, 目录: `jeecg-module-system\jeecg-system-start\src\main\resources\flyway\sql\mysql`
|
||||
> 注意: 升级sql只提供mysql版本;如果有权限升级, 还需要手工角色授权,退出重新登录才好使。
|
||||
|
||||
#### 3.其他数据库脚本说明
|
||||
原先官方默认提供oracle和SqlServer的脚本,但是维护成本太高,未提供脚本的数据库,可以参考下面的文档自己转
|
||||
https://my.oschina.net/jeecg/blog/4905722
|
||||
(注意:定时任务的表qrtz_*,需要删掉用原始的脚本重新执行一下)
|
||||
quartz-2.2.3-distribution.tar.gz放到百度网盘中,大家自己下载,执行所需数据库脚本
|
||||
https://pan.baidu.com/s/1WrmZdUuAPg3iBwJ-LoHWyg?pwd=8mdz
|
||||
|
||||
#### 4.兼容问题
|
||||
#### 3.兼容问题
|
||||
每次发版,会针对不兼容地方重点说明。
|
||||
@ -23,7 +23,7 @@ services:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-redis:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/jeecgdocker/redis:5.0
|
||||
image: redis:5.0
|
||||
ports:
|
||||
- 6379:6379
|
||||
restart: always
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-parent</artifactId>
|
||||
<version>3.7.3</version>
|
||||
<version>3.7.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jeecg-boot-base-core</artifactId>
|
||||
@ -163,7 +163,7 @@
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework</groupId>
|
||||
<artifactId>kingbase8</artifactId>
|
||||
<version>9.0.0</version>
|
||||
<version>${kingbase8.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<!--达梦数据库驱动 版本号1-3-26-2023.07.26-197096-20046-ENT -->
|
||||
@ -192,82 +192,19 @@
|
||||
<version>${java-jwt.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--shiro-->
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-spring-boot-starter</artifactId>
|
||||
<classifier>jakarta</classifier>
|
||||
<version>${shiro.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-spring</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- shiro-redis -->
|
||||
<dependency>
|
||||
<groupId>org.crazycake</groupId>
|
||||
<artifactId>shiro-redis</artifactId>
|
||||
<version>${shiro-redis.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
</exclusion>
|
||||
<!-- TODO shiro 无法使用 spring boot 3.X 自带的jedis,降版本处理 -->
|
||||
<exclusion>
|
||||
<artifactId>jedis</artifactId>
|
||||
<groupId>redis.clients</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- TODO shiro 无法使用 spring boot 3.X 自带的jedis,降版本处理 -->
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>${jedis.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-spring</artifactId>
|
||||
<classifier>jakarta</classifier>
|
||||
<version>${shiro.version}</version>
|
||||
<!-- 排除仍使用了javax.servlet的依赖 -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-web</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- 引入适配jakarta的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-core</artifactId>
|
||||
<classifier>jakarta</classifier>
|
||||
<version>${shiro.version}</version>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-web</artifactId>
|
||||
<classifier>jakarta</classifier>
|
||||
<version>${shiro.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
|
||||
</dependency>
|
||||
<!-- 添加spring security cas支持 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-cas</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- knife4j -->
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
package org.apache.shiro;
|
||||
|
||||
import org.apache.shiro.subject.Subject;
|
||||
|
||||
/**
|
||||
* 兼容处理Online功能使用处理,请勿修改
|
||||
* @author eightmonth@qq.com
|
||||
* @date 2024/4/29 14:05
|
||||
*/
|
||||
public class SecurityUtils {
|
||||
|
||||
|
||||
public static Subject getSubject() {
|
||||
return new Subject() {
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return Subject.super.getPrincipal();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package org.apache.shiro.subject;
|
||||
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
|
||||
/**
|
||||
* 兼容处理Online功能使用处理,请勿修改
|
||||
* @author eightmonth@qq.com
|
||||
* @date 2024/4/29 14:18
|
||||
*/
|
||||
public interface Subject {
|
||||
default Object getPrincipal() {
|
||||
return SecureUtil.currentUser();
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package org.jeecg.common.api;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.jeecg.common.system.vo.*;
|
||||
|
||||
import java.util.List;
|
||||
@ -64,6 +65,13 @@ public interface CommonAPI {
|
||||
*/
|
||||
public String getUserIdByName(String username);
|
||||
|
||||
/**
|
||||
* 5根据用户手机号查询用户信息
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
public LoginUser getUserByPhone(String phone);
|
||||
|
||||
|
||||
/**
|
||||
* 6字典表的 翻译
|
||||
@ -144,4 +152,31 @@ public interface CommonAPI {
|
||||
List<DictModel> translateDictFromTableByKeys(String table, String text, String code, String keys, String dataSource);
|
||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
|
||||
/**
|
||||
* 登录加载系统字典
|
||||
* @return
|
||||
*/
|
||||
Map<String,List<DictModel>> queryAllDictItems();
|
||||
|
||||
/**
|
||||
* 查询SysDepart集合
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
List<SysDepartModel> queryUserDeparts(String userId);
|
||||
|
||||
/**
|
||||
* 根据用户名设置部门ID
|
||||
* @param username
|
||||
* @param orgCode
|
||||
*/
|
||||
void updateUserDepart(String username,String orgCode,Integer loginTenantId);
|
||||
|
||||
/**
|
||||
* 设置登录租户
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
JSONObject setLoginTenant(String username);
|
||||
|
||||
}
|
||||
|
||||
@ -84,12 +84,6 @@ public class MessageDTO implements Serializable {
|
||||
* 邮件抄送地址
|
||||
*/
|
||||
protected Set<String> ccEmailList;
|
||||
|
||||
/**
|
||||
* 是否为定时任务推送email
|
||||
*/
|
||||
private Boolean isTimeJob = false;
|
||||
|
||||
//---【邮件相关参数】-------------------------------------------------------------
|
||||
|
||||
public MessageDTO(){
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.jeecg.common.aspect;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.serializer.PropertyFilter;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
@ -15,13 +16,14 @@ import org.jeecg.common.aspect.annotation.AutoLog;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.enums.ModuleType;
|
||||
import org.jeecg.common.constant.enums.OperateTypeEnum;
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.IpUtils;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@ -101,7 +103,7 @@ public class AutoLogAspect {
|
||||
//设置IP地址
|
||||
dto.setIp(IpUtils.getIpAddr(request));
|
||||
//获取登录用户信息
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
LoginUser sysUser = SecureUtil.currentUser();
|
||||
if(sysUser!=null){
|
||||
dto.setUserid(sysUser.getUsername());
|
||||
dto.setUsername(sysUser.getRealname());
|
||||
|
||||
@ -90,7 +90,7 @@ public interface CommonConstant {
|
||||
/** 登录用户Shiro权限缓存KEY前缀 */
|
||||
public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:";
|
||||
/** 登录用户Token令牌缓存KEY前缀 */
|
||||
String PREFIX_USER_TOKEN = "prefix_user_token:";
|
||||
String PREFIX_USER_TOKEN = "token::jeecg-client::";
|
||||
// /** Token缓存时间:3600秒即一小时 */
|
||||
// int TOKEN_EXPIRE_TIME = 3600;
|
||||
|
||||
@ -144,9 +144,7 @@ public interface CommonConstant {
|
||||
*/
|
||||
String STATUS_0 = "0";
|
||||
String STATUS_1 = "1";
|
||||
Integer STATUS_0_INT = 0;
|
||||
Integer STATUS_1_INT = 1;
|
||||
|
||||
|
||||
/**
|
||||
* 同步工作流引擎1同步0不同步
|
||||
*/
|
||||
@ -477,11 +475,6 @@ public interface CommonConstant {
|
||||
*/
|
||||
String FILE_EDITABLE = "editable";
|
||||
|
||||
/**
|
||||
* 文件 只读
|
||||
*/
|
||||
String FILE_READONLY = "readonly";
|
||||
|
||||
/**
|
||||
* 登录失败,用于记录失败次数的key
|
||||
*/
|
||||
@ -604,11 +597,6 @@ public interface CommonConstant {
|
||||
*/
|
||||
String CHANGE_PHONE_REDIS_KEY_PRE = "sys:cache:phone:change_phone_msg:";
|
||||
|
||||
/**
|
||||
* 手机号短信验证码redis-key的前缀
|
||||
*/
|
||||
String LOG_OFF_PHONE_REDIS_KEY_PRE = "sys:cache:phone:qqy_log_off_user_msg:";
|
||||
|
||||
/**
|
||||
* 缓存用户最后一次收到消息通知的时间 KEY
|
||||
*/
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package org.jeecg.common.constant;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.Resource;
|
||||
@ -23,30 +22,26 @@ public class ProvinceCityArea {
|
||||
List<Area> areaList;
|
||||
|
||||
public String getText(String code){
|
||||
if(StringUtils.isNotBlank(code)){
|
||||
this.initAreaList();
|
||||
if(this.areaList!=null || this.areaList.size()>0){
|
||||
List<String> ls = new ArrayList<String>();
|
||||
getAreaByCode(code,ls);
|
||||
return String.join("/",ls);
|
||||
}
|
||||
this.initAreaList();
|
||||
if(this.areaList!=null || this.areaList.size()>0){
|
||||
List<String> ls = new ArrayList<String>();
|
||||
getAreaByCode(code,ls);
|
||||
return String.join("/",ls);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getCode(String text){
|
||||
if(StringUtils.isNotBlank(text)){
|
||||
this.initAreaList();
|
||||
if(areaList!=null && areaList.size()>0){
|
||||
for(int i=areaList.size()-1;i>=0;i--){
|
||||
//update-begin-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
|
||||
String areaText = areaList.get(i).getText();
|
||||
String cityText = areaList.get(i).getAheadText();
|
||||
if(text.indexOf(areaText)>=0 && (cityText!=null && text.indexOf(cityText)>=0)){
|
||||
return areaList.get(i).getId();
|
||||
}
|
||||
//update-end-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
|
||||
this.initAreaList();
|
||||
if(areaList!=null && areaList.size()>0){
|
||||
for(int i=areaList.size()-1;i>=0;i--){
|
||||
//update-begin-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
|
||||
String areaText = areaList.get(i).getText();
|
||||
String cityText = areaList.get(i).getAheadText();
|
||||
if(text.indexOf(areaText)>=0 && (cityText!=null && text.indexOf(cityText)>=0)){
|
||||
return areaList.get(i).getId();
|
||||
}
|
||||
//update-end-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@ -15,7 +15,15 @@ public enum DySmsEnum {
|
||||
/**修改密码短信模板编码*/
|
||||
CHANGE_PASSWORD_TEMPLATE_CODE("SMS_465391221","敲敲云","code"),
|
||||
/**注册账号短信模板编码*/
|
||||
REGISTER_TEMPLATE_CODE("SMS_175430166","敲敲云","code");
|
||||
REGISTER_TEMPLATE_CODE("SMS_175430166","敲敲云","code"),
|
||||
/**会议通知*/
|
||||
MEET_NOTICE_TEMPLATE_CODE("SMS_201480469","JEECG","username,title,minute,time"),
|
||||
/**我的计划通知*/
|
||||
PLAN_NOTICE_TEMPLATE_CODE("SMS_201470515","JEECG","username,title,time"),
|
||||
/**支付成功短信通知*/
|
||||
PAY_SUCCESS_NOTICE_CODE("SMS_461735163","敲敲云","realname,money,endTime"),
|
||||
/**会员到期通知提醒*/
|
||||
VIP_EXPIRE_NOTICE_CODE("SMS_461885023","敲敲云","realname,endTime");
|
||||
|
||||
/**
|
||||
* 短信模板编码
|
||||
|
||||
@ -6,8 +6,6 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import org.apache.shiro.authz.UnauthorizedException;
|
||||
import org.jeecg.common.api.dto.LogDTO;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
@ -24,6 +22,8 @@ import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.data.redis.connection.PoolException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
@ -43,9 +43,27 @@ import java.util.Map;
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class JeecgBootExceptionHandler {
|
||||
|
||||
@Resource
|
||||
|
||||
@Resource
|
||||
BaseCommonService baseCommonService;
|
||||
|
||||
/**
|
||||
* 验证码错误异常
|
||||
*/
|
||||
|
||||
@ExceptionHandler(JeecgCaptchaException.class)
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
public Result<?> handleJeecgCaptchaException(JeecgCaptchaException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(AuthenticationException.class)
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
public Result<?> handleJeecgCaptchaException(AuthenticationException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.error(401, e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理自定义异常
|
||||
@ -101,10 +119,10 @@ public class JeecgBootExceptionHandler {
|
||||
return Result.error("数据库中已存在该记录");
|
||||
}
|
||||
|
||||
@ExceptionHandler({UnauthorizedException.class, AuthorizationException.class})
|
||||
public Result<?> handleAuthorizationException(AuthorizationException e){
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
public Result<?> handleAuthorizationException(AccessDeniedException e){
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.noauth("没有权限,请联系管理员分配权限!");
|
||||
return Result.noauth("没有权限,请联系管理员授权,后刷新缓存!");
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
package org.jeecg.common.exception;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author kezhijie@wuhandsj.com
|
||||
* @date 2024/1/2 11:38
|
||||
*/
|
||||
@Data
|
||||
public class JeecgCaptchaException extends RuntimeException{
|
||||
|
||||
private Integer code;
|
||||
|
||||
private static final long serialVersionUID = -9093410345065209053L;
|
||||
|
||||
public JeecgCaptchaException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public JeecgCaptchaException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public JeecgCaptchaException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,18 @@
|
||||
package org.jeecg.common.system.base.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.beanutils.PropertyUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
import org.jeecgframework.poi.excel.ExcelImportUtil;
|
||||
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
|
||||
import org.jeecgframework.poi.excel.entity.ExportParams;
|
||||
@ -19,6 +20,7 @@ import org.jeecgframework.poi.excel.entity.ImportParams;
|
||||
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
|
||||
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
@ -51,7 +53,7 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
protected ModelAndView exportXls(HttpServletRequest request, T object, Class<T> clazz, String title) {
|
||||
// Step.1 组装查询条件
|
||||
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
LoginUser sysUser = SecureUtil.currentUser();
|
||||
|
||||
// 过滤选中数据
|
||||
String selections = request.getParameter("selections");
|
||||
@ -89,7 +91,7 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
protected ModelAndView exportXlsSheet(HttpServletRequest request, T object, Class<T> clazz, String title,String exportFields,Integer pageNum) {
|
||||
// Step.1 组装查询条件
|
||||
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
LoginUser sysUser = SecureUtil.currentUser();
|
||||
// Step.2 计算分页sheet数据
|
||||
double total = service.count();
|
||||
int count = (int)Math.ceil(total/pageNum);
|
||||
|
||||
@ -746,11 +746,7 @@ public class QueryGenerator {
|
||||
private static boolean judgedIsUselessField(String name) {
|
||||
return "class".equals(name) || "ids".equals(name)
|
||||
|| "page".equals(name) || "rows".equals(name)
|
||||
//// update-begin--author:sunjianlei date:20240808 for:【TV360X-2009】取消过滤 sort、order 字段,防止前端排序报错 ------
|
||||
//// https://github.com/jeecgboot/JeecgBoot/issues/6937
|
||||
// || "sort".equals(name) || "order".equals(name)
|
||||
//// update-end----author:sunjianlei date:20240808 for:【TV360X-2009】取消过滤 sort、order 字段,防止前端排序报错 ------
|
||||
;
|
||||
|| "sort".equals(name) || "order".equals(name);
|
||||
}
|
||||
|
||||
|
||||
@ -805,9 +801,7 @@ public class QueryGenerator {
|
||||
addEasyQuery(queryWrapper, name, rule, DateUtils.str2Date(dateStr,DateUtils.datetimeFormat.get()));
|
||||
}
|
||||
}else {
|
||||
//update-begin---author:chenrui ---date:20241125 for:[issues/7481]多租户模式下 数据权限使用变量:#{tenant_id} 报错------------
|
||||
addEasyQuery(queryWrapper, name, rule, NumberUtils.parseNumber(converRuleValue(dataRule.getRuleValue()), propertyType));
|
||||
//update-end---author:chenrui ---date:20241125 for:[issues/7481]多租户模式下 数据权限使用变量:#{tenant_id} 报错------------
|
||||
addEasyQuery(queryWrapper, name, rule, NumberUtils.parseNumber(dataRule.getRuleValue(), propertyType));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -840,9 +834,6 @@ public class QueryGenerator {
|
||||
public static String getSqlRuleValue(String sqlRule){
|
||||
try {
|
||||
Set<String> varParams = getSqlRuleParams(sqlRule);
|
||||
if (varParams == null || varParams.isEmpty()) {
|
||||
return sqlRule;
|
||||
}
|
||||
for(String var:varParams){
|
||||
String tempValue = converRuleValue(var);
|
||||
sqlRule = sqlRule.replace("#{"+var+"}",tempValue);
|
||||
@ -861,9 +852,7 @@ public class QueryGenerator {
|
||||
return null;
|
||||
}
|
||||
Set<String> varParams = new HashSet<String>();
|
||||
//update-begin---author:chenrui ---date:20250108 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||
String regex = "#\\{\\[*\\w+]*}";
|
||||
//update-end---author:chenrui ---date:20250108 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||
String regex = "\\#\\{\\w+\\}";
|
||||
|
||||
Pattern p = Pattern.compile(regex);
|
||||
Matcher m = p.matcher(sql);
|
||||
|
||||
@ -54,10 +54,8 @@ public enum QueryRuleEnum {
|
||||
NOT_EMPTY("NOT_EMPTY","not_empty","值不为空"),
|
||||
/**查询规则 不包含*/
|
||||
NOT_IN("NOT_IN","not_in","不包含"),
|
||||
/**查询规则 多词精确匹配*/
|
||||
/**查询规则 多词匹配*/
|
||||
ELE_MATCH("ELE_MATCH","elemMatch","多词匹配"),
|
||||
/**查询规则 多词精确不匹配*/
|
||||
ELE_NOT_MATCH("ELE_NOT_MATCH","elemNotMatch","多词精确不匹配"),
|
||||
/**查询规则 范围查询*/
|
||||
RANGE("RANGE","range","范围查询"),
|
||||
/**查询规则 不在范围内查询*/
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package org.jeecg.common.system.util;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.JWTVerifier;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
@ -10,9 +12,9 @@ import com.google.common.base.Joiner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@ -20,7 +22,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.DataBaseConstant;
|
||||
@ -32,6 +34,22 @@ import org.jeecg.common.system.vo.SysUserCacheInfo;
|
||||
import org.jeecg.common.util.DateUtils;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.security.self.SelfAuthenticationProvider;
|
||||
import org.jeecg.config.security.self.SelfAuthenticationToken;
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.*;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
|
||||
/**
|
||||
* @Author Scott
|
||||
@ -45,6 +63,8 @@ public class JwtUtil {
|
||||
public static final long EXPIRE_TIME = (7 * 12) * 60 * 60 * 1000;
|
||||
static final String WELL_NUMBER = SymbolConstant.WELL_NUMBER + SymbolConstant.LEFT_CURLY_BRACKET;
|
||||
|
||||
public static final String DEFAULT_CLIENT = "jeecg-client";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param response
|
||||
@ -66,7 +86,7 @@ public class JwtUtil {
|
||||
os.flush();
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,13 +100,11 @@ public class JwtUtil {
|
||||
public static boolean verify(String token, String username, String secret) {
|
||||
try {
|
||||
// 根据密码生成JWT效验器
|
||||
Algorithm algorithm = Algorithm.HMAC256(secret);
|
||||
JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
|
||||
JwtDecoder jwtDecoder = SpringContextUtils.getBean(JwtDecoder.class);
|
||||
// 效验TOKEN
|
||||
DecodedJWT jwt = verifier.verify(token);
|
||||
jwtDecoder.decode(token);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
} catch (Exception exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -101,23 +119,30 @@ public class JwtUtil {
|
||||
DecodedJWT jwt = JWT.decode(token);
|
||||
return jwt.getClaim("username").asString();
|
||||
} catch (JWTDecodeException e) {
|
||||
log.warn(e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名,5min后过期
|
||||
* 生成token
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param secret 用户的密码
|
||||
* @return 加密的token
|
||||
*/
|
||||
public static String sign(String username, String secret) {
|
||||
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
|
||||
Algorithm algorithm = Algorithm.HMAC256(secret);
|
||||
// 附带username信息
|
||||
return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
|
||||
Map<String, Object> additionalParameter = new HashMap<>();
|
||||
additionalParameter.put("username", username);
|
||||
|
||||
RegisteredClientRepository registeredClientRepository = SpringContextUtils.getBean(RegisteredClientRepository.class);
|
||||
SelfAuthenticationProvider selfAuthenticationProvider = SpringContextUtils.getBean(SelfAuthenticationProvider.class);
|
||||
|
||||
OAuth2ClientAuthenticationToken client = new OAuth2ClientAuthenticationToken(Objects.requireNonNull(registeredClientRepository.findByClientId("jeecg-client")), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null);
|
||||
client.setAuthenticated(true);
|
||||
SelfAuthenticationToken selfAuthenticationToken = new SelfAuthenticationToken(client, additionalParameter);
|
||||
selfAuthenticationToken.setAuthenticated(true);
|
||||
OAuth2AccessTokenAuthenticationToken accessToken = (OAuth2AccessTokenAuthenticationToken) selfAuthenticationProvider.authenticate(selfAuthenticationToken);
|
||||
return accessToken.getAccessToken().getTokenValue();
|
||||
|
||||
}
|
||||
|
||||
@ -182,7 +207,7 @@ public class JwtUtil {
|
||||
//2.通过shiro获取登录用户信息
|
||||
LoginUser sysUser = null;
|
||||
try {
|
||||
sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
sysUser = SecureUtil.currentUser();
|
||||
} catch (Exception e) {
|
||||
log.warn("SecurityUtils.getSubject() 获取用户信息异常:" + e.getMessage());
|
||||
}
|
||||
@ -200,14 +225,6 @@ public class JwtUtil {
|
||||
} else {
|
||||
key = key;
|
||||
}
|
||||
//update-begin---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||
// 是否存在字符串标志
|
||||
boolean multiStr = false;
|
||||
if(oConvertUtils.isNotEmpty(key) && key.trim().matches("^\\[\\w+]$")){
|
||||
key = key.substring(1,key.length()-1);
|
||||
multiStr = true;
|
||||
}
|
||||
//update-end---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||
//替换为当前系统时间(年月日)
|
||||
if (key.equals(DataBaseConstant.SYS_DATE)|| key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
|
||||
returnValue = DateUtils.formatDate();
|
||||
@ -276,22 +293,11 @@ public class JwtUtil {
|
||||
if(user==null){
|
||||
//TODO 暂时使用用户登录部门,存在逻辑缺陷,不是用户所拥有的部门
|
||||
returnValue = sysUser.getOrgCode();
|
||||
//update-begin---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||
returnValue = multiStr ? "'" + returnValue + "'" : returnValue;
|
||||
//update-end---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||
}else{
|
||||
if(user.isOneDepart()) {
|
||||
returnValue = user.getSysMultiOrgCode().get(0);
|
||||
//update-begin---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||
returnValue = multiStr ? "'" + returnValue + "'" : returnValue;
|
||||
//update-end---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||
}else {
|
||||
//update-begin---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||
returnValue = user.getSysMultiOrgCode().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(orgCode -> "'" + orgCode + "'")
|
||||
.collect(Collectors.joining(", "));
|
||||
//update-end---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||
returnValue = Joiner.on(",").join(user.getSysMultiOrgCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
package org.jeecg.common.system.vo;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.desensitization.annotation.SensitiveField;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -20,8 +25,10 @@ import java.util.Date;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class LoginUser {
|
||||
public class LoginUser implements Serializable {
|
||||
|
||||
|
||||
private static final long serialVersionUID = -7143159031677245866L;
|
||||
/**
|
||||
* 登录人id
|
||||
*/
|
||||
@ -138,4 +145,29 @@ public class LoginUser {
|
||||
/**设备id uniapp推送用*/
|
||||
private String clientId;
|
||||
|
||||
@SensitiveField
|
||||
private String salt;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// 重新构建对象过滤一些敏感字段
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setId(id);
|
||||
loginUser.setUsername(username);
|
||||
loginUser.setRealname(realname);
|
||||
loginUser.setOrgCode(orgCode);
|
||||
loginUser.setSex(sex);
|
||||
loginUser.setEmail(email);
|
||||
loginUser.setPhone(phone);
|
||||
loginUser.setDelFlag(delFlag);
|
||||
loginUser.setStatus(status);
|
||||
loginUser.setActivitiSync(activitiSync);
|
||||
loginUser.setUserIdentity(userIdentity);
|
||||
loginUser.setDepartIds(departIds);
|
||||
loginUser.setPost(post);
|
||||
loginUser.setTelephone(telephone);
|
||||
loginUser.setRelTenantIds(relTenantIds);
|
||||
loginUser.setClientId(clientId);
|
||||
return JSON.toJSONString(loginUser);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.aliyuncs.DefaultAcsClient;
|
||||
import com.aliyuncs.IAcsClient;
|
||||
@ -9,15 +8,11 @@ import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
|
||||
import com.aliyuncs.exceptions.ClientException;
|
||||
import com.aliyuncs.profile.DefaultProfile;
|
||||
import com.aliyuncs.profile.IClientProfile;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.constant.enums.DySmsEnum;
|
||||
import org.jeecg.config.JeecgSmsTemplateConfig;
|
||||
import org.jeecg.config.StaticConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created on 17/6/7.
|
||||
* 短信API产品的DEMO程序,工程中包含了一个SmsDemo类,直接通过
|
||||
@ -80,33 +75,15 @@ public class DySmsHelper {
|
||||
|
||||
//验证json参数
|
||||
validateParam(templateParamJson,dySmsEnum);
|
||||
|
||||
//update-begin---author:wangshuai---date:2024-11-05---for:【QQYUN-9422】短信模板管理,阿里云---
|
||||
String templateCode = dySmsEnum.getTemplateCode();
|
||||
JeecgSmsTemplateConfig baseConfig = SpringContextUtils.getBean(JeecgSmsTemplateConfig.class);
|
||||
if(baseConfig != null && CollectionUtil.isNotEmpty(baseConfig.getTemplateCode())){
|
||||
Map<String, String> smsTemplate = baseConfig.getTemplateCode();
|
||||
if(smsTemplate.containsKey(templateCode) && StringUtils.isNotEmpty(smsTemplate.get(templateCode))){
|
||||
templateCode = smsTemplate.get(templateCode);
|
||||
logger.info("yml中读取短信code{}",templateCode);
|
||||
}
|
||||
}
|
||||
//签名名称
|
||||
String signName = dySmsEnum.getSignName();
|
||||
if(baseConfig != null && StringUtils.isNotEmpty(baseConfig.getSignature())){
|
||||
logger.info("yml中读取签名名称{}",baseConfig.getSignature());
|
||||
signName = baseConfig.getSignature();
|
||||
}
|
||||
//update-end---author:wangshuai---date:2024-11-05---for:【QQYUN-9422】短信模板管理,阿里云---
|
||||
|
||||
//组装请求对象-具体描述见控制台-文档部分内容
|
||||
SendSmsRequest request = new SendSmsRequest();
|
||||
//必填:待发送手机号
|
||||
request.setPhoneNumbers(phone);
|
||||
//必填:短信签名-可在短信控制台中找到
|
||||
request.setSignName(signName);
|
||||
request.setSignName(dySmsEnum.getSignName());
|
||||
//必填:短信模板-可在短信控制台中找到
|
||||
request.setTemplateCode(templateCode);
|
||||
request.setTemplateCode(dySmsEnum.getTemplateCode());
|
||||
//可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
|
||||
request.setTemplateParam(templateParamJson.toJSONString());
|
||||
|
||||
|
||||
@ -1,206 +0,0 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* @program: file
|
||||
* @description: 文件下载
|
||||
* @author: chenrui
|
||||
* @date: 2019-05-24 16:34
|
||||
**/
|
||||
@Slf4j
|
||||
public class FileDownloadUtils {
|
||||
|
||||
/**
|
||||
* 单文件下载
|
||||
*
|
||||
* @param response
|
||||
* @param storePath 下载文件储存地址
|
||||
* @param fileName 文件名称
|
||||
* @author: chenrui
|
||||
* @date: 2019/5/24 17:10
|
||||
*/
|
||||
public static void downloadFile(HttpServletResponse response, String storePath, String fileName) {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
File file = new File(storePath);
|
||||
if (!file.exists()) {
|
||||
throw new NullPointerException("Specified file not found");
|
||||
}
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
throw new NullPointerException("The file name can not null");
|
||||
}
|
||||
// 配置文件下载
|
||||
response.setHeader("content-type", "application/octet-stream");
|
||||
response.setContentType("application/octet-stream");
|
||||
// 下载文件能正常显示中文
|
||||
try {
|
||||
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
|
||||
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
// 实现文件下载
|
||||
byte[] buffer = new byte[1024];
|
||||
try (FileInputStream fis = new FileInputStream(file);
|
||||
BufferedInputStream bis = new BufferedInputStream(fis);) {
|
||||
OutputStream os = response.getOutputStream();
|
||||
int i = bis.read(buffer);
|
||||
while (i != -1) {
|
||||
os.write(buffer, 0, i);
|
||||
i = bis.read(buffer);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 多文件下载
|
||||
*
|
||||
* @param filesPath 下载文件集合
|
||||
* @param zipFileName 多文件合称名
|
||||
* @author: chenrui
|
||||
* @date: 2019/5/24 17:48
|
||||
*/
|
||||
public static void downloadFileMulti(HttpServletResponse response, List<String> filesPath, String zipFileName) throws IOException {
|
||||
//设置压缩包的名字
|
||||
String downloadName = zipFileName + ".zip";
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setHeader("content-type", "application/octet-stream");
|
||||
response.setContentType("application/octet-stream");
|
||||
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(downloadName, "UTF-8"));
|
||||
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
|
||||
|
||||
log.info("开始压缩文件:" + filesPath);
|
||||
//设置压缩流:直接写入response,实现边压缩边下载
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
|
||||
DataOutputStream os = new DataOutputStream(zipOut);) {
|
||||
//设置压缩方法
|
||||
zipOut.setMethod(ZipOutputStream.DEFLATED);
|
||||
for (String filePath : filesPath) {
|
||||
//循环将文件写入压缩流
|
||||
File file = new File(filePath);
|
||||
if (file.exists()) {
|
||||
//添加ZipEntry,并ZipEntry中写入文件流也就是将文件压入zip文件的目录下
|
||||
String fileName = file.getName();
|
||||
zipOut.putNextEntry(new ZipEntry(fileName));
|
||||
//格式输出流文件
|
||||
|
||||
InputStream is = Files.newInputStream(file.toPath());
|
||||
byte[] b = new byte[1024];
|
||||
int length;
|
||||
while ((length = is.read(b)) != -1) {
|
||||
os.write(b, 0, length);
|
||||
}
|
||||
is.close();
|
||||
zipOut.closeEntry();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
throw new JeecgBootException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载网络资源到磁盘
|
||||
*
|
||||
* @param fileUrl
|
||||
* @param storePath
|
||||
* @author chenrui
|
||||
* @date 2024/1/19 10:09
|
||||
*/
|
||||
public static String download2DiskFromNet(String fileUrl, String storePath) {
|
||||
try {
|
||||
URL url = new URL(fileUrl);
|
||||
URLConnection conn = url.openConnection();
|
||||
// 设置超时间为3秒
|
||||
conn.setConnectTimeout(3 * 1000);
|
||||
// 防止屏蔽程序
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
|
||||
// 确保目录存在
|
||||
File file = ensureDestFileDir(storePath);
|
||||
try (InputStream inStream = conn.getInputStream();
|
||||
FileOutputStream fs = new FileOutputStream(file);) {
|
||||
int byteread;
|
||||
byte[] buffer = new byte[1204];
|
||||
while ((byteread = inStream.read(buffer)) != -1) {
|
||||
fs.write(buffer, 0, byteread);
|
||||
}
|
||||
return storePath;
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
throw new JeecgBootException(e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
throw new JeecgBootException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取不重名的文件
|
||||
*
|
||||
* @param file
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2017年5月24日下午6:29:13
|
||||
* @version v0.0.1
|
||||
*/
|
||||
public static File getUniqueFile(final File file) {
|
||||
if (!file.exists()) {
|
||||
return file;
|
||||
}
|
||||
|
||||
File tmpFile = new File(file.getAbsolutePath());
|
||||
File parentDir = tmpFile.getParentFile();
|
||||
int count = 1;
|
||||
String extension = FilenameUtils.getExtension(tmpFile.getName());
|
||||
String baseName = FilenameUtils.getBaseName(tmpFile.getName());
|
||||
do {
|
||||
tmpFile = new File(parentDir, baseName + "(" + count++ + ")." + extension);
|
||||
} while (tmpFile.exists());
|
||||
return tmpFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保输出文件目录
|
||||
*
|
||||
* @param destFilePath
|
||||
* @return
|
||||
* @author: chenrui
|
||||
* @date: 2019-05-21 16:49
|
||||
*/
|
||||
private static File ensureDestFileDir(String destFilePath) {
|
||||
File destFile = new File(destFilePath);
|
||||
FileDownloadUtils.checkDirAndCreate(destFile.getParentFile());
|
||||
return destFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证文件夹存在且创建目录
|
||||
*
|
||||
* @param dir
|
||||
* @author chenrui
|
||||
* @date 2017年5月24日下午6:29:24
|
||||
* @version v0.0.1
|
||||
*/
|
||||
public static void checkDirAndCreate(File dir) {
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,13 +4,9 @@ import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.handler.IFillRuleHandler;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@ -46,30 +42,6 @@ public class FillRuleUtil {
|
||||
if (params == null) {
|
||||
params = new JSONObject();
|
||||
}
|
||||
|
||||
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
|
||||
|
||||
// 解析 params 中的变量
|
||||
// 优先级:queryString > 系统变量 > 默认值
|
||||
for (String key : params.keySet()) {
|
||||
// 1. 判断 queryString 中是否有该参数,如果有就优先取值
|
||||
//noinspection ConstantValue
|
||||
if (request != null) {
|
||||
String parameter = request.getParameter(key);
|
||||
if (oConvertUtils.isNotEmpty(parameter)) {
|
||||
params.put(key, parameter);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String value = params.getString(key);
|
||||
// 2. 用于替换 系统变量的值 #{sys_user_code}
|
||||
if (value != null && value.contains(SymbolConstant.SYS_VAR_PREFIX)) {
|
||||
value = QueryGenerator.getSqlRuleValue(value);
|
||||
params.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (formData == null) {
|
||||
formData = new JSONObject();
|
||||
}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
import org.pegdown.PegDownProcessor;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
/**
|
||||
@ -38,14 +36,8 @@ public class HTMLUtils {
|
||||
* @return
|
||||
*/
|
||||
public static String parseMarkdown(String markdownContent) {
|
||||
//update-begin---author:wangshuai---date:2024-06-26---for:【TV360X-1344】JDK17 邮箱发送失败,需要换写法---
|
||||
/*PegDownProcessor pdp = new PegDownProcessor();
|
||||
return pdp.markdownToHtml(markdownContent);*/
|
||||
Parser parser = Parser.builder().build();
|
||||
Node document = parser.parse(markdownContent);
|
||||
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||
return renderer.render(document);
|
||||
//update-end---author:wangshuai---date:2024-06-26---for:【TV360X-1344】JDK17 邮箱发送失败,需要换写法---
|
||||
PegDownProcessor pdp = new PegDownProcessor();
|
||||
return pdp.markdownToHtml(markdownContent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -57,8 +57,8 @@ public class RestUtil {
|
||||
|
||||
static {
|
||||
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
||||
requestFactory.setConnectTimeout(30000);
|
||||
requestFactory.setReadTimeout(30000);
|
||||
requestFactory.setConnectTimeout(3000);
|
||||
requestFactory.setReadTimeout(3000);
|
||||
RT = new RestTemplate(requestFactory);
|
||||
// 解决乱码问题
|
||||
RT.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
@ -11,8 +12,6 @@ import org.jeecg.common.exception.JeecgBoot401Exception;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* @Author scott
|
||||
* @Date 2019/9/23 14:12
|
||||
@ -106,8 +105,8 @@ public class TokenUtils {
|
||||
}
|
||||
|
||||
// 查询用户信息
|
||||
LoginUser user = TokenUtils.getLoginUser(username, commonApi, redisUtil);
|
||||
//LoginUser user = commonApi.getUserByName(username);
|
||||
//LoginUser user = TokenUtils.getLoginUser(username, commonApi, redisUtil);
|
||||
LoginUser user = commonApi.getUserByName(username);
|
||||
if (user == null) {
|
||||
throw new JeecgBoot401Exception("用户不存在!");
|
||||
}
|
||||
@ -158,10 +157,11 @@ public class TokenUtils {
|
||||
//【重要】此处通过redis原生获取缓存用户,是为了解决微服务下system服务挂了,其他服务互调不通问题---
|
||||
if (redisUtil.hasKey(loginUserKey)) {
|
||||
try {
|
||||
loginUser = (LoginUser) redisUtil.get(loginUserKey);
|
||||
Object obj = redisUtil.get(loginUserKey);
|
||||
loginUser = (LoginUser) obj;
|
||||
//解密用户
|
||||
SensitiveInfoUtil.handlerObject(loginUser, false);
|
||||
} catch (IllegalAccessException e) {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -84,10 +84,6 @@ public class DynamicDBUtil {
|
||||
} else {
|
||||
DruidDataSource dataSource = getJdbcDataSource(dbSource);
|
||||
if(dataSource!=null && dataSource.isEnable()){
|
||||
|
||||
// 【TV360X-2060】设置超时时间 6秒
|
||||
dataSource.setMaxWait(6000);
|
||||
|
||||
DataSourceCachePool.putCacheBasicDataSource(dbKey, dataSource);
|
||||
}else{
|
||||
throw new JeecgBootException("动态数据源连接失败,dbKey:"+dbKey);
|
||||
@ -110,10 +106,9 @@ public class DynamicDBUtil {
|
||||
dataSource.getConnection().commit();
|
||||
dataSource.getConnection().close();
|
||||
dataSource.close();
|
||||
DataSourceCachePool.removeCache(dbKey);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.warn(e.getMessage(), e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
package org.jeecg.common.util.encryption;
|
||||
|
||||
import org.apache.shiro.lang.codec.Base64;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* @Description: AES 加密
|
||||
@ -49,7 +48,7 @@ public class AesEncryptUtil {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
|
||||
byte[] encrypted = cipher.doFinal(plaintext);
|
||||
|
||||
return Base64.encodeToString(encrypted);
|
||||
return Base64.getEncoder().encodeToString(encrypted);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@ -67,7 +66,7 @@ public class AesEncryptUtil {
|
||||
*/
|
||||
public static String desEncrypt(String data, String key, String iv) throws Exception {
|
||||
//update-begin-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理
|
||||
byte[] encrypted1 = Base64.decode(data);
|
||||
byte[] encrypted1 = Base64.getDecoder().decode(data);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
|
||||
|
||||
@ -73,12 +73,6 @@ public class JSqlParserUtils {
|
||||
* @return
|
||||
*/
|
||||
private static SelectSqlInfo parseBySelectBody(SelectBody selectBody) {
|
||||
// 判断是否使用了union等操作
|
||||
if (selectBody instanceof SetOperationList) {
|
||||
// 如果使用了union等操作,则只解析第一个查询
|
||||
List<SelectBody> selectBodyList = ((SetOperationList) selectBody).getSelects();
|
||||
return JSqlParserUtils.parseBySelectBody(selectBodyList.get(0));
|
||||
}
|
||||
// 简单的select查询
|
||||
if (selectBody instanceof PlainSelect) {
|
||||
SelectSqlInfo sqlInfo = new SelectSqlInfo(selectBody);
|
||||
|
||||
@ -32,10 +32,6 @@ public class JeecgBaseConfig {
|
||||
*/
|
||||
private Firewall firewall;
|
||||
|
||||
/**
|
||||
* shiro拦截排除
|
||||
*/
|
||||
private Shiro shiro;
|
||||
/**
|
||||
* 上传文件配置
|
||||
*/
|
||||
@ -62,12 +58,8 @@ public class JeecgBaseConfig {
|
||||
* @return
|
||||
*/
|
||||
private WeiXinPay weiXinPay;
|
||||
|
||||
/**
|
||||
* 百度开放API配置
|
||||
*/
|
||||
private BaiduApi baiduApi;
|
||||
|
||||
|
||||
|
||||
public Elasticsearch getElasticsearch() {
|
||||
return elasticsearch;
|
||||
}
|
||||
@ -92,14 +84,6 @@ public class JeecgBaseConfig {
|
||||
this.signatureSecret = signatureSecret;
|
||||
}
|
||||
|
||||
public Shiro getShiro() {
|
||||
return shiro;
|
||||
}
|
||||
|
||||
public void setShiro(Shiro shiro) {
|
||||
this.shiro = shiro;
|
||||
}
|
||||
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
@ -147,13 +131,5 @@ public class JeecgBaseConfig {
|
||||
public void setWeiXinPay(WeiXinPay weiXinPay) {
|
||||
this.weiXinPay = weiXinPay;
|
||||
}
|
||||
|
||||
public BaiduApi getBaiduApi() {
|
||||
return baiduApi;
|
||||
}
|
||||
|
||||
public void setBaiduApi(BaiduApi baiduApi) {
|
||||
this.baiduApi = baiduApi;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
package org.jeecg.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Description: 短信模板
|
||||
*
|
||||
* @author: wangshuai
|
||||
* @date: 2024/11/5 下午3:44
|
||||
*/
|
||||
@Data
|
||||
@Component("jeecgSmsTemplateConfig")
|
||||
@ConfigurationProperties(prefix = "jeecg.oss.sms-template")
|
||||
public class JeecgSmsTemplateConfig {
|
||||
|
||||
/**
|
||||
* 短信签名
|
||||
*/
|
||||
private String signature;
|
||||
|
||||
|
||||
/**
|
||||
* 短信模板code
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private Map<String,String> templateCode;
|
||||
|
||||
}
|
||||
@ -21,12 +21,6 @@ public class StaticConfig {
|
||||
@Value(value = "${spring.mail.username:}")
|
||||
private String emailFrom;
|
||||
|
||||
/**
|
||||
* 是否开启定时发送
|
||||
*/
|
||||
@Value(value = "${spring.mail.timeJobSend:false}")
|
||||
private Boolean timeJobSend;
|
||||
|
||||
// /**
|
||||
// * 签名密钥串
|
||||
// */
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
//package org.jeecg.config;
|
||||
//
|
||||
//
|
||||
// 已使用swagger3config平替
|
||||
//import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
|
||||
//import io.swagger.annotations.ApiOperation;
|
||||
//import org.jeecg.common.constant.CommonConstant;
|
||||
//import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
|
||||
//import org.springframework.beans.BeansException;
|
||||
//import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
@ -19,13 +19,15 @@
|
||||
//import springfox.documentation.builders.ParameterBuilder;
|
||||
//import springfox.documentation.builders.PathSelectors;
|
||||
//import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
//import springfox.documentation.oas.annotations.EnableOpenApi;
|
||||
//import springfox.documentation.schema.ModelRef;
|
||||
//import springfox.documentation.service.*;
|
||||
//import springfox.documentation.spi.DocumentationType;
|
||||
//import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||
//import springfox.documentation.spring.web.plugins.Docket;
|
||||
//import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
|
||||
//import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
|
||||
//import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
|
||||
//import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
//
|
||||
//import java.lang.reflect.Field;
|
||||
//import java.util.ArrayList;
|
||||
@ -37,7 +39,8 @@
|
||||
// * @Author scott
|
||||
// */
|
||||
//@Configuration
|
||||
//@EnableSwagger2WebMvc
|
||||
//@EnableSwagger2 //开启 Swagger2
|
||||
//@EnableKnife4j //开启 knife4j,可以不写
|
||||
//@Import(BeanValidatorPluginsConfiguration.class)
|
||||
//public class Swagger2Config implements WebMvcConfigurer {
|
||||
//
|
||||
@ -95,14 +98,6 @@
|
||||
// List<Parameter> pars = new ArrayList<>();
|
||||
// tokenPar.name(CommonConstant.X_ACCESS_TOKEN).description("token").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
|
||||
// pars.add(tokenPar.build());
|
||||
// //update-begin-author:liusq---date:2024-08-15--for: 开启多租户时,全局参数增加租户id
|
||||
// if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
|
||||
// ParameterBuilder tenantPar = new ParameterBuilder();
|
||||
// tenantPar.name(CommonConstant.TENANT_ID).description("租户ID").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
|
||||
// pars.add(tenantPar.build());
|
||||
// }
|
||||
// //update-end-author:liusq---date:2024-08-15--for: 开启多租户时,全局参数增加租户id
|
||||
//
|
||||
// return pars;
|
||||
// }
|
||||
//
|
||||
@ -157,7 +152,7 @@
|
||||
//
|
||||
// @Override
|
||||
// public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
// if (bean instanceof WebMvcRequestHandlerProvider) {
|
||||
// if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
|
||||
// customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
|
||||
// }
|
||||
// return bean;
|
||||
|
||||
@ -3,46 +3,22 @@ package org.jeecg.config;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Paths;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
|
||||
import org.springdoc.core.filters.GlobalOpenApiMethodFilter;
|
||||
import org.springdoc.core.models.GroupedOpenApi;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author eightmonth
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@PropertySource("classpath:config/default-spring-doc.properties")
|
||||
public class Swagger3Config implements WebMvcConfigurer {
|
||||
// 定义不需要注入安全要求的路径集合
|
||||
Set<String> excludedPaths = new HashSet<>(Arrays.asList(
|
||||
"/sys/randomImage/{key}",
|
||||
"/sys/login",
|
||||
"/sys/phoneLogin",
|
||||
"/sys/mLogin",
|
||||
"/sys/sms",
|
||||
"/sys/cas/client/validateLogin",
|
||||
"/test/jeecgDemo/demo3",
|
||||
"/sys/thirdLogin/**",
|
||||
"/sys/user/register"
|
||||
));
|
||||
|
||||
/**
|
||||
/**
|
||||
*
|
||||
* 显示swagger-ui.html文档展示页,还必须注入swagger资源:
|
||||
*
|
||||
@ -56,33 +32,15 @@ public class Swagger3Config implements WebMvcConfigurer {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GlobalOpenApiMethodFilter globalOpenApiMethodFilter() {
|
||||
return method -> method.isAnnotationPresent(Operation.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GlobalOpenApiCustomizer globalOpenApiCustomizer() {
|
||||
return openApi -> {
|
||||
// 全局添加鉴权参数
|
||||
if (openApi.getPaths() != null) {
|
||||
openApi.getPaths().forEach((path, pathItem) -> {
|
||||
log.info("path: {}", path);
|
||||
// 检查当前路径是否在排除列表中
|
||||
boolean isExcluded = excludedPaths.stream().anyMatch(excludedPath ->
|
||||
excludedPath.equals(path) ||
|
||||
(excludedPath.endsWith("**") && path.startsWith(excludedPath.substring(0, excludedPath.length() - 2)))
|
||||
);
|
||||
|
||||
if (!isExcluded) {
|
||||
// 接口添加鉴权参数
|
||||
pathItem.readOperations()
|
||||
.forEach(operation ->
|
||||
operation.addSecurityItem(new SecurityRequirement().addList(CommonConstant.X_ACCESS_TOKEN))
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
public GroupedOpenApi swaggerOpenApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("default")
|
||||
.packagesToScan("org.jeecg")
|
||||
// 剔除以下几个包路径的接口生成文档
|
||||
.packagesToExclude("org.jeecg.modules.drag", "org.jeecg.modules.online", "org.jeecg.modules.jmreport")
|
||||
// 加了Operation注解的方法,才生成接口文档
|
||||
.addOpenApiMethodFilter(method -> method.isAnnotationPresent(Operation.class))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -90,13 +48,12 @@ public class Swagger3Config implements WebMvcConfigurer {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("JeecgBoot 后台服务API接口文档")
|
||||
.version("3.7.4")
|
||||
.version("1.0")
|
||||
.contact(new Contact().name("北京国炬信息技术有限公司").url("www.jeccg.com").email("jeecgos@163.com"))
|
||||
.description( "后台API接口")
|
||||
.termsOfService("NO terms of service")
|
||||
.license(new License().name("Apache 2.0").url("http://www.apache.org/licenses/LICENSE-2.0.html")))
|
||||
.addSecurityItem(new SecurityRequirement().addList(CommonConstant.X_ACCESS_TOKEN))
|
||||
.components(new Components().addSecuritySchemes(CommonConstant.X_ACCESS_TOKEN,
|
||||
new SecurityScheme().name(CommonConstant.X_ACCESS_TOKEN).type(SecurityScheme.Type.HTTP)));
|
||||
.license(new License().name("Apache 2.0").url("http://www.apache.org/licenses/LICENSE-2.0.html"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Spring Boot 2.0 解决跨域问题
|
||||
@ -58,6 +59,11 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
||||
@Autowired(required = false)
|
||||
private PrometheusMeterRegistry prometheusMeterRegistry;
|
||||
|
||||
@Autowired
|
||||
private ObjectProvider<Jackson2ObjectMapperBuilder> builderProvider;
|
||||
@Autowired
|
||||
private JacksonProperties jacksonProperties;
|
||||
|
||||
/**
|
||||
* 静态资源的配置 - 使得可以从磁盘中读取 Html、图片、视频、音频等
|
||||
*/
|
||||
@ -110,6 +116,10 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
||||
@Primary
|
||||
public ObjectMapper objectMapper() {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 继承spring jackson 默认机制
|
||||
if (Objects.nonNull(builderProvider.getIfAvailable())) {
|
||||
objectMapper = builderProvider.getIfAvailable().createXmlMapper(false).build();
|
||||
}
|
||||
//处理bigDecimal
|
||||
objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
|
||||
objectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
|
||||
@ -118,8 +128,10 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, false);
|
||||
//默认的处理日期时间格式
|
||||
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
|
||||
//默认的处理日期时间格式,接受通过spring.jackson.date-format配置格式化模式
|
||||
if (Objects.isNull(jacksonProperties.getDateFormat())) {
|
||||
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||
@ -131,16 +143,17 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
//update-begin---author:chenrui ---date:20240514 for:[QQYUN-9247]系统监控功能优化------------
|
||||
// /**
|
||||
// * SpringBootAdmin的Httptrace不见了
|
||||
// * https://blog.csdn.net/u013810234/article/details/110097201
|
||||
// */
|
||||
// @Bean
|
||||
// public InMemoryHttpTraceRepository getInMemoryHttpTrace(){
|
||||
// return new InMemoryHttpTraceRepository();
|
||||
// public InMemoryHttpExchangeRepository getInMemoryHttpTrace(){
|
||||
// InMemoryHttpExchangeRepository repository = new InMemoryHttpExchangeRepository();
|
||||
// // 默认保存1000条http请求记录
|
||||
// repository.setCapacity(1000);
|
||||
// return repository;
|
||||
// }
|
||||
//update-end---author:chenrui ---date:20240514 for:[QQYUN-9247]系统监控功能优化------------
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@ -31,7 +31,7 @@ public class WebSocketConfig {
|
||||
FilterRegistrationBean bean = new FilterRegistrationBean();
|
||||
bean.setFilter(websocketFilter());
|
||||
//TODO 临时注释掉,测试下线上socket总断的问题
|
||||
bean.addUrlPatterns("/taskCountSocket/*", "/websocket/*","/eoaSocket/*","/eoaNewChatSocket/*", "/newsWebsocket/*", "/dragChannelSocket/*", "/vxeSocket/*");
|
||||
bean.addUrlPatterns("/taskCountSocket/*", "/websocket/*","/eoaSocket/*","/eoaNewChatSocket/*", "/newsWebsocket/*", "/vxeSocket/*");
|
||||
return bean;
|
||||
}
|
||||
|
||||
|
||||
@ -2,19 +2,16 @@ package org.jeecg.config.firewall.interceptor;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.CommonUtils;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.config.firewall.interceptor.enums.LowCodeUrlsEnum;
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
@ -66,7 +63,7 @@ public class LowCodeModeInterceptor implements HandlerInterceptor {
|
||||
if (jeecgBaseConfig.getFirewall()!=null && LowCodeModeInterceptor.LOW_CODE_MODE_PROD.equals(jeecgBaseConfig.getFirewall().getLowCodeMode())) {
|
||||
String requestURI = request.getRequestURI().substring(request.getContextPath().length());
|
||||
log.info("低代码模式,拦截请求路径:" + requestURI);
|
||||
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
LoginUser loginUser = SecureUtil.currentUser();
|
||||
Set<String> hasRoles = null;
|
||||
if (loginUser == null) {
|
||||
loginUser = commonAPI.getUserByName(JwtUtil.getUserNameByToken(SpringContextUtils.getHttpServletRequest()));
|
||||
|
||||
@ -6,13 +6,13 @@ import org.apache.ibatis.executor.Executor;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlCommandType;
|
||||
import org.apache.ibatis.plugin.*;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.config.TenantContext;
|
||||
import org.jeecg.common.constant.TenantConstant;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.common.util.TokenUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@ -192,7 +192,7 @@ public class MybatisInterceptor implements Interceptor {
|
||||
private LoginUser getLoginUser() {
|
||||
LoginUser sysUser = null;
|
||||
try {
|
||||
sysUser = SecurityUtils.getSubject().getPrincipal() != null ? (LoginUser) SecurityUtils.getSubject().getPrincipal() : null;
|
||||
sysUser = SecureUtil.currentUser() != null ? SecureUtil.currentUser() : null;
|
||||
} catch (Exception e) {
|
||||
//e.printStackTrace();
|
||||
sysUser = null;
|
||||
|
||||
@ -0,0 +1,90 @@
|
||||
package org.jeecg.config.security;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* spring authorization server 注册客户端便捷工具类
|
||||
* @author eightmonth@qq.com
|
||||
* @date 2024/3/7 11:22
|
||||
*/
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class ClientService {
|
||||
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
|
||||
/**
|
||||
* 修改客户端token有效期
|
||||
* 认证码、设备码有效期与accessToken有效期保持一致
|
||||
*/
|
||||
public void updateTokenValidation(String clientId, Long accessTokenValidation, Long refreshTokenValidation){
|
||||
RegisteredClient registeredClient = findByClientId(clientId);
|
||||
RegisteredClient.Builder builder = RegisteredClient.from(registeredClient);
|
||||
TokenSettings tokenSettings = TokenSettings.builder()
|
||||
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
|
||||
.accessTokenTimeToLive(Duration.ofSeconds(accessTokenValidation))
|
||||
.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
|
||||
.reuseRefreshTokens(true)
|
||||
.refreshTokenTimeToLive(Duration.ofSeconds(refreshTokenValidation))
|
||||
.authorizationCodeTimeToLive(Duration.ofSeconds(accessTokenValidation))
|
||||
.deviceCodeTimeToLive(Duration.ofSeconds(accessTokenValidation))
|
||||
.build();
|
||||
builder.tokenSettings(tokenSettings);
|
||||
registeredClientRepository.save(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改客户端授权类型
|
||||
* @param clientId
|
||||
* @param grantTypes
|
||||
*/
|
||||
public void updateGrantType(String clientId, Set<AuthorizationGrantType> grantTypes) {
|
||||
RegisteredClient registeredClient = findByClientId(clientId);
|
||||
RegisteredClient.Builder builder = RegisteredClient.from(registeredClient);
|
||||
for (AuthorizationGrantType grantType : grantTypes) {
|
||||
builder.authorizationGrantType(grantType);
|
||||
}
|
||||
registeredClientRepository.save(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改客户端重定向uri
|
||||
* @param clientId
|
||||
* @param redirectUris
|
||||
*/
|
||||
public void updateRedirectUris(String clientId, String redirectUris) {
|
||||
RegisteredClient registeredClient = findByClientId(clientId);
|
||||
RegisteredClient.Builder builder = RegisteredClient.from(registeredClient);
|
||||
builder.redirectUri(redirectUris);
|
||||
registeredClientRepository.save(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改客户端授权范围
|
||||
* @param clientId
|
||||
* @param scopes
|
||||
*/
|
||||
public void updateScopes(String clientId, Set<String> scopes) {
|
||||
RegisteredClient registeredClient = findByClientId(clientId);
|
||||
RegisteredClient.Builder builder = RegisteredClient.from(registeredClient);
|
||||
for (String scope : scopes) {
|
||||
builder.scope(scope);
|
||||
}
|
||||
registeredClientRepository.save(builder.build());
|
||||
}
|
||||
|
||||
public RegisteredClient findByClientId(String clientId) {
|
||||
return registeredClientRepository.findByClientId(clientId);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package org.jeecg.config.security;
|
||||
|
||||
import io.undertow.servlet.spec.HttpServletRequestImpl;
|
||||
import io.undertow.util.HttpString;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 复制仪盘表请求query体携带的token
|
||||
* @author eightmonth
|
||||
* @date 2024/7/3 14:04
|
||||
*/
|
||||
@Component
|
||||
@Order(value = Integer.MIN_VALUE)
|
||||
public class CopyTokenFilter extends OncePerRequestFilter {
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
// 以下为undertow定制代码,如切换其它servlet容器,需要同步更换
|
||||
HttpServletRequestImpl undertowRequest = (HttpServletRequestImpl) request;
|
||||
String token = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(token)) {
|
||||
undertowRequest.getExchange().getRequestHeaders().remove("Authorization");
|
||||
undertowRequest.getExchange().getRequestHeaders().add(new HttpString("Authorization"), "bearer " + token);
|
||||
} else {
|
||||
String bearerToken = request.getParameter("token");
|
||||
String headerBearerToken = request.getHeader("X-Access-Token");
|
||||
if (StringUtils.hasText(bearerToken)) {
|
||||
undertowRequest.getExchange().getRequestHeaders().add(new HttpString("Authorization"), "bearer " + bearerToken);
|
||||
} else if (StringUtils.hasText(headerBearerToken)) {
|
||||
undertowRequest.getExchange().getRequestHeaders().add(new HttpString("Authorization"), "bearer " + headerBearerToken);
|
||||
}
|
||||
}
|
||||
filterChain.doFilter(undertowRequest, response);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package org.jeecg.config.security;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* token只存储用户名与过期时间
|
||||
* 这里通过取用户名转全量用户信息存储到Security中
|
||||
* @author eightmonth@qq.com
|
||||
* @date 2024/7/15 11:05
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JeecgAuthenticationConvert implements Converter<Jwt, AbstractAuthenticationToken> {
|
||||
|
||||
private final CommonAPI commonAPI;
|
||||
|
||||
@Override
|
||||
public AbstractAuthenticationToken convert(Jwt source) {
|
||||
String username = source.getClaims().get("username").toString();
|
||||
LoginUser loginUser = commonAPI.getUserByName(username);
|
||||
return new UsernamePasswordAuthenticationToken(loginUser, null, new ArrayList<>());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,135 @@
|
||||
package org.jeecg.config.security;
|
||||
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.security.oauth2.jwt.JwsHeader;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.token.*;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.time.temporal.TemporalUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author eightmonth@qq.com
|
||||
* @date 2024/7/11 17:10
|
||||
*/
|
||||
public class JeecgOAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OAuth2AccessToken> {
|
||||
private final JwtEncoder jwtEncoder;
|
||||
|
||||
private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
|
||||
|
||||
public JeecgOAuth2AccessTokenGenerator(JwtEncoder jwtEncoder) {
|
||||
this.jwtEncoder = jwtEncoder;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public OAuth2AccessToken generate(OAuth2TokenContext context) {
|
||||
if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String issuer = null;
|
||||
if (context.getAuthorizationServerContext() != null) {
|
||||
issuer = context.getAuthorizationServerContext().getIssuer();
|
||||
}
|
||||
RegisteredClient registeredClient = context.getRegisteredClient();
|
||||
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plusMillis(JwtUtil.EXPIRE_TIME);
|
||||
|
||||
OAuth2TokenClaimsSet.Builder claimsBuilder = OAuth2TokenClaimsSet.builder();
|
||||
if (StringUtils.hasText(issuer)) {
|
||||
claimsBuilder.issuer(issuer);
|
||||
}
|
||||
claimsBuilder
|
||||
.subject(context.getPrincipal().getName())
|
||||
.audience(Collections.singletonList(registeredClient.getClientId()))
|
||||
.issuedAt(issuedAt)
|
||||
.expiresAt(expiresAt)
|
||||
.notBefore(issuedAt)
|
||||
.id(UUID.randomUUID().toString());
|
||||
if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) {
|
||||
claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes());
|
||||
}
|
||||
|
||||
if (this.accessTokenCustomizer != null) {
|
||||
OAuth2TokenClaimsContext.Builder accessTokenContextBuilder = OAuth2TokenClaimsContext.with(claimsBuilder)
|
||||
.registeredClient(context.getRegisteredClient())
|
||||
.principal(context.getPrincipal())
|
||||
.authorizationServerContext(context.getAuthorizationServerContext())
|
||||
.authorizedScopes(context.getAuthorizedScopes())
|
||||
.tokenType(context.getTokenType())
|
||||
.authorizationGrantType(context.getAuthorizationGrantType());
|
||||
if (context.getAuthorization() != null) {
|
||||
accessTokenContextBuilder.authorization(context.getAuthorization());
|
||||
}
|
||||
if (context.getAuthorizationGrant() != null) {
|
||||
accessTokenContextBuilder.authorizationGrant(context.getAuthorizationGrant());
|
||||
}
|
||||
|
||||
OAuth2TokenClaimsContext accessTokenContext = accessTokenContextBuilder.build();
|
||||
this.accessTokenCustomizer.customize(accessTokenContext);
|
||||
}
|
||||
|
||||
|
||||
OAuth2TokenClaimsSet accessTokenClaimsSet = claimsBuilder.build();
|
||||
OAuth2AuthorizationGrantAuthenticationToken oAuth2ResourceOwnerBaseAuthenticationToken = context.getAuthorizationGrant();
|
||||
String username = (String) oAuth2ResourceOwnerBaseAuthenticationToken.getAdditionalParameters().get("username");
|
||||
String tokenValue = jwtEncoder.encode(JwtEncoderParameters.from(JwsHeader.with(SignatureAlgorithm.ES256).keyId("jeecg").build(),
|
||||
JwtClaimsSet.builder().claim("username", username).expiresAt(expiresAt).build())).getTokenValue();
|
||||
|
||||
//此处可以做改造将tokenValue随机数换成用户信息,方便后续多系统token互通认证(通过解密token得到username)
|
||||
return new OAuth2AccessTokenClaims(OAuth2AccessToken.TokenType.BEARER, tokenValue,
|
||||
accessTokenClaimsSet.getIssuedAt(), accessTokenClaimsSet.getExpiresAt(), context.getAuthorizedScopes(),
|
||||
accessTokenClaimsSet.getClaims());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link OAuth2TokenCustomizer} that customizes the
|
||||
* {@link OAuth2TokenClaimsContext#getClaims() claims} for the
|
||||
* {@link OAuth2AccessToken}.
|
||||
* @param accessTokenCustomizer the {@link OAuth2TokenCustomizer} that customizes the
|
||||
* claims for the {@code OAuth2AccessToken}
|
||||
*/
|
||||
public void setAccessTokenCustomizer(OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer) {
|
||||
Assert.notNull(accessTokenCustomizer, "accessTokenCustomizer cannot be null");
|
||||
this.accessTokenCustomizer = accessTokenCustomizer;
|
||||
}
|
||||
|
||||
private static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor {
|
||||
|
||||
private final Map<String, Object> claims;
|
||||
|
||||
private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt,
|
||||
Set<String> scopes, Map<String, Object> claims) {
|
||||
super(tokenType, tokenValue, issuedAt, expiresAt, scopes);
|
||||
this.claims = claims;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getClaims() {
|
||||
return this.claims;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
package org.jeecg.config.security;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* spring authorization server自定义权限处理,根据@PreAuthorize注解,判断当前用户是否具备权限
|
||||
* @author EightMonth
|
||||
* @date 2024/1/10 17:00
|
||||
*/
|
||||
@Service("jps")
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class JeecgPermissionService {
|
||||
private final String SPLIT = "::";
|
||||
private final String PERM_PREFIX = "jps" + SPLIT;
|
||||
|
||||
private final CommonAPI commonAPI;
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
/**
|
||||
* 判断接口是否有任意xxx,xxx权限
|
||||
* @param permissions 权限
|
||||
* @return {boolean}
|
||||
*/
|
||||
public boolean requiresPermissions(String... permissions) {
|
||||
if (ArrayUtil.isEmpty(permissions)) {
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = SecureUtil.currentUser();
|
||||
|
||||
Object cache = redisUtil.get(buildKey("permission", loginUser.getId()));
|
||||
Set<String> permissionList;
|
||||
if (Objects.nonNull(cache)) {
|
||||
permissionList = (Set<String>) cache;
|
||||
} else {
|
||||
permissionList = commonAPI.queryUserAuths(loginUser.getId());
|
||||
redisUtil.set(buildKey("permission", loginUser.getId()), permissionList);
|
||||
}
|
||||
|
||||
boolean pass = permissionList.stream().filter(StringUtils::hasText)
|
||||
.anyMatch(x -> PatternMatchUtils.simpleMatch(permissions, x));
|
||||
if (!pass) {
|
||||
log.error("权限不足,缺少权限:"+ Arrays.toString(permissions));
|
||||
}
|
||||
return pass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断接口是否有任意xxx,xxx角色
|
||||
* @param roles 角色
|
||||
* @return {boolean}
|
||||
*/
|
||||
public boolean requiresRoles(String... roles) {
|
||||
if (ArrayUtil.isEmpty(roles)) {
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = SecureUtil.currentUser();
|
||||
|
||||
Object cache = redisUtil.get(buildKey("role", loginUser.getUsername()));
|
||||
Set<String> roleList;
|
||||
if (Objects.nonNull(cache)) {
|
||||
roleList = (Set<String>) cache;
|
||||
} else {
|
||||
roleList = commonAPI.queryUserRoles(loginUser.getUsername());
|
||||
redisUtil.set(buildKey("role", loginUser.getUsername()), roleList);
|
||||
}
|
||||
|
||||
boolean pass = roleList.stream().filter(StringUtils::hasText)
|
||||
.anyMatch(x -> PatternMatchUtils.simpleMatch(roles, x));
|
||||
if (!pass) {
|
||||
log.error("权限不足,缺少角色:" + Arrays.toString(roles));
|
||||
}
|
||||
return pass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 由于缓存key是以人的维度,角色列表、权限列表在值中,jeecg是以权限列表绑定在角色上,形成的权限集合
|
||||
* 权限发生变更时,需要清理全部人的权限缓存
|
||||
*/
|
||||
public void clearCache() {
|
||||
redisUtil.removeAll(PERM_PREFIX);
|
||||
}
|
||||
|
||||
private String buildKey(String type, String username) {
|
||||
return PERM_PREFIX + type + SPLIT + username;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package org.jeecg.config.security;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* spring authorization server 自定义redis保存授权范围信息
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JeecgRedisOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
private final static Long TIMEOUT = 10L;
|
||||
|
||||
@Override
|
||||
public void save(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
|
||||
redisTemplate.opsForValue().set(buildKey(authorizationConsent), authorizationConsent, TIMEOUT,
|
||||
TimeUnit.MINUTES);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
redisTemplate.delete(buildKey(authorizationConsent));
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
|
||||
Assert.hasText(registeredClientId, "registeredClientId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
return (OAuth2AuthorizationConsent) redisTemplate.opsForValue()
|
||||
.get(buildKey(registeredClientId, principalName));
|
||||
}
|
||||
|
||||
private static String buildKey(String registeredClientId, String principalName) {
|
||||
return "token:consent:" + registeredClientId + ":" + principalName;
|
||||
}
|
||||
|
||||
private static String buildKey(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
return buildKey(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,181 @@
|
||||
package org.jeecg.config.security;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* spring authorization server自定义redis保存认证信息
|
||||
* @author EightMonth
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JeecgRedisOAuth2AuthorizationService implements OAuth2AuthorizationService {
|
||||
|
||||
private final static Long TIMEOUT = 10L;
|
||||
|
||||
private static final String AUTHORIZATION = "token";
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Override
|
||||
public void save(OAuth2Authorization authorization) {
|
||||
Assert.notNull(authorization, "authorization cannot be null");
|
||||
|
||||
if (isState(authorization)) {
|
||||
String token = authorization.getAttribute("state");
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT,
|
||||
TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
if (isCode(authorization)) {
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
|
||||
.getToken(OAuth2AuthorizationCode.class);
|
||||
OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
|
||||
long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(),
|
||||
authorizationCodeToken.getExpiresAt());
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()),
|
||||
authorization, between, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
if (isRefreshToken(authorization)) {
|
||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
||||
long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt());
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()),
|
||||
authorization, between, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
if (isAccessToken(authorization)) {
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()),
|
||||
authorization, between, TimeUnit.SECONDS);
|
||||
|
||||
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
|
||||
String tokenUsername = String.format("%s::%s::%s", AUTHORIZATION, authorization.getPrincipalName(), accessToken.getTokenValue());
|
||||
redisTemplate.opsForValue().set(tokenUsername, accessToken.getTokenValue(), between, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(OAuth2Authorization authorization) {
|
||||
Assert.notNull(authorization, "authorization cannot be null");
|
||||
|
||||
List<String> keys = new ArrayList<>();
|
||||
if (isState(authorization)) {
|
||||
String token = authorization.getAttribute("state");
|
||||
keys.add(buildKey(OAuth2ParameterNames.STATE, token));
|
||||
}
|
||||
|
||||
if (isCode(authorization)) {
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
|
||||
.getToken(OAuth2AuthorizationCode.class);
|
||||
OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
|
||||
keys.add(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()));
|
||||
}
|
||||
|
||||
if (isRefreshToken(authorization)) {
|
||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
||||
keys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()));
|
||||
}
|
||||
|
||||
if (isAccessToken(authorization)) {
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()));
|
||||
|
||||
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
|
||||
String key = String.format("%s::%s::%s", AUTHORIZATION, authorization.getPrincipalName(), accessToken.getTokenValue());
|
||||
keys.add(key);
|
||||
}
|
||||
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuth2Authorization findById(String id) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) {
|
||||
Assert.hasText(token, "token cannot be empty");
|
||||
Assert.notNull(tokenType, "tokenType cannot be empty");
|
||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||
return (OAuth2Authorization) redisTemplate.opsForValue().get(buildKey(tokenType.getValue(), token));
|
||||
}
|
||||
|
||||
private String buildKey(String type, String id) {
|
||||
return String.format("%s::%s::%s", AUTHORIZATION, type, id);
|
||||
}
|
||||
|
||||
private static boolean isState(OAuth2Authorization authorization) {
|
||||
return Objects.nonNull(authorization.getAttribute("state"));
|
||||
}
|
||||
|
||||
private static boolean isCode(OAuth2Authorization authorization) {
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
|
||||
.getToken(OAuth2AuthorizationCode.class);
|
||||
return Objects.nonNull(authorizationCode);
|
||||
}
|
||||
|
||||
private static boolean isRefreshToken(OAuth2Authorization authorization) {
|
||||
return Objects.nonNull(authorization.getRefreshToken());
|
||||
}
|
||||
|
||||
private static boolean isAccessToken(OAuth2Authorization authorization) {
|
||||
return Objects.nonNull(authorization.getAccessToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展方法根据 username 查询是否存在存储的
|
||||
* @param authentication
|
||||
* @return
|
||||
*/
|
||||
public void removeByUsername(Authentication authentication) {
|
||||
// 根据 username查询对应access-token
|
||||
String authenticationName = authentication.getName();
|
||||
|
||||
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
|
||||
String tokenUsernameKey = String.format("%s::%s::*", AUTHORIZATION, authenticationName);
|
||||
Set<String> keys = redisTemplate.keys(tokenUsernameKey);
|
||||
if (CollUtil.isEmpty(keys)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Object> tokenList = redisTemplate.opsForValue().multiGet(keys);
|
||||
|
||||
for (Object token : tokenList) {
|
||||
// 根据token 查询存储的 OAuth2Authorization
|
||||
OAuth2Authorization authorization = this.findByToken((String) token, OAuth2TokenType.ACCESS_TOKEN);
|
||||
// 根据 OAuth2Authorization 删除相关令牌
|
||||
this.remove(authorization);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package org.jeecg.config.security;
|
||||
|
||||
/**
|
||||
* 登录模式
|
||||
* @author EightMonth
|
||||
* @date 2024/1/10 17:43
|
||||
*/
|
||||
public class LoginType {
|
||||
|
||||
/**
|
||||
* 密码模式
|
||||
*/
|
||||
public static final String PASSWORD = "password";
|
||||
|
||||
|
||||
/**
|
||||
* 手机号+验证码模式
|
||||
*/
|
||||
public static final String PHONE = "phone";
|
||||
|
||||
|
||||
/**
|
||||
* app登录
|
||||
*/
|
||||
public static final String APP = "app";
|
||||
|
||||
/**
|
||||
* 扫码登录
|
||||
*/
|
||||
public static final String SCAN = "scan";
|
||||
|
||||
/**
|
||||
* 所有联合登录,比如github\钉钉\企业微信\微信
|
||||
*/
|
||||
public static final String SOCIAL = "social";
|
||||
|
||||
public static final String SELF = "self";
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package org.jeecg.config.security;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenErrors;
|
||||
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 当用户被强退时,使客户端token失效
|
||||
* @author eightmonth@qq.com
|
||||
* @date 2024/3/7 17:30
|
||||
*/
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class RedisTokenValidationFilter extends OncePerRequestFilter {
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
private JwtDecoder jwtDecoder;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
// 从请求中获取token
|
||||
DefaultBearerTokenResolver defaultBearerTokenResolver = new DefaultBearerTokenResolver();
|
||||
String token = defaultBearerTokenResolver.resolve(request);
|
||||
|
||||
|
||||
if (Objects.nonNull(token)) {
|
||||
// 检查认证信息是否已被清除,如果已被清除,则令该token失效
|
||||
OAuth2Authorization oAuth2Authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
|
||||
if (Objects.isNull(oAuth2Authorization)) {
|
||||
throw new OAuth2AuthenticationException(BearerTokenErrors.invalidToken("认证信息已失效,请重新登录"));
|
||||
}
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,240 @@
|
||||
package org.jeecg.config.security;
|
||||
|
||||
import com.nimbusds.jose.jwk.Curve;
|
||||
import com.nimbusds.jose.jwk.ECKey;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.jeecg.config.security.app.AppGrantAuthenticationConvert;
|
||||
import org.jeecg.config.security.app.AppGrantAuthenticationProvider;
|
||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationConvert;
|
||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationProvider;
|
||||
import org.jeecg.config.security.phone.PhoneGrantAuthenticationConvert;
|
||||
import org.jeecg.config.security.phone.PhoneGrantAuthenticationProvider;
|
||||
import org.jeecg.config.security.social.SocialGrantAuthenticationConvert;
|
||||
import org.jeecg.config.security.social.SocialGrantAuthenticationProvider;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.security.config.Customizer;
|
||||
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.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.token.*;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* spring authorization server核心配置
|
||||
* @author eightmonth@qq.com
|
||||
* @date 2024/1/2 9:29
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
@AllArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
private JeecgAuthenticationConvert jeecgAuthenticationConvert;
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
|
||||
throws Exception {
|
||||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
|
||||
// 注册自定义登录类型
|
||||
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
|
||||
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new PasswordGrantAuthenticationConvert())
|
||||
.authenticationProvider(new PasswordGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
||||
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new PhoneGrantAuthenticationConvert())
|
||||
.authenticationProvider(new PhoneGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
||||
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new AppGrantAuthenticationConvert())
|
||||
.authenticationProvider(new AppGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
||||
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new SocialGrantAuthenticationConvert())
|
||||
.authenticationProvider(new SocialGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
||||
//开启OpenID Connect 1.0(其中oidc为OpenID Connect的缩写)。 访问 /.well-known/openid-configuration即可获取认证信息
|
||||
.oidc(Customizer.withDefaults());
|
||||
http
|
||||
//将需要认证的请求,重定向到login页面行登录认证。
|
||||
.exceptionHandling((exceptions) -> exceptions
|
||||
.defaultAuthenticationEntryPointFor(
|
||||
new LoginUrlAuthenticationEntryPoint("/sys/login"),
|
||||
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
|
||||
)
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
|
||||
throws Exception {
|
||||
http
|
||||
//设置所有请求都需要认证,未认证的请求都被重定向到login页面进行登录
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/cas/client/validateLogin")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/randomImage/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/checkCaptcha")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/login")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/mLogin")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/logout")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/thirdLogin/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/getEncryptedString")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/sms")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/phoneLogin")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/user/checkOnlyUser")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/user/register")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/user/phoneVerification")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/user/passwordChange")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/auth/2step-code")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/common/static/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/common/pdf/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/generic/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/getLoginQrcode/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/getQrcodeToken/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/checkAuth")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/doc.html")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.js")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.css")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.html")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.svg")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.pdf")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.jpg")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.png")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.gif")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.ico")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.ttf")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.woff")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.woff2")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/druid/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/swagger-ui.html")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/swagger**/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/webjars/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/v3/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/WW_verify*")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/annountCement/show/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/jmreport/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.js.map")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.css.map")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/list")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/view")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/onlDragDatasetHead/getLoginUser")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/page/queryById")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/onlDragDatasetHead/getAllChartData")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/onlDragDatasetHead/getTotalData")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/mock/json/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/test/bigScreen/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/bigscreen/template1/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/bigscreen/template1/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/websocket/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/newsWebsocket/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/vxeSocket/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/test/seata/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/error")).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
||||
.cors(cors -> cors
|
||||
.configurationSource(req -> {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.applyPermitDefaultValues();
|
||||
config.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
|
||||
return config;
|
||||
}))
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jeecgAuthenticationConvert)));
|
||||
return http.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库保存注册客户端信息
|
||||
*/
|
||||
@Bean
|
||||
public RegisteredClientRepository registeredClientRepository() {
|
||||
return new JdbcRegisteredClientRepository(jdbcTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
*配置 JWK,为JWT(id_token)提供加密密钥,用于加密/解密或签名/验签
|
||||
* JWK详细见:https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key-41
|
||||
*/
|
||||
@Bean
|
||||
@SneakyThrows
|
||||
public JWKSource<SecurityContext> jwkSource() {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
|
||||
// 如果不设置secureRandom,会存在一个问题,当应用重启后,原有的token将会全部失效,因为重启的keyPair与之前已经不同
|
||||
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
|
||||
// 重要!生产环境需要修改!
|
||||
secureRandom.setSeed("jeecg".getBytes());
|
||||
keyPairGenerator.initialize(256, secureRandom);
|
||||
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
|
||||
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
|
||||
|
||||
ECKey jwk = new ECKey.Builder(Curve.P_256, publicKey)
|
||||
.privateKey(privateKey)
|
||||
.keyID("jeecg")
|
||||
.build();
|
||||
JWKSet jwkSet = new JWKSet(jwk);
|
||||
return new ImmutableJWKSet<>(jwkSet);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return NoOpPasswordEncoder.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置jwt解析器
|
||||
*/
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
||||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
||||
}
|
||||
|
||||
/**
|
||||
*配置token生成器
|
||||
*/
|
||||
@Bean
|
||||
OAuth2TokenGenerator<?> tokenGenerator() {
|
||||
JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource()));
|
||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
||||
return new DelegatingOAuth2TokenGenerator(
|
||||
new JeecgOAuth2AccessTokenGenerator(new NimbusJwtEncoder(jwkSource())),
|
||||
new OAuth2RefreshTokenGenerator()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
package org.jeecg.config.security.app;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jeecg.config.security.LoginType;
|
||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* APP模式认证转换器
|
||||
* @author EightMonth
|
||||
* @date 2024/1/1
|
||||
*/
|
||||
public class AppGrantAuthenticationConvert implements AuthenticationConverter {
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
|
||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
||||
if (!LoginType.APP.equals(grantType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
//从request中提取请求参数,然后存入MultiValueMap<String, String>
|
||||
MultiValueMap<String, String> parameters = getParameters(request);
|
||||
|
||||
// username (REQUIRED)
|
||||
String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
|
||||
if (!StringUtils.hasText(username) ||
|
||||
parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {
|
||||
throw new OAuth2AuthenticationException("无效请求,用户名不能为空!");
|
||||
}
|
||||
String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
|
||||
if (!StringUtils.hasText(password) ||
|
||||
parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) {
|
||||
throw new OAuth2AuthenticationException("无效请求,密码不能为空!");
|
||||
}
|
||||
|
||||
//收集要传入PasswordGrantAuthenticationToken构造方法的参数,
|
||||
//该参数接下来在PasswordGrantAuthenticationProvider中使用
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
//遍历从request中提取的参数,排除掉grant_type、client_id、code等字段参数,其他参数收集到additionalParameters中
|
||||
parameters.forEach((key, value) -> {
|
||||
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
|
||||
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
|
||||
!key.equals(OAuth2ParameterNames.CODE)) {
|
||||
additionalParameters.put(key, value.get(0));
|
||||
}
|
||||
});
|
||||
|
||||
//返回自定义的PasswordGrantAuthenticationToken对象
|
||||
return new PasswordGrantAuthenticationToken(clientPrincipal, additionalParameters);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*从request中提取请求参数,然后存入MultiValueMap<String, String>
|
||||
*/
|
||||
private static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
|
||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
|
||||
parameterMap.forEach((key, values) -> {
|
||||
if (values.length > 0) {
|
||||
for (String value : values) {
|
||||
parameters.add(key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,318 @@
|
||||
package org.jeecg.config.security.app;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.constant.CacheConstant;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.exception.JeecgCaptchaException;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.system.vo.SysDepartModel;
|
||||
import org.jeecg.common.util.Md5Util;
|
||||
import org.jeecg.common.util.PasswordUtil;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationToken;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.*;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* APP模式认证处理器,负责处理该认证模式下的核心逻辑
|
||||
* @author EightMonth
|
||||
* @date 2024/1/1
|
||||
*/
|
||||
@Slf4j
|
||||
public class AppGrantAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
||||
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||
@Autowired
|
||||
private CommonAPI commonAPI;
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
@Autowired
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
@Autowired
|
||||
private BaseCommonService baseCommonService;
|
||||
|
||||
public AppGrantAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
|
||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
|
||||
this.authorizationService = authorizationService;
|
||||
this.tokenGenerator = tokenGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
AppGrantAuthenticationToken appGrantAuthenticationToken = (AppGrantAuthenticationToken) authentication;
|
||||
Map<String, Object> additionalParameter = appGrantAuthenticationToken.getAdditionalParameters();
|
||||
|
||||
// 授权类型
|
||||
AuthorizationGrantType authorizationGrantType = appGrantAuthenticationToken.getGrantType();
|
||||
// 用户名
|
||||
String username = (String) additionalParameter.get(OAuth2ParameterNames.USERNAME);
|
||||
// 密码
|
||||
String password = (String) additionalParameter.get(OAuth2ParameterNames.PASSWORD);
|
||||
//请求参数权限范围
|
||||
String requestScopesStr = (String)additionalParameter.getOrDefault(OAuth2ParameterNames.SCOPE, "*");
|
||||
//请求参数权限范围专场集合
|
||||
Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet());
|
||||
// 验证码
|
||||
String captcha = (String) additionalParameter.get("captcha");
|
||||
String checkKey = (String) additionalParameter.get("checkKey");
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(appGrantAuthenticationToken);
|
||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||
|
||||
// 检查登录失败次数
|
||||
if(isLoginFailOvertimes(username)){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "该用户登录失败次数过多,请于10分钟后再次登录!");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
if(captcha==null){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "验证码无效");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
String lowerCaseCaptcha = captcha.toLowerCase();
|
||||
// 加入密钥作为混淆,避免简单的拼接,被外部利用,用户自定义该密钥即可
|
||||
String origin = lowerCaseCaptcha+checkKey+jeecgBaseConfig.getSignatureSecret();
|
||||
String realKey = Md5Util.md5Encode(origin, "utf-8");
|
||||
Object checkCode = redisUtil.get(realKey);
|
||||
//当进入登录页时,有一定几率出现验证码错误 #1714
|
||||
if(checkCode==null || !checkCode.toString().equals(lowerCaseCaptcha)) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "验证码错误");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "非法登录");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
// 通过用户名获取用户信息
|
||||
LoginUser loginUser = commonAPI.getUserByName(username);
|
||||
//update-begin---author:eightmonth ---date:2024-04-30 for:【6168】master分支切sas分支登录发生错误-----------
|
||||
if (Objects.isNull(loginUser) || !StringUtils.hasText(loginUser.getSalt())) {
|
||||
redisUtil.del(CacheConstant.SYS_USERS_CACHE+"::"+username);
|
||||
loginUser = commonAPI.getUserByName(username);
|
||||
}
|
||||
//update-end---author:eightmonth ---date::2024-04-30 for:【6168】master分支切sas分支登录发生错误--------------
|
||||
// 检查用户可行性
|
||||
checkUserIsEffective(loginUser);
|
||||
|
||||
// 不使用spring security passwordEncoder针对密码进行匹配,使用自有加密匹配,针对 spring security使用noop传输
|
||||
password = PasswordUtil.encrypt(username, password, loginUser.getSalt());
|
||||
if (!password.equals(loginUser.getPassword())) {
|
||||
addLoginFailOvertimes(username);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "用户名或密码不正确");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(loginUser,clientPrincipal,new ArrayList<>());
|
||||
|
||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.principal(usernamePasswordAuthenticationToken)
|
||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
||||
.authorizationGrantType(authorizationGrantType)
|
||||
.authorizedScopes(requestScopeSet)
|
||||
.authorizationGrant(appGrantAuthenticationToken);
|
||||
|
||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName(clientPrincipal.getName())
|
||||
.authorizedScopes(requestScopeSet)
|
||||
.attribute(Principal.class.getName(), username)
|
||||
.authorizationGrantType(authorizationGrantType);
|
||||
|
||||
|
||||
// ----- Access token -----
|
||||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (generatedAccessToken == null) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "无法生成访问token,请联系管理系。");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
||||
authorizationBuilder.token(accessToken, (metadata) -> {
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
|
||||
});
|
||||
} else {
|
||||
authorizationBuilder.accessToken(accessToken);
|
||||
}
|
||||
|
||||
// ----- Refresh token -----
|
||||
OAuth2RefreshToken refreshToken = null;
|
||||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
||||
// 不向公共客户端颁发刷新令牌
|
||||
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
|
||||
|
||||
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
|
||||
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "无法生成刷新token,请联系管理员。");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
|
||||
authorizationBuilder.refreshToken(refreshToken);
|
||||
}
|
||||
|
||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
||||
|
||||
// 保存认证信息至redis
|
||||
authorizationService.save(authorization);
|
||||
|
||||
// 登录成功,删除redis中的验证码
|
||||
redisUtil.del(realKey);
|
||||
redisUtil.del(CommonConstant.LOGIN_FAIL + username);
|
||||
baseCommonService.addLog("用户名: " + username + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
|
||||
|
||||
JSONObject addition = new JSONObject(new LinkedHashMap<>());
|
||||
addition.put("token", accessToken.getTokenValue());
|
||||
// 设置租户
|
||||
JSONObject jsonObject = commonAPI.setLoginTenant(username);
|
||||
addition.putAll(jsonObject.getInnerMap());
|
||||
|
||||
// 设置登录用户信息
|
||||
addition.put("userInfo", loginUser);
|
||||
addition.put("sysAllDictItems", commonAPI.queryAllDictItems());
|
||||
|
||||
List<SysDepartModel> departs = commonAPI.queryUserDeparts(loginUser.getId());
|
||||
addition.put("departs", departs);
|
||||
if (departs == null || departs.size() == 0) {
|
||||
addition.put("multi_depart", 0);
|
||||
} else if (departs.size() == 1) {
|
||||
commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
|
||||
addition.put("multi_depart", 1);
|
||||
} else {
|
||||
//查询当前是否有登录部门
|
||||
if(oConvertUtils.isEmpty(loginUser.getOrgCode())){
|
||||
commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
|
||||
}
|
||||
addition.put("multi_depart", 2);
|
||||
}
|
||||
|
||||
// 兼容原有shiro登录结果处理
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("result", addition);
|
||||
map.put("code", 200);
|
||||
map.put("success", true);
|
||||
map.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
// 返回access_token、refresh_token以及其它信息给到前端
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return AppGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
||||
}
|
||||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
||||
return clientPrincipal;
|
||||
}
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录失败超出次数5 返回true
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
private boolean isLoginFailOvertimes(String username){
|
||||
String key = CommonConstant.LOGIN_FAIL + username;
|
||||
Object failTime = redisUtil.get(key);
|
||||
if(failTime!=null){
|
||||
Integer val = Integer.parseInt(failTime.toString());
|
||||
if(val>5){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录失败次数
|
||||
* @param username
|
||||
*/
|
||||
private void addLoginFailOvertimes(String username){
|
||||
String key = CommonConstant.LOGIN_FAIL + username;
|
||||
Object failTime = redisUtil.get(key);
|
||||
Integer val = 0;
|
||||
if(failTime!=null){
|
||||
val = Integer.parseInt(failTime.toString());
|
||||
}
|
||||
// 10分钟
|
||||
redisUtil.set(key, ++val, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验用户是否有效
|
||||
*/
|
||||
private void checkUserIsEffective(LoginUser loginUser) {
|
||||
//情况1:根据用户信息查询,该用户不存在
|
||||
if (Objects.isNull(loginUser)) {
|
||||
baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户不存在,请注册");
|
||||
}
|
||||
//情况2:根据用户信息查询,该用户已注销
|
||||
//update-begin---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
||||
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
|
||||
//update-end---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户已注销");
|
||||
}
|
||||
//情况3:根据用户信息查询,该用户已冻结
|
||||
if (CommonConstant.USER_FREEZE.equals(loginUser.getStatus())) {
|
||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户已冻结");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package org.jeecg.config.security.app;
|
||||
|
||||
import org.jeecg.config.security.LoginType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* APP模式认证专用token类型,方法spring authorization server进行认证流转,配合convert使用
|
||||
* @author EightMonth
|
||||
* @date 2024/1/1
|
||||
*/
|
||||
public class AppGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
|
||||
|
||||
public AppGrantAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) {
|
||||
super(new AuthorizationGrantType(LoginType.APP), clientPrincipal, additionalParameters);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
package org.jeecg.config.security.password;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.config.security.LoginType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 密码模式认证转换器
|
||||
* @author EightMonth
|
||||
* @date 2024/1/1
|
||||
*/
|
||||
public class PasswordGrantAuthenticationConvert implements AuthenticationConverter {
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
|
||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
||||
if (!LoginType.PASSWORD.equals(grantType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
//从request中提取请求参数,然后存入MultiValueMap<String, String>
|
||||
MultiValueMap<String, String> parameters = getParameters(request);
|
||||
|
||||
// username (REQUIRED)
|
||||
String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
|
||||
if (!StringUtils.hasText(username) ||
|
||||
parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {
|
||||
throw new OAuth2AuthenticationException("无效请求,用户名不能为空!");
|
||||
}
|
||||
String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
|
||||
if (!StringUtils.hasText(password) ||
|
||||
parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) {
|
||||
throw new OAuth2AuthenticationException("无效请求,密码不能为空!");
|
||||
}
|
||||
|
||||
//收集要传入PasswordGrantAuthenticationToken构造方法的参数,
|
||||
//该参数接下来在PasswordGrantAuthenticationProvider中使用
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
//遍历从request中提取的参数,排除掉grant_type、client_id、code等字段参数,其他参数收集到additionalParameters中
|
||||
parameters.forEach((key, value) -> {
|
||||
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
|
||||
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
|
||||
!key.equals(OAuth2ParameterNames.CODE)) {
|
||||
additionalParameters.put(key, value.get(0));
|
||||
}
|
||||
});
|
||||
|
||||
//返回自定义的PasswordGrantAuthenticationToken对象
|
||||
return new PasswordGrantAuthenticationToken(clientPrincipal, additionalParameters);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*从request中提取请求参数,然后存入MultiValueMap<String, String>
|
||||
*/
|
||||
private static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
|
||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
|
||||
parameterMap.forEach((key, values) -> {
|
||||
if (values.length > 0) {
|
||||
for (String value : values) {
|
||||
parameters.add(key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,317 @@
|
||||
package org.jeecg.config.security.password;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.constant.CacheConstant;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.exception.JeecgCaptchaException;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.system.vo.SysDepartModel;
|
||||
import org.jeecg.common.util.Md5Util;
|
||||
import org.jeecg.common.util.PasswordUtil;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.oauth2.core.*;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 密码模式认证处理器,负责处理该认证模式下的核心逻辑
|
||||
* @author EightMonth
|
||||
* @date 2024/1/1
|
||||
*/
|
||||
@Slf4j
|
||||
public class PasswordGrantAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
||||
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||
@Autowired
|
||||
private CommonAPI commonAPI;
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
@Autowired
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
@Autowired
|
||||
private BaseCommonService baseCommonService;
|
||||
|
||||
public PasswordGrantAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
|
||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
|
||||
this.authorizationService = authorizationService;
|
||||
this.tokenGenerator = tokenGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
PasswordGrantAuthenticationToken passwordGrantAuthenticationToken = (PasswordGrantAuthenticationToken) authentication;
|
||||
Map<String, Object> additionalParameter = passwordGrantAuthenticationToken.getAdditionalParameters();
|
||||
|
||||
// 授权类型
|
||||
AuthorizationGrantType authorizationGrantType = passwordGrantAuthenticationToken.getGrantType();
|
||||
// 用户名
|
||||
String username = (String) additionalParameter.get(OAuth2ParameterNames.USERNAME);
|
||||
// 密码
|
||||
String password = (String) additionalParameter.get(OAuth2ParameterNames.PASSWORD);
|
||||
//请求参数权限范围
|
||||
String requestScopesStr = (String)additionalParameter.getOrDefault(OAuth2ParameterNames.SCOPE, "*");
|
||||
//请求参数权限范围专场集合
|
||||
Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet());
|
||||
// 验证码
|
||||
String captcha = (String) additionalParameter.get("captcha");
|
||||
String checkKey = (String) additionalParameter.get("checkKey");
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken);
|
||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||
|
||||
// 检查登录失败次数
|
||||
if(isLoginFailOvertimes(username)){
|
||||
throw new JeecgBootException("该用户登录失败次数过多,请于10分钟后再次登录!");
|
||||
}
|
||||
|
||||
if(captcha==null){
|
||||
throw new JeecgBootException("验证码无效");
|
||||
}
|
||||
String lowerCaseCaptcha = captcha.toLowerCase();
|
||||
// 加入密钥作为混淆,避免简单的拼接,被外部利用,用户自定义该密钥即可
|
||||
String origin = lowerCaseCaptcha+checkKey+jeecgBaseConfig.getSignatureSecret();
|
||||
String realKey = Md5Util.md5Encode(origin, "utf-8");
|
||||
Object checkCode = redisUtil.get(realKey);
|
||||
//当进入登录页时,有一定几率出现验证码错误 #1714
|
||||
if(checkCode==null || !checkCode.toString().equals(lowerCaseCaptcha)) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "验证码错误");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "非法登录");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
// 通过用户名获取用户信息
|
||||
LoginUser loginUser = commonAPI.getUserByName(username);
|
||||
//update-begin---author:eightmonth ---date:2024-04-30 for:【6168】master分支切sas分支登录发生错误-----------
|
||||
if (Objects.isNull(loginUser) || !StringUtils.hasText(loginUser.getSalt())) {
|
||||
redisUtil.del(CacheConstant.SYS_USERS_CACHE+"::"+username);
|
||||
loginUser = commonAPI.getUserByName(username);
|
||||
}
|
||||
//update-end---author:eightmonth ---date::2024-04-30 for:【6168】master分支切sas分支登录发生错误--------------
|
||||
// 检查用户可行性
|
||||
checkUserIsEffective(loginUser);
|
||||
|
||||
// 不使用spring security passwordEncoder针对密码进行匹配,使用自有加密匹配,针对 spring security使用noop传输
|
||||
password = PasswordUtil.encrypt(username, password, loginUser.getSalt());
|
||||
if (!password.equals(loginUser.getPassword())) {
|
||||
addLoginFailOvertimes(username);
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "用户名或密码不正确");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(loginUser,clientPrincipal,new ArrayList<>());
|
||||
|
||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.principal(usernamePasswordAuthenticationToken)
|
||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
||||
.authorizationGrantType(authorizationGrantType)
|
||||
.authorizedScopes(requestScopeSet)
|
||||
.authorizationGrant(passwordGrantAuthenticationToken);
|
||||
|
||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName(clientPrincipal.getName())
|
||||
.authorizedScopes(requestScopeSet)
|
||||
.attribute(Principal.class.getName(), username)
|
||||
.authorizationGrantType(authorizationGrantType);
|
||||
|
||||
|
||||
// ----- Access token -----
|
||||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (generatedAccessToken == null) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "无法生成访问token,请联系管理系。");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
||||
authorizationBuilder.token(accessToken, (metadata) -> {
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
|
||||
});
|
||||
} else {
|
||||
authorizationBuilder.accessToken(accessToken);
|
||||
}
|
||||
|
||||
// ----- Refresh token -----
|
||||
OAuth2RefreshToken refreshToken = null;
|
||||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
||||
// 不向公共客户端颁发刷新令牌
|
||||
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
|
||||
|
||||
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
|
||||
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "无法生成访问token,请联系管理系。");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
|
||||
authorizationBuilder.refreshToken(refreshToken);
|
||||
}
|
||||
|
||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
||||
|
||||
// 保存认证信息至redis
|
||||
authorizationService.save(authorization);
|
||||
|
||||
// 登录成功,删除redis中的验证码
|
||||
redisUtil.del(realKey);
|
||||
redisUtil.del(CommonConstant.LOGIN_FAIL + username);
|
||||
baseCommonService.addLog("用户名: " + username + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
|
||||
|
||||
JSONObject addition = new JSONObject(new LinkedHashMap<>());
|
||||
addition.put("token", accessToken.getTokenValue());
|
||||
|
||||
// 设置租户
|
||||
JSONObject jsonObject = commonAPI.setLoginTenant(username);
|
||||
addition.putAll(jsonObject.getInnerMap());
|
||||
|
||||
// 设置登录用户信息
|
||||
addition.put("userInfo", loginUser);
|
||||
addition.put("sysAllDictItems", commonAPI.queryAllDictItems());
|
||||
|
||||
List<SysDepartModel> departs = commonAPI.queryUserDeparts(loginUser.getId());
|
||||
addition.put("departs", departs);
|
||||
if (departs == null || departs.size() == 0) {
|
||||
addition.put("multi_depart", 0);
|
||||
} else if (departs.size() == 1) {
|
||||
commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
|
||||
addition.put("multi_depart", 1);
|
||||
} else {
|
||||
//查询当前是否有登录部门
|
||||
if(oConvertUtils.isEmpty(loginUser.getOrgCode())){
|
||||
commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
|
||||
}
|
||||
addition.put("multi_depart", 2);
|
||||
}
|
||||
|
||||
// 兼容原有shiro登录结果处理
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("result", addition);
|
||||
map.put("code", 200);
|
||||
map.put("success", true);
|
||||
map.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
// 返回access_token、refresh_token以及其它信息给到前端
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return PasswordGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
||||
}
|
||||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
||||
return clientPrincipal;
|
||||
}
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录失败超出次数5 返回true
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
private boolean isLoginFailOvertimes(String username){
|
||||
String key = CommonConstant.LOGIN_FAIL + username;
|
||||
Object failTime = redisUtil.get(key);
|
||||
if(failTime!=null){
|
||||
Integer val = Integer.parseInt(failTime.toString());
|
||||
if(val>5){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录失败次数
|
||||
* @param username
|
||||
*/
|
||||
private void addLoginFailOvertimes(String username){
|
||||
String key = CommonConstant.LOGIN_FAIL + username;
|
||||
Object failTime = redisUtil.get(key);
|
||||
Integer val = 0;
|
||||
if(failTime!=null){
|
||||
val = Integer.parseInt(failTime.toString());
|
||||
}
|
||||
// 10分钟
|
||||
redisUtil.set(key, ++val, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验用户是否有效
|
||||
*/
|
||||
private void checkUserIsEffective(LoginUser loginUser) {
|
||||
//情况1:根据用户信息查询,该用户不存在
|
||||
if (Objects.isNull(loginUser)) {
|
||||
baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户不存在,请注册");
|
||||
}
|
||||
//情况2:根据用户信息查询,该用户已注销
|
||||
//update-begin---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
||||
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
|
||||
//update-end---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户已注销");
|
||||
}
|
||||
//情况3:根据用户信息查询,该用户已冻结
|
||||
if (CommonConstant.USER_FREEZE.equals(loginUser.getStatus())) {
|
||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户已冻结");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package org.jeecg.config.security.password;
|
||||
|
||||
import org.jeecg.config.security.LoginType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 密码模式认证专用token类型,方法spring authorization server进行认证流转,配合convert使用
|
||||
* @author EightMonth
|
||||
* @date 2024/1/1
|
||||
*/
|
||||
public class PasswordGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
|
||||
|
||||
public PasswordGrantAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) {
|
||||
super(new AuthorizationGrantType(LoginType.PASSWORD), clientPrincipal, additionalParameters);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package org.jeecg.config.security.phone;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.jeecg.config.security.LoginType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 手机号模式认证转换器
|
||||
* @author EightMonth
|
||||
* @date 2024/1/1
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class PhoneGrantAuthenticationConvert implements AuthenticationConverter {
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
|
||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
||||
if (!LoginType.PHONE.equals(grantType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
//从request中提取请求参数,然后存入MultiValueMap<String, String>
|
||||
MultiValueMap<String, String> parameters = getParameters(request);
|
||||
|
||||
// 验证码
|
||||
String captcha = parameters.getFirst("captcha");
|
||||
if (!StringUtils.hasText(captcha)) {
|
||||
throw new OAuth2AuthenticationException("无效请求,验证码不能为空!");
|
||||
}
|
||||
|
||||
//收集要传入PhoneGrantAuthenticationToken构造方法的参数,
|
||||
//该参数接下来在PhoneGrantAuthenticationProvider中使用
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
//遍历从request中提取的参数,排除掉grant_type、client_id、code等字段参数,其他参数收集到additionalParameters中
|
||||
parameters.forEach((key, value) -> {
|
||||
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
|
||||
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
|
||||
!key.equals(OAuth2ParameterNames.CODE)) {
|
||||
additionalParameters.put(key, value.get(0));
|
||||
}
|
||||
});
|
||||
|
||||
//返回自定义的PhoneGrantAuthenticationToken对象
|
||||
return new PhoneGrantAuthenticationToken(clientPrincipal, additionalParameters);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*从request中提取请求参数,然后存入MultiValueMap<String, String>
|
||||
*/
|
||||
private static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
|
||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
|
||||
parameterMap.forEach((key, values) -> {
|
||||
if (values.length > 0) {
|
||||
for (String value : values) {
|
||||
parameters.add(key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
return parameters;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,290 @@
|
||||
package org.jeecg.config.security.phone;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.exception.JeecgCaptchaException;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.system.vo.SysDepartModel;
|
||||
import org.jeecg.common.util.Md5Util;
|
||||
import org.jeecg.common.util.PasswordUtil;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationToken;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.*;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 手机号模式认证处理器,负责处理该认证模式下的核心逻辑
|
||||
* @author EightMonth
|
||||
* @date 2024/1/1
|
||||
*/
|
||||
@Slf4j
|
||||
public class PhoneGrantAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
||||
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||
@Autowired
|
||||
private CommonAPI commonAPI;
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
@Autowired
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
@Autowired
|
||||
private BaseCommonService baseCommonService;
|
||||
|
||||
public PhoneGrantAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
|
||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
|
||||
this.authorizationService = authorizationService;
|
||||
this.tokenGenerator = tokenGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
PhoneGrantAuthenticationToken phoneGrantAuthenticationToken = (PhoneGrantAuthenticationToken) authentication;
|
||||
Map<String, Object> additionalParameter = phoneGrantAuthenticationToken.getAdditionalParameters();
|
||||
|
||||
// 授权类型
|
||||
AuthorizationGrantType authorizationGrantType = phoneGrantAuthenticationToken.getGrantType();
|
||||
// 手机号
|
||||
String phone = (String) additionalParameter.get("mobile");
|
||||
|
||||
if(isLoginFailOvertimes(phone)){
|
||||
throw new JeecgBootException("该用户登录失败次数过多,请于10分钟后再次登录!");
|
||||
}
|
||||
|
||||
//请求参数权限范围
|
||||
String requestScopesStr = (String)additionalParameter.getOrDefault(OAuth2ParameterNames.SCOPE, "*");
|
||||
//请求参数权限范围专场集合
|
||||
Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet());
|
||||
// 验证码
|
||||
String captcha = (String) additionalParameter.get("captcha");
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(phoneGrantAuthenticationToken);
|
||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||
|
||||
// 通过手机号获取用户信息
|
||||
LoginUser loginUser = commonAPI.getUserByPhone(phone);
|
||||
// 检查用户可行性
|
||||
checkUserIsEffective(loginUser);
|
||||
|
||||
|
||||
String redisKey = CommonConstant.PHONE_REDIS_KEY_PRE+phone;
|
||||
Object code = redisUtil.get(redisKey);
|
||||
|
||||
if (!captcha.equals(code)) {
|
||||
//update-begin-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
|
||||
addLoginFailOvertimes(phone);
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "手机验证码错误");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
|
||||
}
|
||||
|
||||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "非法登录");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(loginUser,clientPrincipal,new ArrayList<>());
|
||||
|
||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.principal(usernamePasswordAuthenticationToken)
|
||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
||||
.authorizationGrantType(authorizationGrantType)
|
||||
.authorizedScopes(requestScopeSet)
|
||||
.authorizationGrant(phoneGrantAuthenticationToken);
|
||||
|
||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName(clientPrincipal.getName())
|
||||
.authorizedScopes(requestScopeSet)
|
||||
.attribute(Principal.class.getName(), loginUser.getUsername())
|
||||
.authorizationGrantType(authorizationGrantType);
|
||||
|
||||
|
||||
// ----- Access token -----
|
||||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (generatedAccessToken == null) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "无法生成刷新token,请联系管理员。");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
||||
authorizationBuilder.token(accessToken, (metadata) -> {
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
|
||||
});
|
||||
} else {
|
||||
authorizationBuilder.accessToken(accessToken);
|
||||
}
|
||||
|
||||
// ----- Refresh token -----
|
||||
OAuth2RefreshToken refreshToken = null;
|
||||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
||||
// 不向公共客户端颁发刷新令牌
|
||||
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
|
||||
|
||||
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
|
||||
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "无法生成刷新token,请联系管理员。");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
|
||||
authorizationBuilder.refreshToken(refreshToken);
|
||||
}
|
||||
|
||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
||||
|
||||
// 保存认证信息至redis
|
||||
authorizationService.save(authorization);
|
||||
|
||||
baseCommonService.addLog("用户名: " + loginUser.getUsername() + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
|
||||
|
||||
JSONObject addition = new JSONObject(new LinkedHashMap<>());
|
||||
addition.put("token", accessToken.getTokenValue());
|
||||
// 设置租户
|
||||
JSONObject jsonObject = commonAPI.setLoginTenant(loginUser.getUsername());
|
||||
addition.putAll(jsonObject.getInnerMap());
|
||||
|
||||
// 设置登录用户信息
|
||||
addition.put("userInfo", loginUser);
|
||||
addition.put("sysAllDictItems", commonAPI.queryAllDictItems());
|
||||
|
||||
List<SysDepartModel> departs = commonAPI.queryUserDeparts(loginUser.getId());
|
||||
addition.put("departs", departs);
|
||||
if (departs == null || departs.size() == 0) {
|
||||
addition.put("multi_depart", 0);
|
||||
} else if (departs.size() == 1) {
|
||||
commonAPI.updateUserDepart(loginUser.getUsername(), departs.get(0).getOrgCode(),null);
|
||||
addition.put("multi_depart", 1);
|
||||
} else {
|
||||
//查询当前是否有登录部门
|
||||
if(oConvertUtils.isEmpty(loginUser.getOrgCode())){
|
||||
commonAPI.updateUserDepart(loginUser.getUsername(), departs.get(0).getOrgCode(),null);
|
||||
}
|
||||
addition.put("multi_depart", 2);
|
||||
}
|
||||
|
||||
// 兼容原有shiro登录结果处理
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("result", addition);
|
||||
map.put("code", 200);
|
||||
map.put("success", true);
|
||||
map.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
// 返回access_token、refresh_token以及其它信息给到前端
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return PhoneGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
||||
}
|
||||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
||||
return clientPrincipal;
|
||||
}
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录失败超出次数5 返回true
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
private boolean isLoginFailOvertimes(String username){
|
||||
String key = CommonConstant.LOGIN_FAIL + username;
|
||||
Object failTime = redisUtil.get(key);
|
||||
if(failTime!=null){
|
||||
Integer val = Integer.parseInt(failTime.toString());
|
||||
if(val>5){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录失败次数
|
||||
* @param username
|
||||
*/
|
||||
private void addLoginFailOvertimes(String username){
|
||||
String key = CommonConstant.LOGIN_FAIL + username;
|
||||
Object failTime = redisUtil.get(key);
|
||||
Integer val = 0;
|
||||
if(failTime!=null){
|
||||
val = Integer.parseInt(failTime.toString());
|
||||
}
|
||||
// 10分钟
|
||||
redisUtil.set(key, ++val, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验用户是否有效
|
||||
*/
|
||||
private void checkUserIsEffective(LoginUser loginUser) {
|
||||
//情况1:根据用户信息查询,该用户不存在
|
||||
if (Objects.isNull(loginUser)) {
|
||||
baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户不存在,请注册");
|
||||
}
|
||||
//情况2:根据用户信息查询,该用户已注销
|
||||
//update-begin---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
||||
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
|
||||
//update-end---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户已注销");
|
||||
}
|
||||
//情况3:根据用户信息查询,该用户已冻结
|
||||
if (CommonConstant.USER_FREEZE.equals(loginUser.getStatus())) {
|
||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户已冻结");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package org.jeecg.config.security.phone;
|
||||
|
||||
import org.jeecg.config.security.LoginType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 手机号模式认证专用token类型,方法spring authorization server进行认证流转,配合convert使用
|
||||
* @author EightMonth
|
||||
* @date 2024/1/1
|
||||
*/
|
||||
public class PhoneGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
|
||||
|
||||
public PhoneGrantAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) {
|
||||
super(new AuthorizationGrantType(LoginType.PHONE), clientPrincipal, additionalParameters);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,188 @@
|
||||
package org.jeecg.config.security.self;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.exception.JeecgBoot401Exception;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.system.vo.SysDepartModel;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.*;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 自用生成token处理器,不对外开放,外部请求无法通过该方式生成token
|
||||
* @author eightmonth@qq.com
|
||||
* @date 2024/3/19 11:40
|
||||
*/
|
||||
@Component
|
||||
public class SelfAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
||||
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||
@Autowired
|
||||
private CommonAPI commonAPI;
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
@Autowired
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
@Autowired
|
||||
private BaseCommonService baseCommonService;
|
||||
|
||||
public SelfAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
|
||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
|
||||
this.authorizationService = authorizationService;
|
||||
this.tokenGenerator = tokenGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
SelfAuthenticationToken passwordGrantAuthenticationToken = (SelfAuthenticationToken) authentication;
|
||||
Map<String, Object> additionalParameter = passwordGrantAuthenticationToken.getAdditionalParameters();
|
||||
|
||||
// 授权类型
|
||||
AuthorizationGrantType authorizationGrantType = passwordGrantAuthenticationToken.getGrantType();
|
||||
// 用户名
|
||||
String username = (String) additionalParameter.get(OAuth2ParameterNames.USERNAME);
|
||||
//请求参数权限范围
|
||||
String requestScopesStr = "*";
|
||||
//请求参数权限范围专场集合
|
||||
Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet());
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken);
|
||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||
|
||||
// 通过用户名获取用户信息
|
||||
// LoginUser loginUser = commonAPI.getUserByName(username);
|
||||
// 检查用户可行性
|
||||
// checkUserIsEffective(loginUser);
|
||||
|
||||
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(username,clientPrincipal,new ArrayList<>());
|
||||
|
||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.principal(usernamePasswordAuthenticationToken)
|
||||
.authorizationGrantType(authorizationGrantType)
|
||||
.authorizedScopes(requestScopeSet)
|
||||
.authorizationGrant(passwordGrantAuthenticationToken);
|
||||
|
||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName(clientPrincipal.getName())
|
||||
.authorizedScopes(requestScopeSet)
|
||||
.attribute(Principal.class.getName(), username)
|
||||
.authorizationGrantType(authorizationGrantType);
|
||||
|
||||
|
||||
// ----- Access token -----
|
||||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (generatedAccessToken == null) {
|
||||
throw new JeecgBoot401Exception("无法生成刷新token,请联系管理员。");
|
||||
}
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
||||
authorizationBuilder.token(accessToken, (metadata) -> {
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
|
||||
});
|
||||
} else {
|
||||
authorizationBuilder.accessToken(accessToken);
|
||||
}
|
||||
|
||||
// ----- Refresh token -----
|
||||
OAuth2RefreshToken refreshToken = null;
|
||||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
||||
// 不向公共客户端颁发刷新令牌
|
||||
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
|
||||
|
||||
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
|
||||
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "无法生成刷新token,请联系管理员。");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
|
||||
authorizationBuilder.refreshToken(refreshToken);
|
||||
}
|
||||
|
||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
||||
|
||||
// 保存认证信息至redis
|
||||
authorizationService.save(authorization);
|
||||
|
||||
// 返回access_token、refresh_token以及其它信息给到前端
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return SelfAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
||||
}
|
||||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
||||
return clientPrincipal;
|
||||
}
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验用户是否有效
|
||||
*/
|
||||
private void checkUserIsEffective(LoginUser loginUser) {
|
||||
//情况1:根据用户信息查询,该用户不存在
|
||||
if (Objects.isNull(loginUser)) {
|
||||
baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户不存在,请注册");
|
||||
}
|
||||
//情况2:根据用户信息查询,该用户已注销
|
||||
//update-begin---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
||||
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
|
||||
//update-end---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户已注销");
|
||||
}
|
||||
//情况3:根据用户信息查询,该用户已冻结
|
||||
if (CommonConstant.USER_FREEZE.equals(loginUser.getStatus())) {
|
||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户已冻结");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package org.jeecg.config.security.self;
|
||||
|
||||
import org.jeecg.config.security.LoginType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 自用生成token,不支持对外请求,仅为程序内部生成token
|
||||
* @author eightmonth
|
||||
* @date 2024/3/19 11:37
|
||||
*/
|
||||
public class SelfAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
|
||||
public SelfAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) {
|
||||
super(new AuthorizationGrantType(LoginType.SELF), clientPrincipal, additionalParameters);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
package org.jeecg.config.security.social;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.jeecg.config.security.LoginType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 社交模式认证转换器,配合github、企业微信、钉钉、微信登录使用
|
||||
* @author EightMonth
|
||||
* @date 2024/1/1
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class SocialGrantAuthenticationConvert implements AuthenticationConverter {
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
|
||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
||||
if (!LoginType.SOCIAL.equals(grantType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
//从request中提取请求参数,然后存入MultiValueMap<String, String>
|
||||
MultiValueMap<String, String> parameters = getParameters(request);
|
||||
|
||||
String token = parameters.getFirst("token");
|
||||
if (!StringUtils.hasText(token)) {
|
||||
throw new OAuth2AuthenticationException("无效请求,三方token不能为空!");
|
||||
}
|
||||
|
||||
String source = parameters.getFirst("thirdType");
|
||||
if (!StringUtils.hasText(source)) {
|
||||
throw new OAuth2AuthenticationException("无效请求,三方来源不能为空!");
|
||||
}
|
||||
|
||||
//收集要传入PhoneGrantAuthenticationToken构造方法的参数,
|
||||
//该参数接下来在PhoneGrantAuthenticationProvider中使用
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
//遍历从request中提取的参数,排除掉grant_type、client_id、code等字段参数,其他参数收集到additionalParameters中
|
||||
parameters.forEach((key, value) -> {
|
||||
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
|
||||
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
|
||||
!key.equals(OAuth2ParameterNames.CODE)) {
|
||||
additionalParameters.put(key, value.get(0));
|
||||
}
|
||||
});
|
||||
|
||||
//返回自定义的PhoneGrantAuthenticationToken对象
|
||||
return new SocialGrantAuthenticationToken(clientPrincipal, additionalParameters);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*从request中提取请求参数,然后存入MultiValueMap<String, String>
|
||||
*/
|
||||
private static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
|
||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
|
||||
parameterMap.forEach((key, values) -> {
|
||||
if (values.length > 0) {
|
||||
for (String value : values) {
|
||||
parameters.add(key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
return parameters;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,276 @@
|
||||
package org.jeecg.config.security.social;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.system.vo.SysDepartModel;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationToken;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.*;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 社交模式认证处理器,负责处理该认证模式下的核心逻辑,配合github、企业微信、钉钉、微信登录使用
|
||||
* @author EightMonth
|
||||
* @date 2024/1/1
|
||||
*/
|
||||
@Slf4j
|
||||
public class SocialGrantAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
||||
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||
@Autowired
|
||||
private CommonAPI commonAPI;
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
@Autowired
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
@Autowired
|
||||
private BaseCommonService baseCommonService;
|
||||
|
||||
public SocialGrantAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
|
||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
|
||||
this.authorizationService = authorizationService;
|
||||
this.tokenGenerator = tokenGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
SocialGrantAuthenticationToken socialGrantAuthenticationToken = (SocialGrantAuthenticationToken) authentication;
|
||||
Map<String, Object> additionalParameter = socialGrantAuthenticationToken.getAdditionalParameters();
|
||||
|
||||
// 授权类型
|
||||
AuthorizationGrantType authorizationGrantType = socialGrantAuthenticationToken.getGrantType();
|
||||
// 三方token
|
||||
String token = (String) additionalParameter.get("token");
|
||||
// 三方来源
|
||||
String source = (String) additionalParameter.get("thirdType");
|
||||
|
||||
//请求参数权限范围
|
||||
String requestScopesStr = (String)additionalParameter.getOrDefault(OAuth2ParameterNames.SCOPE, "*");
|
||||
//请求参数权限范围专场集合
|
||||
Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet());
|
||||
|
||||
DecodedJWT jwt = JWT.decode(token);
|
||||
String username = jwt.getClaim("username").asString();
|
||||
|
||||
// 通过手机号获取用户信息
|
||||
LoginUser loginUser = commonAPI.getUserByName(username);
|
||||
// 检查用户可行性
|
||||
checkUserIsEffective(loginUser);
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(socialGrantAuthenticationToken);
|
||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||
|
||||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "非法登录");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
|
||||
}
|
||||
|
||||
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(loginUser,clientPrincipal,new ArrayList<>());
|
||||
|
||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.principal(usernamePasswordAuthenticationToken)
|
||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
||||
.authorizationGrantType(authorizationGrantType)
|
||||
.authorizedScopes(requestScopeSet)
|
||||
.authorizationGrant(socialGrantAuthenticationToken);
|
||||
|
||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName(clientPrincipal.getName())
|
||||
.authorizedScopes(requestScopeSet)
|
||||
.attribute(Principal.class.getName(), loginUser.getUsername())
|
||||
.authorizationGrantType(authorizationGrantType);
|
||||
|
||||
|
||||
// ----- Access token -----
|
||||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (generatedAccessToken == null) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "无法生成访问token,请联系管理系。");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
|
||||
}
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
||||
authorizationBuilder.token(accessToken, (metadata) -> {
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
|
||||
});
|
||||
} else {
|
||||
authorizationBuilder.accessToken(accessToken);
|
||||
}
|
||||
|
||||
// ----- Refresh token -----
|
||||
OAuth2RefreshToken refreshToken = null;
|
||||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
||||
// 不向公共客户端颁发刷新令牌
|
||||
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
|
||||
|
||||
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
|
||||
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("message", "无法生成刷新token,请联系管理员。");
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
||||
}
|
||||
|
||||
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
|
||||
authorizationBuilder.refreshToken(refreshToken);
|
||||
}
|
||||
|
||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
||||
|
||||
// 保存认证信息至redis
|
||||
authorizationService.save(authorization);
|
||||
|
||||
baseCommonService.addLog("用户名: " + loginUser.getUsername() + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
|
||||
|
||||
JSONObject addition = new JSONObject(new LinkedHashMap<>());
|
||||
addition.put("token", accessToken.getTokenValue());
|
||||
// 设置租户
|
||||
JSONObject jsonObject = commonAPI.setLoginTenant(loginUser.getUsername());
|
||||
addition.putAll(jsonObject.getInnerMap());
|
||||
|
||||
// 设置登录用户信息
|
||||
addition.put("userInfo", loginUser);
|
||||
addition.put("sysAllDictItems", commonAPI.queryAllDictItems());
|
||||
|
||||
List<SysDepartModel> departs = commonAPI.queryUserDeparts(loginUser.getId());
|
||||
addition.put("departs", departs);
|
||||
if (departs == null || departs.size() == 0) {
|
||||
addition.put("multi_depart", 0);
|
||||
} else if (departs.size() == 1) {
|
||||
commonAPI.updateUserDepart(loginUser.getUsername(), departs.get(0).getOrgCode(),null);
|
||||
addition.put("multi_depart", 1);
|
||||
} else {
|
||||
//查询当前是否有登录部门
|
||||
if(oConvertUtils.isEmpty(loginUser.getOrgCode())){
|
||||
commonAPI.updateUserDepart(loginUser.getUsername(), departs.get(0).getOrgCode(),null);
|
||||
}
|
||||
addition.put("multi_depart", 2);
|
||||
}
|
||||
|
||||
// 兼容原有shiro登录结果处理
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("result", addition);
|
||||
map.put("code", 200);
|
||||
map.put("success", true);
|
||||
map.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
|
||||
// 返回access_token、refresh_token以及其它信息给到前端
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return SocialGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
||||
}
|
||||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
||||
return clientPrincipal;
|
||||
}
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录失败超出次数5 返回true
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
private boolean isLoginFailOvertimes(String username){
|
||||
String key = CommonConstant.LOGIN_FAIL + username;
|
||||
Object failTime = redisUtil.get(key);
|
||||
if(failTime!=null){
|
||||
Integer val = Integer.parseInt(failTime.toString());
|
||||
if(val>5){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录失败次数
|
||||
* @param username
|
||||
*/
|
||||
private void addLoginFailOvertimes(String username){
|
||||
String key = CommonConstant.LOGIN_FAIL + username;
|
||||
Object failTime = redisUtil.get(key);
|
||||
Integer val = 0;
|
||||
if(failTime!=null){
|
||||
val = Integer.parseInt(failTime.toString());
|
||||
}
|
||||
// 10分钟
|
||||
redisUtil.set(key, ++val, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验用户是否有效
|
||||
*/
|
||||
private void checkUserIsEffective(LoginUser loginUser) {
|
||||
//情况1:根据用户信息查询,该用户不存在
|
||||
if (Objects.isNull(loginUser)) {
|
||||
baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户不存在,请注册");
|
||||
}
|
||||
//情况2:根据用户信息查询,该用户已注销
|
||||
//update-begin---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
||||
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
|
||||
//update-end---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户已注销");
|
||||
}
|
||||
//情况3:根据用户信息查询,该用户已冻结
|
||||
if (CommonConstant.USER_FREEZE.equals(loginUser.getStatus())) {
|
||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
|
||||
throw new JeecgBootException("该用户已冻结");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package org.jeecg.config.security.social;
|
||||
|
||||
import org.jeecg.config.security.LoginType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 社交模式认证专用token类型,方法spring authorization server进行认证流转,配合convert使用,配合github、企业微信、钉钉、微信登录使用
|
||||
* @author EightMonth
|
||||
* @date 2024/1/1
|
||||
*/
|
||||
public class SocialGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
|
||||
|
||||
public SocialGrantAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) {
|
||||
super(new AuthorizationGrantType(LoginType.SOCIAL), clientPrincipal, additionalParameters);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.jeecg.config.security.utils;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
/**
|
||||
* 认证信息工具类
|
||||
* @author EightMonth
|
||||
* @date 2024/1/10 17:03
|
||||
*/
|
||||
public class SecureUtil {
|
||||
|
||||
/**
|
||||
* 通过当前认证信息获取用户信息
|
||||
* @return
|
||||
*/
|
||||
public static LoginUser currentUser() {
|
||||
String name = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
return JSONObject.parseObject(name, LoginUser.class);
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
package org.jeecg.config.shiro;
|
||||
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
|
||||
/**
|
||||
* @Author Scott
|
||||
* @create 2018-07-12 15:19
|
||||
* @desc
|
||||
**/
|
||||
public class JwtToken implements AuthenticationToken {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String token;
|
||||
|
||||
public JwtToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@ -1,366 +0,0 @@
|
||||
package org.jeecg.config.shiro;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
||||
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
|
||||
import org.apache.shiro.mgt.DefaultSubjectDAO;
|
||||
import org.apache.shiro.mgt.SecurityManager;
|
||||
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
||||
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||
import org.crazycake.shiro.*;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.config.shiro.filters.CustomShiroFilterFactoryBean;
|
||||
import org.jeecg.config.shiro.filters.JwtFilter;
|
||||
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
import redis.clients.jedis.HostAndPort;
|
||||
import redis.clients.jedis.JedisCluster;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author: Scott
|
||||
* @date: 2018/2/7
|
||||
* @description: shiro 配置类
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class ShiroConfig {
|
||||
|
||||
@Resource
|
||||
private LettuceConnectionFactory lettuceConnectionFactory;
|
||||
@Autowired
|
||||
private Environment env;
|
||||
@Resource
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
@Autowired(required = false)
|
||||
private RedisProperties redisProperties;
|
||||
|
||||
/**
|
||||
* Filter Chain定义说明
|
||||
*
|
||||
* 1、一个URL可以配置多个Filter,使用逗号分隔
|
||||
* 2、当设置多个过滤器时,全部验证通过,才视为通过
|
||||
* 3、部分过滤器可指定参数,如perms,roles
|
||||
*/
|
||||
@Bean("shiroFilterFactoryBean")
|
||||
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
|
||||
CustomShiroFilterFactoryBean shiroFilterFactoryBean = new CustomShiroFilterFactoryBean();
|
||||
shiroFilterFactoryBean.setSecurityManager(securityManager);
|
||||
// 拦截器
|
||||
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
|
||||
|
||||
//支持yml方式,配置拦截排除
|
||||
if(jeecgBaseConfig!=null && jeecgBaseConfig.getShiro()!=null){
|
||||
String shiroExcludeUrls = jeecgBaseConfig.getShiro().getExcludeUrls();
|
||||
if(oConvertUtils.isNotEmpty(shiroExcludeUrls)){
|
||||
String[] permissionUrl = shiroExcludeUrls.split(",");
|
||||
for(String url : permissionUrl){
|
||||
filterChainDefinitionMap.put(url,"anon");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 配置不会被拦截的链接 顺序判断
|
||||
filterChainDefinitionMap.put("/sys/cas/client/validateLogin", "anon"); //cas验证登录
|
||||
filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除
|
||||
filterChainDefinitionMap.put("/sys/checkCaptcha", "anon"); //登录验证码接口排除
|
||||
filterChainDefinitionMap.put("/sys/smsCheckCaptcha", "anon"); //短信次数发送太多验证码排除
|
||||
filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
|
||||
filterChainDefinitionMap.put("/sys/mLogin", "anon"); //登录接口排除
|
||||
filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除
|
||||
filterChainDefinitionMap.put("/sys/thirdLogin/**", "anon"); //第三方登录
|
||||
filterChainDefinitionMap.put("/sys/getEncryptedString", "anon"); //获取加密串
|
||||
filterChainDefinitionMap.put("/sys/sms", "anon");//短信验证码
|
||||
filterChainDefinitionMap.put("/sys/phoneLogin", "anon");//手机登录
|
||||
filterChainDefinitionMap.put("/sys/user/checkOnlyUser", "anon");//校验用户是否存在
|
||||
filterChainDefinitionMap.put("/sys/user/register", "anon");//用户注册
|
||||
filterChainDefinitionMap.put("/sys/user/phoneVerification", "anon");//用户忘记密码验证手机号
|
||||
filterChainDefinitionMap.put("/sys/user/passwordChange", "anon");//用户更改密码
|
||||
filterChainDefinitionMap.put("/auth/2step-code", "anon");//登录验证码
|
||||
filterChainDefinitionMap.put("/sys/common/static/**", "anon");//图片预览 &下载文件不限制token
|
||||
filterChainDefinitionMap.put("/sys/common/pdf/**", "anon");//pdf预览
|
||||
|
||||
//filterChainDefinitionMap.put("/sys/common/view/**", "anon");//图片预览不限制token
|
||||
//filterChainDefinitionMap.put("/sys/common/download/**", "anon");//文件下载不限制token
|
||||
filterChainDefinitionMap.put("/generic/**", "anon");//pdf预览需要文件
|
||||
|
||||
filterChainDefinitionMap.put("/sys/getLoginQrcode/**", "anon"); //登录二维码
|
||||
filterChainDefinitionMap.put("/sys/getQrcodeToken/**", "anon"); //监听扫码
|
||||
filterChainDefinitionMap.put("/sys/checkAuth", "anon"); //授权接口排除
|
||||
|
||||
|
||||
//update-begin--Author:scott Date:20221116 for:排除静态资源后缀
|
||||
filterChainDefinitionMap.put("/", "anon");
|
||||
filterChainDefinitionMap.put("/doc.html", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.js", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.css", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.html", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.svg", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.pdf", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.jpg", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.png", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.gif", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.ico", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.ttf", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.woff", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.woff2", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.glb", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.wasm", "anon");
|
||||
//update-end--Author:scott Date:20221116 for:排除静态资源后缀
|
||||
|
||||
filterChainDefinitionMap.put("/druid/**", "anon");
|
||||
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
|
||||
filterChainDefinitionMap.put("/swagger**/**", "anon");
|
||||
filterChainDefinitionMap.put("/webjars/**", "anon");
|
||||
filterChainDefinitionMap.put("/v3/**", "anon");
|
||||
|
||||
// update-begin--Author:sunjianlei Date:20210510 for:排除消息通告查看详情页面(用于第三方APP)
|
||||
filterChainDefinitionMap.put("/sys/annountCement/show/**", "anon");
|
||||
// update-end--Author:sunjianlei Date:20210510 for:排除消息通告查看详情页面(用于第三方APP)
|
||||
|
||||
//积木报表排除
|
||||
filterChainDefinitionMap.put("/jmreport/**", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.js.map", "anon");
|
||||
filterChainDefinitionMap.put("/**/*.css.map", "anon");
|
||||
|
||||
//积木BI大屏和仪表盘排除
|
||||
filterChainDefinitionMap.put("/drag/view", "anon");
|
||||
filterChainDefinitionMap.put("/drag/page/queryById", "anon");
|
||||
filterChainDefinitionMap.put("/drag/page/addVisitsNumber", "anon");
|
||||
filterChainDefinitionMap.put("/drag/page/queryTemplateList", "anon");
|
||||
filterChainDefinitionMap.put("/drag/share/view/**", "anon");
|
||||
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getAllChartData", "anon");
|
||||
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getTotalData", "anon");
|
||||
filterChainDefinitionMap.put("/drag/mock/json/**", "anon");
|
||||
filterChainDefinitionMap.put("/jimubi/view", "anon");
|
||||
filterChainDefinitionMap.put("/jimubi/share/view/**", "anon");
|
||||
|
||||
//大屏模板例子
|
||||
filterChainDefinitionMap.put("/test/bigScreen/**", "anon");
|
||||
filterChainDefinitionMap.put("/bigscreen/template1/**", "anon");
|
||||
filterChainDefinitionMap.put("/bigscreen/template2/**", "anon");
|
||||
//filterChainDefinitionMap.put("/test/jeecgDemo/rabbitMqClientTest/**", "anon"); //MQ测试
|
||||
//filterChainDefinitionMap.put("/test/jeecgDemo/html", "anon"); //模板页面
|
||||
//filterChainDefinitionMap.put("/test/jeecgDemo/redis/**", "anon"); //redis测试
|
||||
|
||||
//websocket排除
|
||||
filterChainDefinitionMap.put("/websocket/**", "anon");//系统通知和公告
|
||||
filterChainDefinitionMap.put("/newsWebsocket/**", "anon");//CMS模块
|
||||
filterChainDefinitionMap.put("/vxeSocket/**", "anon");//JVxeTable无痕刷新示例
|
||||
|
||||
//性能监控——安全隐患泄露TOEKN(durid连接池也有)
|
||||
//filterChainDefinitionMap.put("/actuator/**", "anon");
|
||||
//测试模块排除
|
||||
filterChainDefinitionMap.put("/test/seata/**", "anon");
|
||||
|
||||
//错误路径排除
|
||||
filterChainDefinitionMap.put("/error", "anon");
|
||||
// 企业微信证书排除
|
||||
filterChainDefinitionMap.put("/WW_verify*", "anon");
|
||||
|
||||
filterChainDefinitionMap.put("/openapi/call/**", "anon");
|
||||
|
||||
// 添加自己的过滤器并且取名为jwt
|
||||
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
|
||||
//如果cloudServer为空 则说明是单体 需要加载跨域配置【微服务跨域切换】
|
||||
Object cloudServer = env.getProperty(CommonConstant.CLOUD_SERVER_KEY);
|
||||
filterMap.put("jwt", new JwtFilter(cloudServer==null));
|
||||
shiroFilterFactoryBean.setFilters(filterMap);
|
||||
// <!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
|
||||
filterChainDefinitionMap.put("/**", "jwt");
|
||||
|
||||
// 未授权界面返回JSON
|
||||
shiroFilterFactoryBean.setUnauthorizedUrl("/sys/common/403");
|
||||
shiroFilterFactoryBean.setLoginUrl("/sys/common/403");
|
||||
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
|
||||
return shiroFilterFactoryBean;
|
||||
}
|
||||
|
||||
//update-begin---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||
|
||||
/**
|
||||
* spring过滤装饰器 <br/>
|
||||
* 因为shiro的filter不支持异步请求,导致所有的异步请求都会报错. <br/>
|
||||
* 所以需要用spring的FilterRegistrationBean再代理一下shiro的filter.为他扩展异步支持. <br/>
|
||||
* 后续所有异步的接口都需要再这里增加registration.addUrlPatterns("/xxx/xxx");
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2024/12/3 19:49
|
||||
*/
|
||||
@Bean
|
||||
public FilterRegistrationBean shiroFilterRegistration() {
|
||||
FilterRegistrationBean registration = new FilterRegistrationBean();
|
||||
registration.setFilter(new DelegatingFilterProxy("shiroFilterFactoryBean"));
|
||||
registration.setEnabled(true);
|
||||
//update-begin---author:chenrui ---date:20241202 for:[issues/7491]运行时间好长,效率慢 ------------
|
||||
registration.addUrlPatterns("/test/ai/chat/send");
|
||||
//update-end---author:chenrui ---date:20241202 for:[issues/7491]运行时间好长,效率慢 ------------
|
||||
//支持异步
|
||||
registration.setAsyncSupported(true);
|
||||
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
|
||||
return registration;
|
||||
}
|
||||
//update-end---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||
|
||||
@Bean("securityManager")
|
||||
public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
|
||||
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
||||
securityManager.setRealm(myRealm);
|
||||
|
||||
/*
|
||||
* 关闭shiro自带的session,详情见文档
|
||||
* http://shiro.apache.org/session-management.html#SessionManagement-
|
||||
* StatelessApplications%28Sessionless%29
|
||||
*/
|
||||
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
|
||||
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
|
||||
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
|
||||
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
|
||||
securityManager.setSubjectDAO(subjectDAO);
|
||||
//自定义缓存实现,使用redis
|
||||
securityManager.setCacheManager(redisCacheManager());
|
||||
return securityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下面的代码是添加注解支持
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
@DependsOn("lifecycleBeanPostProcessor")
|
||||
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
|
||||
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
|
||||
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
|
||||
/**
|
||||
* 解决重复代理问题 github#994
|
||||
* 添加前缀判断 不匹配 任何Advisor
|
||||
*/
|
||||
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
|
||||
defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor");
|
||||
return defaultAdvisorAutoProxyCreator;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
|
||||
return new LifecycleBeanPostProcessor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
|
||||
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
|
||||
advisor.setSecurityManager(securityManager);
|
||||
return advisor;
|
||||
}
|
||||
|
||||
/**
|
||||
* cacheManager 缓存 redis实现
|
||||
* 使用的是shiro-redis开源插件
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public RedisCacheManager redisCacheManager() {
|
||||
log.info("===============(1)创建缓存管理器RedisCacheManager");
|
||||
RedisCacheManager redisCacheManager = new RedisCacheManager();
|
||||
redisCacheManager.setRedisManager(redisManager());
|
||||
//redis中针对不同用户缓存(此处的id需要对应user实体中的id字段,用于唯一标识)
|
||||
redisCacheManager.setPrincipalIdFieldName("id");
|
||||
//用户权限信息缓存时间
|
||||
redisCacheManager.setExpire(200000);
|
||||
return redisCacheManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* RedisConfig在项目starter项目中
|
||||
* jeecg-boot-starter-github\jeecg-boot-common\src\main\java\org\jeecg\common\modules\redis\config\RedisConfig.java
|
||||
*
|
||||
* 配置shiro redisManager
|
||||
* 使用的是shiro-redis开源插件
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public IRedisManager redisManager() {
|
||||
log.info("===============(2)创建RedisManager,连接Redis..");
|
||||
IRedisManager manager;
|
||||
// sentinel cluster redis(【issues/5569】shiro集成 redis 不支持 sentinel 方式部署的redis集群 #5569)
|
||||
if (Objects.nonNull(redisProperties)
|
||||
&& Objects.nonNull(redisProperties.getSentinel())
|
||||
&& !CollectionUtils.isEmpty(redisProperties.getSentinel().getNodes())) {
|
||||
RedisSentinelManager sentinelManager = new RedisSentinelManager();
|
||||
sentinelManager.setMasterName(redisProperties.getSentinel().getMaster());
|
||||
sentinelManager.setHost(String.join(",", redisProperties.getSentinel().getNodes()));
|
||||
sentinelManager.setPassword(redisProperties.getPassword());
|
||||
sentinelManager.setDatabase(redisProperties.getDatabase());
|
||||
|
||||
return sentinelManager;
|
||||
}
|
||||
|
||||
// redis 单机支持,在集群为空,或者集群无机器时候使用 add by jzyadmin@163.com
|
||||
if (lettuceConnectionFactory.getClusterConfiguration() == null || lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().isEmpty()) {
|
||||
RedisManager redisManager = new RedisManager();
|
||||
redisManager.setHost(lettuceConnectionFactory.getHostName() + ":" + lettuceConnectionFactory.getPort());
|
||||
//(lettuceConnectionFactory.getPort());
|
||||
redisManager.setDatabase(lettuceConnectionFactory.getDatabase());
|
||||
redisManager.setTimeout(0);
|
||||
if (!StringUtils.isEmpty(lettuceConnectionFactory.getPassword())) {
|
||||
redisManager.setPassword(lettuceConnectionFactory.getPassword());
|
||||
}
|
||||
manager = redisManager;
|
||||
}else{
|
||||
// redis集群支持,优先使用集群配置
|
||||
RedisClusterManager redisManager = new RedisClusterManager();
|
||||
Set<HostAndPort> portSet = new HashSet<>();
|
||||
lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().forEach(node -> portSet.add(new HostAndPort(node.getHost() , node.getPort())));
|
||||
//update-begin--Author:scott Date:20210531 for:修改集群模式下未设置redis密码的bug issues/I3QNIC
|
||||
if (oConvertUtils.isNotEmpty(lettuceConnectionFactory.getPassword())) {
|
||||
JedisCluster jedisCluster = new JedisCluster(portSet, 2000, 2000, 5,
|
||||
lettuceConnectionFactory.getPassword(), new GenericObjectPoolConfig());
|
||||
redisManager.setPassword(lettuceConnectionFactory.getPassword());
|
||||
redisManager.setJedisCluster(jedisCluster);
|
||||
} else {
|
||||
JedisCluster jedisCluster = new JedisCluster(portSet);
|
||||
redisManager.setJedisCluster(jedisCluster);
|
||||
}
|
||||
//update-end--Author:scott Date:20210531 for:修改集群模式下未设置redis密码的bug issues/I3QNIC
|
||||
manager = redisManager;
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
private List<String> rebuildUrl(String[] bases, String[] uris) {
|
||||
List<String> urls = new ArrayList<>();
|
||||
for (String base : bases) {
|
||||
for (String uri : uris) {
|
||||
urls.add(prefix(base)+prefix(uri));
|
||||
}
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
private String prefix(String seg) {
|
||||
return seg.startsWith("/") ? seg : "/"+seg;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,234 +0,0 @@
|
||||
package org.jeecg.config.shiro;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.authc.AuthenticationInfo;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
import org.apache.shiro.authc.SimpleAuthenticationInfo;
|
||||
import org.apache.shiro.authz.AuthorizationInfo;
|
||||
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||
import org.apache.shiro.realm.AuthorizingRealm;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.config.TenantContext;
|
||||
import org.jeecg.common.constant.CacheConstant;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.common.util.TokenUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @Description: 用户登录鉴权和获取用户授权
|
||||
* @Author: Scott
|
||||
* @Date: 2019-4-23 8:13
|
||||
* @Version: 1.1
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ShiroRealm extends AuthorizingRealm {
|
||||
@Lazy
|
||||
@Resource
|
||||
private CommonAPI commonApi;
|
||||
|
||||
@Lazy
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
/**
|
||||
* 必须重写此方法,不然Shiro会报错
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(AuthenticationToken token) {
|
||||
return token instanceof JwtToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
|
||||
* 触发检测用户权限时才会调用此方法,例如checkRole,checkPermission
|
||||
*
|
||||
* @param principals 身份信息
|
||||
* @return AuthorizationInfo 权限信息
|
||||
*/
|
||||
@Override
|
||||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
|
||||
log.debug("===============Shiro权限认证开始============ [ roles、permissions]==========");
|
||||
String username = null;
|
||||
String userId = null;
|
||||
if (principals != null) {
|
||||
LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
|
||||
username = sysUser.getUsername();
|
||||
userId = sysUser.getId();
|
||||
}
|
||||
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
||||
|
||||
// 设置用户拥有的角色集合,比如“admin,test”
|
||||
Set<String> roleSet = commonApi.queryUserRolesById(userId);
|
||||
//System.out.println(roleSet.toString());
|
||||
info.setRoles(roleSet);
|
||||
|
||||
// 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
|
||||
Set<String> permissionSet = commonApi.queryUserAuths(userId);
|
||||
info.addStringPermissions(permissionSet);
|
||||
//System.out.println(permissionSet);
|
||||
log.info("===============Shiro权限认证成功==============");
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户信息认证是在用户进行登录的时候进行验证(不存redis)
|
||||
* 也就是说验证用户输入的账号和密码是否正确,错误抛出异常
|
||||
*
|
||||
* @param auth 用户登录的账号密码信息
|
||||
* @return 返回封装了用户信息的 AuthenticationInfo 实例
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
@Override
|
||||
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
|
||||
log.debug("===============Shiro身份认证开始============doGetAuthenticationInfo==========");
|
||||
String token = (String) auth.getCredentials();
|
||||
if (token == null) {
|
||||
HttpServletRequest req = SpringContextUtils.getHttpServletRequest();
|
||||
log.info("————————身份认证失败——————————IP地址: "+ oConvertUtils.getIpAddrByRequest(req) +",URL:"+req.getRequestURI());
|
||||
throw new AuthenticationException("token为空!");
|
||||
}
|
||||
// 校验token有效性
|
||||
LoginUser loginUser = null;
|
||||
try {
|
||||
loginUser = this.checkUserTokenIsEffect(token);
|
||||
} catch (AuthenticationException e) {
|
||||
JwtUtil.responseError(SpringContextUtils.getHttpServletResponse(),401,e.getMessage());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
return new SimpleAuthenticationInfo(loginUser, token, getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验token的有效性
|
||||
*
|
||||
* @param token
|
||||
*/
|
||||
public LoginUser checkUserTokenIsEffect(String token) throws AuthenticationException {
|
||||
// 解密获得username,用于和数据库进行对比
|
||||
String username = JwtUtil.getUsername(token);
|
||||
if (username == null) {
|
||||
throw new AuthenticationException("token非法无效!");
|
||||
}
|
||||
|
||||
// 查询用户信息
|
||||
log.debug("———校验token是否有效————checkUserTokenIsEffect——————— "+ token);
|
||||
LoginUser loginUser = TokenUtils.getLoginUser(username, commonApi, redisUtil);
|
||||
//LoginUser loginUser = commonApi.getUserByName(username);
|
||||
if (loginUser == null) {
|
||||
throw new AuthenticationException("用户不存在!");
|
||||
}
|
||||
// 判断用户状态
|
||||
if (loginUser.getStatus() != 1) {
|
||||
throw new AuthenticationException("账号已被锁定,请联系管理员!");
|
||||
}
|
||||
// 校验token是否超时失效 & 或者账号密码是否错误
|
||||
if (!jwtTokenRefresh(token, username, loginUser.getPassword())) {
|
||||
throw new AuthenticationException(CommonConstant.TOKEN_IS_INVALID_MSG);
|
||||
}
|
||||
//update-begin-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
|
||||
String userTenantIds = loginUser.getRelTenantIds();
|
||||
if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL && oConvertUtils.isNotEmpty(userTenantIds)){
|
||||
String contextTenantId = TenantContext.getTenant();
|
||||
log.debug("登录租户:" + contextTenantId);
|
||||
log.debug("用户拥有那些租户:" + userTenantIds);
|
||||
//登录用户无租户,前端header中租户ID值为 0
|
||||
String str ="0";
|
||||
if(oConvertUtils.isNotEmpty(contextTenantId) && !str.equals(contextTenantId)){
|
||||
//update-begin-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
|
||||
String[] arr = userTenantIds.split(",");
|
||||
if(!oConvertUtils.isIn(contextTenantId, arr)){
|
||||
boolean isAuthorization = false;
|
||||
//========================================================================
|
||||
// 查询用户信息(如果租户不匹配从数据库中重新查询一次用户信息)
|
||||
String loginUserKey = CacheConstant.SYS_USERS_CACHE + "::" + username;
|
||||
redisUtil.del(loginUserKey);
|
||||
LoginUser loginUserFromDb = commonApi.getUserByName(username);
|
||||
if (oConvertUtils.isNotEmpty(loginUserFromDb.getRelTenantIds())) {
|
||||
String[] newArray = loginUserFromDb.getRelTenantIds().split(",");
|
||||
if (oConvertUtils.isIn(contextTenantId, newArray)) {
|
||||
isAuthorization = true;
|
||||
}
|
||||
}
|
||||
//========================================================================
|
||||
|
||||
//*********************************************
|
||||
if(!isAuthorization){
|
||||
log.info("租户异常——登录租户:" + contextTenantId);
|
||||
log.info("租户异常——用户拥有租户组:" + userTenantIds);
|
||||
throw new AuthenticationException("登录租户授权变更,请重新登陆!");
|
||||
}
|
||||
//*********************************************
|
||||
}
|
||||
//update-end-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
|
||||
}
|
||||
}
|
||||
//update-end-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* JWTToken刷新生命周期 (实现: 用户在线操作不掉线功能)
|
||||
* 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样),缓存有效期设置为Jwt有效时间的2倍
|
||||
* 2、当该用户再次请求时,通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
|
||||
* 3、当该用户这次请求jwt生成的token值已经超时,但该token对应cache中的k还是存在,则表示该用户一直在操作只是JWT的token失效了,程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值,该缓存生命周期重新计算
|
||||
* 4、当该用户这次请求jwt在生成的token值已经超时,并在cache中不存在对应的k,则表示该用户账户空闲超时,返回用户信息已失效,请重新登录。
|
||||
* 注意: 前端请求Header中设置Authorization保持不变,校验有效性以缓存中的token为准。
|
||||
* 用户过期时间 = Jwt有效时间 * 2。
|
||||
*
|
||||
* @param userName
|
||||
* @param passWord
|
||||
* @return
|
||||
*/
|
||||
public boolean jwtTokenRefresh(String token, String userName, String passWord) {
|
||||
String cacheToken = String.valueOf(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));
|
||||
if (oConvertUtils.isNotEmpty(cacheToken)) {
|
||||
// 校验token有效性
|
||||
if (!JwtUtil.verify(cacheToken, userName, passWord)) {
|
||||
String newAuthorization = JwtUtil.sign(userName, passWord);
|
||||
// 设置超时时间
|
||||
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
|
||||
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME *2 / 1000);
|
||||
log.debug("——————————用户在线操作,更新token保证不掉线—————————jwtTokenRefresh——————— "+ token);
|
||||
}
|
||||
//update-begin--Author:scott Date:20191005 for:解决每次请求,都重写redis中 token缓存问题
|
||||
// else {
|
||||
// // 设置超时时间
|
||||
// redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, cacheToken);
|
||||
// redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);
|
||||
// }
|
||||
//update-end--Author:scott Date:20191005 for:解决每次请求,都重写redis中 token缓存问题
|
||||
return true;
|
||||
}
|
||||
|
||||
//redis中不存在此TOEKN,说明token非法返回false
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前用户的权限认证缓存
|
||||
*
|
||||
* @param principals 权限信息
|
||||
*/
|
||||
@Override
|
||||
public void clearCache(PrincipalCollection principals) {
|
||||
super.clearCache(principals);
|
||||
//update-begin---author:scott ---date::2024-06-18 for:【TV360X-1320】分配权限必须退出重新登录才生效,造成很多用户困扰---
|
||||
super.clearCachedAuthorizationInfo(principals);
|
||||
//update-end---author:scott ---date::2024-06-18 for:【TV360X-1320】分配权限必须退出重新登录才生效,造成很多用户困扰---
|
||||
}
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
package org.jeecg.config.shiro.filters;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.apache.shiro.web.filter.InvalidRequestFilter;
|
||||
import org.apache.shiro.web.filter.mgt.DefaultFilter;
|
||||
import org.apache.shiro.web.filter.mgt.FilterChainManager;
|
||||
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
|
||||
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
|
||||
import org.apache.shiro.web.mgt.WebSecurityManager;
|
||||
import org.apache.shiro.web.servlet.AbstractShiroFilter;
|
||||
import org.apache.shiro.mgt.SecurityManager;
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 自定义ShiroFilterFactoryBean解决资源中文路径问题
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Slf4j
|
||||
public class CustomShiroFilterFactoryBean extends ShiroFilterFactoryBean {
|
||||
@Override
|
||||
public Class getObjectType() {
|
||||
return MySpringShiroFilter.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractShiroFilter createInstance() throws Exception {
|
||||
|
||||
SecurityManager securityManager = getSecurityManager();
|
||||
if (securityManager == null) {
|
||||
String msg = "SecurityManager property must be set.";
|
||||
throw new BeanInitializationException(msg);
|
||||
}
|
||||
|
||||
if (!(securityManager instanceof WebSecurityManager)) {
|
||||
String msg = "The security manager does not implement the WebSecurityManager interface.";
|
||||
throw new BeanInitializationException(msg);
|
||||
}
|
||||
|
||||
FilterChainManager manager = createFilterChainManager();
|
||||
//Expose the constructed FilterChainManager by first wrapping it in a
|
||||
// FilterChainResolver implementation. The AbstractShiroFilter implementations
|
||||
// do not know about FilterChainManagers - only resolvers:
|
||||
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
|
||||
chainResolver.setFilterChainManager(manager);
|
||||
|
||||
Map<String, Filter> filterMap = manager.getFilters();
|
||||
Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name());
|
||||
if (invalidRequestFilter instanceof InvalidRequestFilter) {
|
||||
//此处是关键,设置false跳过URL携带中文400,servletPath中文校验bug
|
||||
((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false);
|
||||
}
|
||||
//Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
|
||||
//FilterChainResolver. It doesn't matter that the instance is an anonymous inner class
|
||||
//here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
|
||||
//injection of the SecurityManager and FilterChainResolver:
|
||||
return new MySpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
|
||||
}
|
||||
|
||||
private static final class MySpringShiroFilter extends AbstractShiroFilter {
|
||||
protected MySpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
|
||||
if (webSecurityManager == null) {
|
||||
throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
|
||||
} else {
|
||||
this.setSecurityManager(webSecurityManager);
|
||||
if (resolver != null) {
|
||||
this.setFilterChainResolver(resolver);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,130 +0,0 @@
|
||||
package org.jeecg.config.shiro.filters;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
|
||||
import org.jeecg.common.config.TenantContext;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.shiro.JwtToken;
|
||||
import org.jeecg.config.shiro.ignore.InMemoryIgnoreAuth;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @Description: 鉴权登录拦截器
|
||||
* @Author: Scott
|
||||
* @Date: 2018/10/7
|
||||
**/
|
||||
@Slf4j
|
||||
public class JwtFilter extends BasicHttpAuthenticationFilter {
|
||||
|
||||
/**
|
||||
* 默认开启跨域设置(使用单体)
|
||||
* 微服务情况下,此属性设置为false
|
||||
*/
|
||||
private boolean allowOrigin = true;
|
||||
|
||||
public JwtFilter(){}
|
||||
public JwtFilter(boolean allowOrigin){
|
||||
this.allowOrigin = allowOrigin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行登录认证
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @param mappedValue
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
|
||||
try {
|
||||
// 判断当前路径是不是注解了@IngoreAuth路径,如果是,则放开验证
|
||||
if (InMemoryIgnoreAuth.contains(((HttpServletRequest) request).getServletPath())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
executeLogin(request, response);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
JwtUtil.responseError(response,401,CommonConstant.TOKEN_IS_INVALID_MSG);
|
||||
return false;
|
||||
//throw new AuthenticationException("Token失效,请重新登录", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
|
||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||
String token = httpServletRequest.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||
// update-begin--Author:lvdandan Date:20210105 for:JT-355 OA聊天添加token验证,获取token参数
|
||||
if (oConvertUtils.isEmpty(token)) {
|
||||
token = httpServletRequest.getParameter("token");
|
||||
}
|
||||
// update-end--Author:lvdandan Date:20210105 for:JT-355 OA聊天添加token验证,获取token参数
|
||||
|
||||
JwtToken jwtToken = new JwtToken(token);
|
||||
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
|
||||
getSubject(request, response).login(jwtToken);
|
||||
// 如果没有抛出异常则代表登入成功,返回true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对跨域提供支持
|
||||
*/
|
||||
@Override
|
||||
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
|
||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
|
||||
if(allowOrigin){
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, httpServletRequest.getHeader(HttpHeaders.ORIGIN));
|
||||
// 允许客户端请求方法
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,POST,OPTIONS,PUT,DELETE");
|
||||
// 允许客户端提交的Header
|
||||
String requestHeaders = httpServletRequest.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
|
||||
if (StringUtils.isNotEmpty(requestHeaders)) {
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders);
|
||||
}
|
||||
// 允许客户端携带凭证信息(是否允许发送Cookie)
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
}
|
||||
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
|
||||
if (RequestMethod.OPTIONS.name().equalsIgnoreCase(httpServletRequest.getMethod())) {
|
||||
httpServletResponse.setStatus(HttpStatus.OK.value());
|
||||
return false;
|
||||
}
|
||||
//update-begin-author:taoyan date:20200708 for:多租户用到
|
||||
String tenantId = httpServletRequest.getHeader(CommonConstant.TENANT_ID);
|
||||
TenantContext.setTenant(tenantId);
|
||||
//update-end-author:taoyan date:20200708 for:多租户用到
|
||||
|
||||
return super.preHandle(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* JwtFilter中ThreadLocal需要及时清除 #3634
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @param exception
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
|
||||
//log.info("------清空线程中多租户的ID={}------",TenantContext.getTenant());
|
||||
TenantContext.clear();
|
||||
}
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
package org.jeecg.config.shiro.filters;
|
||||
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.web.filter.AccessControlFilter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @Author Scott
|
||||
* @create 2019-02-01 15:56
|
||||
* @desc 鉴权请求URL访问权限拦截器
|
||||
*/
|
||||
@Slf4j
|
||||
public class ResourceCheckFilter extends AccessControlFilter {
|
||||
|
||||
private String errorUrl;
|
||||
|
||||
public String getErrorUrl() {
|
||||
return errorUrl;
|
||||
}
|
||||
|
||||
public void setErrorUrl(String errorUrl) {
|
||||
this.errorUrl = errorUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示是否允许访问 ,如果允许访问返回true,否则false;
|
||||
*
|
||||
* @param servletRequest
|
||||
* @param servletResponse
|
||||
* @param o 表示写在拦截器中括号里面的字符串 mappedValue 就是 [urls] 配置中拦截器参数部分
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
|
||||
Subject subject = getSubject(servletRequest, servletResponse);
|
||||
String url = getPathWithinApplication(servletRequest);
|
||||
log.info("当前用户正在访问的 url => " + url);
|
||||
return subject.isPermitted(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* onAccessDenied:表示当访问拒绝时是否已经处理了; 如果返回 true 表示需要继续处理; 如果返回 false
|
||||
* 表示该拦截器实例已经处理了,将直接返回即可。
|
||||
*
|
||||
* @param servletRequest
|
||||
* @param servletResponse
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
|
||||
log.info("当 isAccessAllowed 返回 false 的时候,才会执行 method onAccessDenied ");
|
||||
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
response.sendRedirect(request.getContextPath() + this.errorUrl);
|
||||
|
||||
// 返回 false 表示已经处理,例如页面跳转啥的,表示不在走以下的拦截器了(如果还有配置的话)
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@ -3,16 +3,21 @@ package org.jeecg.config.shiro.ignore;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.config.shiro.IgnoreAuth;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 在spring boot初始化时,根据@RestController注解获取当前spring容器中的bean
|
||||
@ -22,25 +27,32 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class IgnoreAuthPostProcessor implements InitializingBean {
|
||||
|
||||
private RequestMappingHandlerMapping requestMappingHandlerMapping;
|
||||
public class IgnoreAuthPostProcessor implements ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
List<String> ignoreAuthUrls = new ArrayList<>();
|
||||
Set<Class<?>> restControllers = requestMappingHandlerMapping.getHandlerMethods().values().stream().map(HandlerMethod::getBeanType).collect(Collectors.toSet());
|
||||
for (Class<?> restController : restControllers) {
|
||||
ignoreAuthUrls.addAll(postProcessRestController(restController));
|
||||
if (event.getApplicationContext().getParent() == null) {
|
||||
// 只处理根应用上下文的事件,避免在子上下文中重复处理
|
||||
Map<String, Object> restControllers = applicationContext.getBeansWithAnnotation(RestController.class);
|
||||
for (Object restController : restControllers.values()) {
|
||||
// 如 online系统的controller并不是spring 默认生成
|
||||
if (restController instanceof Advised) {
|
||||
ignoreAuthUrls.addAll(postProcessRestController(restController));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Init Token ignoreAuthUrls Config [ 集合 ] :{}", ignoreAuthUrls);
|
||||
if (!CollectionUtils.isEmpty(ignoreAuthUrls)) {
|
||||
InMemoryIgnoreAuth.set(ignoreAuthUrls);
|
||||
|
||||
// 添加免登录url
|
||||
addIgnoreUrl(ignoreAuthUrls);
|
||||
}
|
||||
|
||||
// 计算方法的耗时
|
||||
@ -49,8 +61,9 @@ public class IgnoreAuthPostProcessor implements InitializingBean {
|
||||
log.info("Init Token ignoreAuthUrls Config [ 耗时 ] :" + elapsedTime + "毫秒");
|
||||
}
|
||||
|
||||
private List<String> postProcessRestController(Class<?> clazz) {
|
||||
private List<String> postProcessRestController(Object restController) {
|
||||
List<String> ignoreAuthUrls = new ArrayList<>();
|
||||
Class<?> clazz = ((Advised) restController).getTargetClass();
|
||||
RequestMapping base = clazz.getAnnotation(RequestMapping.class);
|
||||
String[] baseUrl = Objects.nonNull(base) ? base.value() : new String[]{};
|
||||
Method[] methods = clazz.getDeclaredMethods();
|
||||
@ -105,4 +118,28 @@ public class IgnoreAuthPostProcessor implements InitializingBean {
|
||||
private String prefix(String seg) {
|
||||
return seg.startsWith("/") ? seg : "/"+seg;
|
||||
}
|
||||
|
||||
private void addIgnoreUrl(List<String> urls){
|
||||
FilterChainProxy obj = applicationContext.getBean(FilterChainProxy.class);
|
||||
if (Objects.isNull(obj)) {
|
||||
return;
|
||||
}
|
||||
List<SecurityFilterChain> filterChains = (List<SecurityFilterChain>) getProperty(obj,"filterChains");
|
||||
|
||||
if (!CollectionUtils.isEmpty(filterChains)) {
|
||||
for (String url : urls) {
|
||||
filterChains.add(0, new DefaultSecurityFilterChain(new AntPathRequestMatcher(url, null)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object getProperty(Object obj, String fieldName) {
|
||||
try {
|
||||
Field field = obj.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
package org.jeecg.config.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 百度开放api配置
|
||||
*/
|
||||
@Data
|
||||
public class BaiduApi {
|
||||
|
||||
private String appId;
|
||||
private String apiKey;
|
||||
private String secretKey;
|
||||
|
||||
}
|
||||
@ -11,10 +11,6 @@ public class Firewall {
|
||||
* 数据源安全 (开启后,Online报表和图表的数据源为必填)
|
||||
*/
|
||||
private Boolean dataSourceSafe = false;
|
||||
/**
|
||||
* 是否禁止使用 * 查询所有字段
|
||||
*/
|
||||
private Boolean disableSelectAll = false;
|
||||
/**
|
||||
* 低代码模式(dev:开发模式,prod:发布模式——关闭所有在线开发配置能力)
|
||||
*/
|
||||
@ -40,11 +36,4 @@ public class Firewall {
|
||||
this.lowCodeMode = lowCodeMode;
|
||||
}
|
||||
|
||||
public Boolean getDisableSelectAll() {
|
||||
return disableSelectAll;
|
||||
}
|
||||
|
||||
public void setDisableSelectAll(Boolean disableSelectAll) {
|
||||
this.disableSelectAll = disableSelectAll;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
package org.jeecg.config.vo;
|
||||
|
||||
/**
|
||||
* @Description: TODO
|
||||
* @author: scott
|
||||
* @date: 2022年01月21日 14:23
|
||||
*/
|
||||
public class Shiro {
|
||||
private String excludeUrls = "";
|
||||
|
||||
public String getExcludeUrls() {
|
||||
return excludeUrls;
|
||||
}
|
||||
|
||||
public void setExcludeUrls(String excludeUrls) {
|
||||
this.excludeUrls = excludeUrls;
|
||||
}
|
||||
}
|
||||
@ -2,10 +2,10 @@ package org.jeecg.modules.base.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.dto.LogDTO;
|
||||
import org.jeecg.common.constant.enums.ClientTerminalTypeEnum;
|
||||
import org.jeecg.common.util.BrowserUtils;
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
import org.jeecg.modules.base.mapper.BaseCommonMapper;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
@ -35,7 +35,7 @@ public class BaseCommonServiceImpl implements BaseCommonService {
|
||||
logDTO.setId(String.valueOf(IdWorker.getId()));
|
||||
}
|
||||
//保存日志(异常捕获处理,防止数据太大存储失败,导致业务失败)JT-238
|
||||
try {
|
||||
try {
|
||||
logDTO.setCreateTime(new Date());
|
||||
baseCommonMapper.saveLog(logDTO);
|
||||
} catch (Exception e) {
|
||||
@ -74,7 +74,7 @@ public class BaseCommonServiceImpl implements BaseCommonService {
|
||||
//获取登录用户信息
|
||||
if(user==null){
|
||||
try {
|
||||
user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
user = SecureUtil.currentUser();
|
||||
} catch (Exception e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
springdoc.auto-tag-classes: false
|
||||
springdoc.packages-to-scan: org.jeecg
|
||||
File diff suppressed because one or more lines are too long
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>jeecg-boot-parent</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>3.7.3</version>
|
||||
<version>3.7.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -14,10 +14,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
//update-begin---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||
|
||||
/**
|
||||
* OpenAI的SSE监听
|
||||
* @author chenrui
|
||||
@ -31,47 +29,22 @@ public class OpenAISSEEventSourceListener extends EventSourceListener {
|
||||
private SseEmitter sseEmitter;
|
||||
|
||||
private String topicId;
|
||||
/**
|
||||
* 回复消息内容
|
||||
*/
|
||||
private String messageContent = "";
|
||||
|
||||
/**
|
||||
* 完成回复回调
|
||||
*/
|
||||
private Consumer<String> doneCallback;
|
||||
|
||||
/**
|
||||
* 是否正在思考
|
||||
*/
|
||||
private boolean isThinking = false;
|
||||
|
||||
public OpenAISSEEventSourceListener(SseEmitter sseEmitter) {
|
||||
this.sseEmitter = sseEmitter;
|
||||
}
|
||||
|
||||
public OpenAISSEEventSourceListener(String topicId, SseEmitter sseEmitter){
|
||||
public OpenAISSEEventSourceListener(String topicId,SseEmitter sseEmitter){
|
||||
this.topicId = topicId;
|
||||
this.sseEmitter = sseEmitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置消息完成响应时的回调
|
||||
* for [QQYUN-11102/QQYUN-11109]兼容deepseek模型,支持tink标签
|
||||
* @param doneCallback
|
||||
* @author chenrui
|
||||
* @date 2025/2/7 18:14
|
||||
*/
|
||||
public void onDone(Consumer<String> doneCallback){
|
||||
this.doneCallback = doneCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) {
|
||||
log.info("ai-chat建立sse连接...");
|
||||
log.info("OpenAI建立sse连接...");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,12 +53,10 @@ public class OpenAISSEEventSourceListener extends EventSourceListener {
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void onEvent(@NotNull EventSource eventSource, String id, String type, @NotNull String data) {
|
||||
log.debug("ai-chat返回数据:{}", data);
|
||||
log.debug("OpenAI返回数据:{}", data);
|
||||
tokens += 1;
|
||||
if (data.equals("[DONE]")) {
|
||||
log.info("ai-chat返回数据结束了");
|
||||
this.doneCallback.accept(messageContent);
|
||||
messageContent = "";
|
||||
log.info("OpenAI返回数据结束了");
|
||||
sseEmitter.send(SseEmitter.event()
|
||||
.id("[TOKENS]")
|
||||
.data("<br/><br/>tokens:" + tokens())
|
||||
@ -101,46 +72,12 @@ public class OpenAISSEEventSourceListener extends EventSourceListener {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); // 读取Json
|
||||
try {
|
||||
//update-begin---author:chenrui ---date:20250207 for:[QQYUN-11102/QQYUN-11109]兼容deepseek模型,支持think标签------------
|
||||
// 兼容think标签
|
||||
//update-begin---author:chenrui ---date:20250210 for:判断空,防止反悔的内容为空报错.------------
|
||||
if(null != completionResponse.getChoices()
|
||||
&& !completionResponse.getChoices().isEmpty()
|
||||
&& null != completionResponse.getChoices().get(0)) {
|
||||
//update-end---author:chenrui ---date:20250210 for:判断空,防止反悔的内容为空报错.------------
|
||||
Message delta = completionResponse.getChoices().get(0).getDelta();
|
||||
if (null != delta) {
|
||||
String content = delta.getContent();
|
||||
if ("<think>".equals(content)) {
|
||||
isThinking = true;
|
||||
content = "> ";
|
||||
delta.setContent(content);
|
||||
}
|
||||
if ("</think>".equals(content)) {
|
||||
isThinking = false;
|
||||
content = "\n\n";
|
||||
delta.setContent(content);
|
||||
}
|
||||
if (isThinking) {
|
||||
if (null != content && content.contains("\n")) {
|
||||
content = "\n> ";
|
||||
delta.setContent(content);
|
||||
}
|
||||
} else {
|
||||
// 响应消息体不记录思考过程
|
||||
messageContent += null == content ? "" : content;
|
||||
}
|
||||
log.info("ai-chat返回数据,发送给前端:" + content);
|
||||
sseEmitter.send(SseEmitter.event()
|
||||
.id(this.topicId)
|
||||
.data(delta)
|
||||
.reconnectTime(3000));
|
||||
}
|
||||
}
|
||||
//update-end---author:chenrui ---date:20250207 for:[QQYUN-11102/QQYUN-11109]兼容deepseek模型,支持think标签------------
|
||||
sseEmitter.send(SseEmitter.event()
|
||||
.id(this.topicId)
|
||||
.data(completionResponse.getChoices().get(0).getDelta())
|
||||
.reconnectTime(3000));
|
||||
} catch (Exception e) {
|
||||
log.error("ai-chat返回数据,发生异常"+e.getMessage(),e);
|
||||
sseEmitter.completeWithError(e);
|
||||
log.error(e.getMessage(),e);
|
||||
eventSource.cancel();
|
||||
}
|
||||
}
|
||||
@ -149,7 +86,7 @@ public class OpenAISSEEventSourceListener extends EventSourceListener {
|
||||
@Override
|
||||
public void onClosed(@NotNull EventSource eventSource) {
|
||||
log.info("流式输出返回值总共{}tokens", tokens() - 2);
|
||||
log.info("ai-chat关闭sse连接...");
|
||||
log.info("OpenAI关闭sse连接...");
|
||||
}
|
||||
|
||||
|
||||
@ -159,10 +96,10 @@ public class OpenAISSEEventSourceListener extends EventSourceListener {
|
||||
String errMsg = "";
|
||||
ResponseBody body = null == response ? null:response.body();
|
||||
if (Objects.nonNull(body)) {
|
||||
log.error("ai-chat sse连接异常data:{},异常:{}", body.string(), t.getMessage());
|
||||
log.error("OpenAI sse连接异常data:{},异常:{}", body.string(), t.getMessage());
|
||||
errMsg = body.string();
|
||||
} else {
|
||||
log.error("ai-chat sse连接异常data:{},异常:{}", response, t.getMessage());
|
||||
log.error("OpenAI sse连接异常data:{},异常:{}", response, t.getMessage());
|
||||
errMsg = t.getMessage();
|
||||
}
|
||||
eventSource.cancel();
|
||||
|
||||
@ -9,7 +9,6 @@ import com.unfbx.chatgpt.entity.chat.Message;
|
||||
import com.unfbx.chatgpt.exception.BaseException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.chatgpt.prop.AiChatProperties;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
@ -59,12 +58,13 @@ public class ChatServiceImpl implements ChatService {
|
||||
|
||||
private OpenAiStreamClient openAiStreamClient = null;
|
||||
|
||||
/**
|
||||
* ai聊天配置
|
||||
* for [QQYUN-10943]【AI 重要】jeecg-boot-starter-chatgpt 支持deepseek等国产模型
|
||||
*/
|
||||
private AiChatProperties aiChatProperties;
|
||||
//update-begin---author:chenrui ---date:20240131 for:[QQYUN-8212]fix 没有配置启动报错------------
|
||||
public ChatServiceImpl() {
|
||||
try {
|
||||
this.openAiStreamClient = SpringContextUtils.getBean(OpenAiStreamClient.class);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 防止client不能成功注入
|
||||
@ -72,17 +72,11 @@ public class ChatServiceImpl implements ChatService {
|
||||
* @author chenrui
|
||||
* @date 2024/2/3 23:08
|
||||
*/
|
||||
private void ensureClient(){
|
||||
if (null == this.openAiStreamClient){
|
||||
//update-begin---author:chenrui ---date:20240625 for:[TV360X-1570]给于更友好的提示,提示未配置ai------------
|
||||
try {
|
||||
this.openAiStreamClient = SpringContextUtils.getBean(OpenAiStreamClient.class);
|
||||
this.aiChatProperties = SpringContextUtils.getBean(AiChatProperties.class);
|
||||
} catch (Exception ignored) {
|
||||
sendErrorMsg("如果您想使用AI助手,请先设置相应配置!");
|
||||
}
|
||||
//update-end---author:chenrui ---date:20240625 for:[TV360X-1570]给于更友好的提示,提示未配置ai------------
|
||||
private OpenAiStreamClient ensureClient(){
|
||||
if(null == this.openAiStreamClient){
|
||||
this.openAiStreamClient = SpringContextUtils.getBean(OpenAiStreamClient.class);
|
||||
}
|
||||
return this.openAiStreamClient;
|
||||
}
|
||||
//update-end---author:chenrui ---date:20240131 for:[QQYUN-8212]fix 没有配置启动报错------------
|
||||
|
||||
@ -104,7 +98,6 @@ public class ChatServiceImpl implements ChatService {
|
||||
//超时回调
|
||||
sseEmitter.onTimeout(() -> {
|
||||
log.info("[{}]连接超时...................", uid);
|
||||
LocalCache.CACHE.remove(uid);
|
||||
});
|
||||
//异常回调
|
||||
sseEmitter.onError(
|
||||
@ -116,7 +109,7 @@ public class ChatServiceImpl implements ChatService {
|
||||
.name("发生异常!")
|
||||
.data(Message.builder().content("发生异常请重试!").build())
|
||||
.reconnectTime(3000));
|
||||
LocalCache.CACHE.remove(uid);
|
||||
LocalCache.CACHE.put(uid, sseEmitter);
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(),e);
|
||||
}
|
||||
@ -145,7 +138,6 @@ public class ChatServiceImpl implements ChatService {
|
||||
|
||||
@Override
|
||||
public void sendMessage(String topicId, String message) {
|
||||
ensureClient();
|
||||
String uid = getUserId();
|
||||
if (StrUtil.isBlank(message)) {
|
||||
log.info("参数异常,message为null");
|
||||
@ -171,25 +163,16 @@ public class ChatServiceImpl implements ChatService {
|
||||
log.info("聊天消息推送失败uid:[{}],没有创建连接,请重试。", uid);
|
||||
throw new JeecgBootException("聊天消息推送失败uid:[{}],没有创建连接,请重试。~");
|
||||
}
|
||||
//update-begin---author:chenrui ---date:20240625 for:[TV360X-1570]给于更友好的提示,提示未配置ai------------
|
||||
if (null != openAiStreamClient) {
|
||||
OpenAISSEEventSourceListener openAIEventSourceListener = new OpenAISSEEventSourceListener(topicId, sseEmitter);
|
||||
List<Message> finalMsgHistory = msgHistory;
|
||||
openAIEventSourceListener.onDone(respMessage -> {
|
||||
Message tempMessage = Message.builder().content(respMessage).role(Message.Role.ASSISTANT).build();
|
||||
finalMsgHistory.add(tempMessage);
|
||||
redisTemplate.opsForHash().put(cacheKey, CACHE_KEY_MSG_CONTEXT, JSONUtil.toJsonStr(finalMsgHistory));
|
||||
});
|
||||
log.info("话题:{},开始发送消息~~~", topicId);
|
||||
ChatCompletion completion = ChatCompletion
|
||||
.builder()
|
||||
.messages(msgHistory)
|
||||
.model(aiChatProperties.getModel())
|
||||
.build();
|
||||
openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
|
||||
//update-end---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||
}
|
||||
//update-end---author:chenrui ---date:20240625 for:[TV360X-1570]给于更友好的提示,提示未配置ai------------
|
||||
OpenAISSEEventSourceListener openAIEventSourceListener = new OpenAISSEEventSourceListener(topicId, sseEmitter);
|
||||
ChatCompletion completion = ChatCompletion
|
||||
.builder()
|
||||
.messages(msgHistory)
|
||||
.model(ChatCompletion.Model.GPT_3_5_TURBO.getName())
|
||||
.build();
|
||||
ensureClient().streamChatCompletion(completion, openAIEventSourceListener);
|
||||
redisTemplate.opsForHash().put(cacheKey, CACHE_KEY_MSG_CONTEXT, JSONUtil.toJsonStr(msgHistory));
|
||||
//update-end---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||
Result.ok(completion.tokens());
|
||||
}
|
||||
|
||||
//update-begin---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||
@ -211,35 +194,6 @@ public class ChatServiceImpl implements ChatService {
|
||||
return Result.OK(chatHistoryVO);
|
||||
}
|
||||
//update-end---author:chenrui ---date:20240223 for:[QQYUN-8225]聊天记录保存------------
|
||||
|
||||
/**
|
||||
* 发送异常消息给前端
|
||||
* [TV360X-1570]给于更友好的提示,提示未配置ai
|
||||
*
|
||||
* @param msg
|
||||
* @author chenrui
|
||||
* @date 2024/6/25 10:38
|
||||
*/
|
||||
private void sendErrorMsg(String msg) {
|
||||
String uid = getUserId();
|
||||
SseEmitter sseEmitter = (SseEmitter) LocalCache.CACHE.get(uid);
|
||||
if (sseEmitter == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sseEmitter.send(SseEmitter.event()
|
||||
.id("[ERR]")
|
||||
.data(Message.builder().content(msg).build())
|
||||
.reconnectTime(3000));
|
||||
sseEmitter.send(SseEmitter.event()
|
||||
.id("[DONE]")
|
||||
.data("[DONE]")
|
||||
.reconnectTime(3000));
|
||||
sseEmitter.complete();
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//update-end---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||
|
||||
@ -11,8 +11,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.mgt.DefaultSecurityManager;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.aspect.annotation.AutoLog;
|
||||
import org.jeecg.common.aspect.annotation.PermissionData;
|
||||
@ -479,8 +477,8 @@ public class JeecgDemoController extends JeecgController<JeecgDemo, IJeecgDemoSe
|
||||
public Mono<String> test() {
|
||||
//解决shiro报错No SecurityManager accessible to the calling code, either bound to the org.apache.shiro
|
||||
// https://blog.csdn.net/Japhet_jiu/article/details/131177210
|
||||
DefaultSecurityManager securityManager = new DefaultSecurityManager();
|
||||
SecurityUtils.setSecurityManager(securityManager);
|
||||
// DefaultSecurityManager securityManager = new DefaultSecurityManager();
|
||||
// SecurityUtils.setSecurityManager(securityManager);
|
||||
|
||||
return Mono.just("测试");
|
||||
}
|
||||
|
||||
@ -5,15 +5,16 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
import org.jeecg.modules.demo.test.entity.JeecgDemo;
|
||||
import org.jeecg.modules.demo.test.entity.JeecgOrderCustomer;
|
||||
import org.jeecg.modules.demo.test.entity.JeecgOrderMain;
|
||||
@ -30,6 +31,7 @@ import org.jeecgframework.poi.excel.entity.ImportParams;
|
||||
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@ -184,7 +186,7 @@ public class JeecgOrderMainController extends JeecgController<JeecgOrderMain, IJ
|
||||
//Step.2 AutoPoi 导出Excel
|
||||
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
|
||||
//获取当前用户
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
LoginUser sysUser = SecureUtil.currentUser();
|
||||
|
||||
List<JeecgOrderMainPage> pageList = new ArrayList<JeecgOrderMainPage>();
|
||||
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
package org.jeecg.modules.demo.test.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.constant.CacheConstant;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
import org.jeecg.modules.demo.test.entity.JeecgDemo;
|
||||
import org.jeecg.modules.demo.test.mapper.JeecgDemoMapper;
|
||||
import org.jeecg.modules.demo.test.service.IJeecgDemoService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@ -81,7 +83,7 @@ public class JeecgDemoServiceImpl extends ServiceImpl<JeecgDemoMapper, JeecgDemo
|
||||
|
||||
@Override
|
||||
public String getExportFields() {
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
LoginUser sysUser = SecureUtil.currentUser();
|
||||
//权限配置列导出示例
|
||||
//1.配置前缀与菜单中配置的列前缀一致
|
||||
List<String> noAuthList = new ArrayList<>();
|
||||
|
||||
@ -95,7 +95,7 @@ public class JeecgOrderMainServiceImpl extends ServiceImpl<JeecgOrderMainMapper,
|
||||
// 当传过来的id数据库不存在时,说明数据库没有,走新增逻辑
|
||||
ticket.setOrderId(jeecgOrderMain.getId());
|
||||
jeecgOrderTicketMapper.insert(ticket);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if(orderTicket.getId().equals(ticket.getId())){
|
||||
// 传过来的id和数据库id一至时,说明数据库存在该数据,走更新逻辑
|
||||
@ -109,7 +109,7 @@ public class JeecgOrderMainServiceImpl extends ServiceImpl<JeecgOrderMainMapper,
|
||||
// 当传过来的id数据库不存在时,说明数据库没有,走新增逻辑
|
||||
customer.setOrderId(jeecgOrderMain.getId());
|
||||
jeecgOrderCustomerMapper.insert(customer);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if(customers.getId().equals(customer.getId())){
|
||||
//TODO 传过来的id和数据库id一至时,说明数据库存在该数据,走更新逻辑
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,370 +0,0 @@
|
||||
{
|
||||
"list": [
|
||||
{
|
||||
"key": "1717072932495_439966",
|
||||
"type": "card",
|
||||
"isAutoGrid": true,
|
||||
"isContainer": true,
|
||||
"list": [
|
||||
{
|
||||
"type": "input",
|
||||
"name": "名称",
|
||||
"className": "form-input",
|
||||
"icon": "icon-input",
|
||||
"hideTitle": false,
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"defaultValue": "",
|
||||
"required": true,
|
||||
"dataType": null,
|
||||
"pattern": "",
|
||||
"placeholder": "",
|
||||
"clearable": false,
|
||||
"readonly": false,
|
||||
"disabled": false,
|
||||
"fillRuleCode": "",
|
||||
"showPassword": false,
|
||||
"unique": false,
|
||||
"hidden": false,
|
||||
"hiddenOnAdd": false,
|
||||
"fieldNote": "",
|
||||
"autoWidth": 50
|
||||
},
|
||||
"advancedSetting": {
|
||||
"defaultValue": {
|
||||
"type": "compose",
|
||||
"value": "",
|
||||
"format": "string",
|
||||
"allowFunc": true,
|
||||
"valueSplit": "",
|
||||
"customConfig": false
|
||||
}
|
||||
},
|
||||
"remoteAPI": {
|
||||
"url": "",
|
||||
"executed": false
|
||||
},
|
||||
"key": "1717072932495_556479",
|
||||
"model": "input_1717072932495_556479",
|
||||
"modelType": "main",
|
||||
"rules": [
|
||||
{
|
||||
"required": true,
|
||||
"message": "${title}必须填写"
|
||||
}
|
||||
],
|
||||
"isSubItem": false
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"name": "数字",
|
||||
"className": "form-number",
|
||||
"icon": "icon-number",
|
||||
"hideTitle": false,
|
||||
"options": {
|
||||
"width": "",
|
||||
"required": false,
|
||||
"defaultValue": 0,
|
||||
"placeholder": "",
|
||||
"controls": false,
|
||||
"min": 0,
|
||||
"minUnlimited": true,
|
||||
"max": 100,
|
||||
"maxUnlimited": true,
|
||||
"step": 1,
|
||||
"disabled": false,
|
||||
"controlsPosition": "right",
|
||||
"unitText": "",
|
||||
"unitPosition": "suffix",
|
||||
"showPercent": false,
|
||||
"align": "left",
|
||||
"hidden": false,
|
||||
"hiddenOnAdd": false,
|
||||
"fieldNote": "",
|
||||
"autoWidth": 50
|
||||
},
|
||||
"advancedSetting": {
|
||||
"defaultValue": {
|
||||
"type": "compose",
|
||||
"value": "",
|
||||
"format": "number",
|
||||
"allowFunc": true,
|
||||
"valueSplit": "",
|
||||
"customConfig": false
|
||||
}
|
||||
},
|
||||
"remoteAPI": {
|
||||
"url": "",
|
||||
"executed": false
|
||||
},
|
||||
"key": "1717072985868_606195",
|
||||
"model": "number_1717072985868_606195",
|
||||
"modelType": "main",
|
||||
"rules": [],
|
||||
"isSubItem": false
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"required": false,
|
||||
"hiddenOnAdd": false,
|
||||
"hidden": false,
|
||||
"fieldNote": ""
|
||||
},
|
||||
"model": "card_1717072932495_439966",
|
||||
"hideTitle": false,
|
||||
"modelType": "main"
|
||||
},
|
||||
{
|
||||
"key": "1717072988159_545097",
|
||||
"type": "card",
|
||||
"isAutoGrid": true,
|
||||
"isContainer": true,
|
||||
"list": [
|
||||
{
|
||||
"type": "money",
|
||||
"name": "金额",
|
||||
"className": "form-money",
|
||||
"icon": "icon-money",
|
||||
"hideTitle": false,
|
||||
"options": {
|
||||
"width": "180px",
|
||||
"placeholder": "请输入金额",
|
||||
"required": false,
|
||||
"unitText": "元",
|
||||
"unitPosition": "suffix",
|
||||
"precision": 2,
|
||||
"hidden": false,
|
||||
"disabled": false,
|
||||
"hiddenOnAdd": false,
|
||||
"fieldNote": "",
|
||||
"autoWidth": 50
|
||||
},
|
||||
"advancedSetting": {
|
||||
"defaultValue": {
|
||||
"type": "compose",
|
||||
"value": "",
|
||||
"format": "number",
|
||||
"allowFunc": true,
|
||||
"valueSplit": "",
|
||||
"customConfig": false
|
||||
}
|
||||
},
|
||||
"remoteAPI": {
|
||||
"url": "",
|
||||
"executed": false
|
||||
},
|
||||
"key": "1717072988159_568693",
|
||||
"model": "money_1717072988159_568693",
|
||||
"modelType": "main",
|
||||
"rules": [],
|
||||
"isSubItem": false
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"name": "下拉选择框",
|
||||
"className": "form-select",
|
||||
"icon": "icon-select",
|
||||
"hideTitle": false,
|
||||
"options": {
|
||||
"defaultValue": "",
|
||||
"multiple": false,
|
||||
"disabled": false,
|
||||
"clearable": true,
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"showLabel": false,
|
||||
"showType": "default",
|
||||
"width": "",
|
||||
"useColor": false,
|
||||
"colorIteratorIndex": 3,
|
||||
"options": [
|
||||
{
|
||||
"value": "下拉框1",
|
||||
"itemColor": "#2196F3"
|
||||
},
|
||||
{
|
||||
"value": "下拉框2",
|
||||
"itemColor": "#08C9C9"
|
||||
},
|
||||
{
|
||||
"value": "下拉框3",
|
||||
"itemColor": "#00C345"
|
||||
}
|
||||
],
|
||||
"remote": false,
|
||||
"filterable": false,
|
||||
"remoteOptions": [],
|
||||
"props": {
|
||||
"value": "value",
|
||||
"label": "label"
|
||||
},
|
||||
"remoteFunc": "",
|
||||
"hidden": false,
|
||||
"hiddenOnAdd": false,
|
||||
"fieldNote": "",
|
||||
"autoWidth": 50
|
||||
},
|
||||
"advancedSetting": {
|
||||
"defaultValue": {
|
||||
"type": "compose",
|
||||
"value": "",
|
||||
"format": "string",
|
||||
"allowFunc": true,
|
||||
"valueSplit": ",",
|
||||
"customConfig": true
|
||||
}
|
||||
},
|
||||
"remoteAPI": {
|
||||
"url": "",
|
||||
"executed": false
|
||||
},
|
||||
"key": "1717072991431_622198",
|
||||
"model": "select_1717072991431_622198",
|
||||
"modelType": "main",
|
||||
"rules": [],
|
||||
"isSubItem": false
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"required": false,
|
||||
"hiddenOnAdd": false,
|
||||
"hidden": false,
|
||||
"fieldNote": ""
|
||||
},
|
||||
"model": "card_1717072988159_545097",
|
||||
"hideTitle": false,
|
||||
"modelType": "main"
|
||||
},
|
||||
{
|
||||
"key": "1717072932495_382575",
|
||||
"type": "card",
|
||||
"isAutoGrid": true,
|
||||
"isContainer": true,
|
||||
"list": [
|
||||
{
|
||||
"type": "imgupload",
|
||||
"name": "图片上传",
|
||||
"className": "form-tupian",
|
||||
"icon": "icon-tupian",
|
||||
"hideTitle": false,
|
||||
"options": {
|
||||
"defaultValue": [],
|
||||
"size": {
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"width": "",
|
||||
"tokenFunc": "funcGetToken",
|
||||
"token": "",
|
||||
"domain": "http://img.h5huodong.com",
|
||||
"disabled": false,
|
||||
"length": 9,
|
||||
"multiple": true,
|
||||
"hidden": false,
|
||||
"hiddenOnAdd": false,
|
||||
"required": false,
|
||||
"fieldNote": "",
|
||||
"autoWidth": 50
|
||||
},
|
||||
"key": "1717072996509_795340",
|
||||
"model": "imgupload_1717072996509_795340",
|
||||
"modelType": "main",
|
||||
"rules": [],
|
||||
"isSubItem": false
|
||||
},
|
||||
{
|
||||
"type": "file-upload",
|
||||
"name": "附件",
|
||||
"className": "form-file-upload",
|
||||
"icon": "icon-shangchuan",
|
||||
"hideTitle": false,
|
||||
"options": {
|
||||
"defaultValue": [],
|
||||
"token": "",
|
||||
"length": 1,
|
||||
"drag": false,
|
||||
"multiple": false,
|
||||
"disabled": false,
|
||||
"buttonText": "添加附件",
|
||||
"tokenFunc": "funcGetToken",
|
||||
"hidden": false,
|
||||
"hiddenOnAdd": false,
|
||||
"required": false,
|
||||
"fieldNote": "",
|
||||
"autoWidth": 50
|
||||
},
|
||||
"key": "1717072932495_669325",
|
||||
"model": "file_upload_1717072932495_669325",
|
||||
"modelType": "main",
|
||||
"rules": [],
|
||||
"isSubItem": false
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"required": false,
|
||||
"hiddenOnAdd": false,
|
||||
"hidden": false,
|
||||
"fieldNote": ""
|
||||
},
|
||||
"model": "card_1717072932495_382575",
|
||||
"hideTitle": false,
|
||||
"modelType": "main"
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"titleField": "input_1717072932495_556479",
|
||||
"showHeaderTitle": true,
|
||||
"labelWidth": 100,
|
||||
"labelPosition": "top",
|
||||
"size": "small",
|
||||
"dialogOptions": {
|
||||
"top": 20,
|
||||
"width": 1000,
|
||||
"padding": {
|
||||
"top": 25,
|
||||
"right": 25,
|
||||
"bottom": 30,
|
||||
"left": 25
|
||||
}
|
||||
},
|
||||
"disabledAutoGrid": false,
|
||||
"designMobileView": false,
|
||||
"enableComment": true,
|
||||
"hasWidgets": [
|
||||
"input",
|
||||
"number",
|
||||
"card",
|
||||
"money",
|
||||
"select",
|
||||
"imgupload",
|
||||
"file-upload"
|
||||
],
|
||||
"expand": {
|
||||
"js": "",
|
||||
"css": "",
|
||||
"url": {
|
||||
"js": "",
|
||||
"css": ""
|
||||
}
|
||||
},
|
||||
"transactional": true,
|
||||
"customRequestURL": [
|
||||
{
|
||||
"url": ""
|
||||
}
|
||||
],
|
||||
"allowExternalLink": false,
|
||||
"externalLinkShowData": false,
|
||||
"headerImgUrl": "",
|
||||
"externalTitle": "",
|
||||
"enableNotice": false,
|
||||
"noticeMode": "external",
|
||||
"noticeType": "system",
|
||||
"noticeReceiver": "",
|
||||
"allowPrint": false,
|
||||
"allowJmReport": false,
|
||||
"jmReportURL": "",
|
||||
"bizRuleConfig": [],
|
||||
"bigDataMode": false
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user