mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2025-12-08 17:12:28 +08:00
Compare commits
170 Commits
v3.8.1
...
springboot
| Author | SHA1 | Date | |
|---|---|---|---|
| 74d80459e1 | |||
| bdc1e7988b | |||
| 5730ed2dd2 | |||
| 5743d71f3c | |||
| db16cb5fda | |||
| 07c012adc7 | |||
| 8edb547113 | |||
| da9e8570cd | |||
| 5e37b4de8f | |||
| cab42b819c | |||
| d3b8948f40 | |||
| 766ec0df52 | |||
| b72c343090 | |||
| cdd7c5c5a1 | |||
| 120cf85eb4 | |||
| a3969a5c63 | |||
| 1222c76ff8 | |||
| 6048866313 | |||
| 83edfa9090 | |||
| 5d5d9fc53d | |||
| 002bfe25f8 | |||
| 7cb2dc4fde | |||
| ab151879b3 | |||
| 000ae1db30 | |||
| 63e066180d | |||
| 44c1079f87 | |||
| e825e0f912 | |||
| 132e89b0e1 | |||
| 881a637285 | |||
| 02e9f8984f | |||
| a4343fc2cb | |||
| 152e8c7aaa | |||
| d7dc81455d | |||
| aefdcd6315 | |||
| 1cf4054e76 | |||
| 7829cf18d7 | |||
| 69c3a9da9a | |||
| 4d34150479 | |||
| 29687c8908 | |||
| 2e93a92dde | |||
| d383f7458d | |||
| 700318e1c1 | |||
| d728d6b090 | |||
| 7abc2e4c9c | |||
| da2b0cc354 | |||
| a6751c22be | |||
| f087525a75 | |||
| 4f46213df6 | |||
| d76842ae07 | |||
| 8c64db46e5 | |||
| 81fb2ac3b2 | |||
| fa98817aeb | |||
| 1158977826 | |||
| 8b6def0ee3 | |||
| 39c0d5b3f5 | |||
| adeebee840 | |||
| 862aaa8632 | |||
| 6a11ff8a64 | |||
| 73059b8a53 | |||
| fd60e49f5b | |||
| 5f1dc06067 | |||
| 208d9990ae | |||
| 4f3c71af5b | |||
| 70bd639206 | |||
| d245ef3037 | |||
| 7af8346b79 | |||
| 1b8a31f0d3 | |||
| 294ad5a6c9 | |||
| 065b255d90 | |||
| b7519d7199 | |||
| 81ba07c853 | |||
| 92ed296e63 | |||
| c2aff84914 | |||
| e002cd3bf3 | |||
| 1de07ff3ff | |||
| 35852d41f1 | |||
| a2cb1d9f25 | |||
| 2002af54d0 | |||
| 89747403a2 | |||
| 3db0995c3f | |||
| 950621dd88 | |||
| 033cf51d69 | |||
| fb9f367517 | |||
| b2da45d803 | |||
| 2840f0d325 | |||
| 6ace7eae8a | |||
| 3d88147c59 | |||
| ba0052d452 | |||
| 69fca254f0 | |||
| b3de596199 | |||
| f46273d15e | |||
| 0fe258dbc2 | |||
| de7f23c555 | |||
| d97e56b2f0 | |||
| c868496b78 | |||
| c5150baa69 | |||
| 3d9f59c69b | |||
| 420d6db3fb | |||
| 473a626039 | |||
| 0308b0597c | |||
| 2191f5d48c | |||
| 1158b0b6e7 | |||
| ead2cef1f4 | |||
| 83bb0a0a6a | |||
| b474e9e5a5 | |||
| 422373e300 | |||
| 1cf11a4c2a | |||
| 925f163784 | |||
| d01c1d7d47 | |||
| 3576b54945 | |||
| a760f94b94 | |||
| 342bdd2e38 | |||
| 419e2bea0b | |||
| 443abc3ede | |||
| 358e46559f | |||
| ea59454f51 | |||
| 615a1bc4ff | |||
| d976f12c8f | |||
| 3783765161 | |||
| 8cc6810fdd | |||
| a902d9af19 | |||
| 37a116f2fb | |||
| 1564831f7e | |||
| 18c1cd00c1 | |||
| a988b05e72 | |||
| a9a6fd529d | |||
| 6586d3a880 | |||
| af354f9f5e | |||
| a638a93b65 | |||
| f1cad333da | |||
| 676fffa2c8 | |||
| 23cc569a47 | |||
| dafacf153b | |||
| 7a9f357510 | |||
| f30a8c658a | |||
| e84d7726d2 | |||
| 0f39802698 | |||
| a014a3ed0e | |||
| 5720d1a01e | |||
| 5eed6ac6d2 | |||
| 0cfa1e223a | |||
| 219869f4c0 | |||
| e6edde963a | |||
| c44b66128e | |||
| 9356b04741 | |||
| d0a094f9a3 | |||
| 73eb625737 | |||
| 74880705b8 | |||
| 118775cf79 | |||
| 7b80ae3e68 | |||
| 336e7851aa | |||
| 56b9131675 | |||
| 3c0cc49f0c | |||
| 69b2e97935 | |||
| 1c2a49d371 | |||
| 967197d224 | |||
| 396718bc5e | |||
| 37c62c3962 | |||
| 96b378bb7a | |||
| 9d440a4261 | |||
| 4870c43f39 | |||
| 9c21f621c0 | |||
| 8d382b76ad | |||
| 410f2539e1 | |||
| baee00921f | |||
| e00b25af42 | |||
| 44d6b37873 | |||
| 271712b050 | |||
| 3482b3a0db | |||
| 444c7140f6 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -9,6 +9,8 @@ assignees: getActivity
|
||||
|
||||
##### 版本号:
|
||||
|
||||
##### 分支:
|
||||
|
||||
|
||||
##### 问题描述:
|
||||
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/feature_request.md
vendored
3
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -6,9 +6,10 @@ assignees: getActivity
|
||||
|
||||
---
|
||||
|
||||
|
||||
##### 版本号:
|
||||
|
||||
##### 分支:
|
||||
|
||||
|
||||
##### 问题描述:
|
||||
|
||||
|
||||
12
README-EN.md
12
README-EN.md
@ -7,12 +7,12 @@
|
||||
JEECG BOOT AI Low Code Platform
|
||||
===============
|
||||
|
||||
Current version: 3.8.1 (Release date: 2025-06-30)
|
||||
Current version: 3.8.3 (Release date: 2025-10-09)
|
||||
|
||||
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||
[](http://www.jeecg.com)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
|
||||
@ -69,9 +69,13 @@ Jeecg-Boot AI low code platform can be applied in the development of any J2EE pr
|
||||
Starts the project
|
||||
-----------------------------------
|
||||
|
||||
- [IDEA Quick start](https://help.jeecg.com/java/setup/idea/startup)
|
||||
- [Docker Quick start](https://help.jeecg.com/java/docker/quick)
|
||||
> Default account password: admin/123456
|
||||
|
||||
- [Development Environment setup](https://help.jeecg.com/java/setup/tools)
|
||||
- [IDEA Quick start(single model)](https://help.jeecg.com/java/setup/idea/startup)
|
||||
- [Docker Quick start(single model)](https://help.jeecg.com/java/docker/quick)
|
||||
- [IDEA Quick start(microservices model)](https://help.jeecg.com/java/springcloud/switchcloud/monomer)
|
||||
- [Docker Quick start(microservices model)](https://help.jeecg.com/java/docker/quickcloud)
|
||||
|
||||
|
||||
Technical documentation
|
||||
|
||||
97
README.md
97
README.md
@ -2,12 +2,14 @@
|
||||
JeecgBoot AI低代码平台
|
||||
===============
|
||||
|
||||
当前最新版本: 3.8.1(发布日期:2025-06-30)
|
||||
当前最新版本: 3.8.3(发布日期:2025-10-09)
|
||||
|
||||
> 重要提醒: v3.8.3 是 SpringBoot2 的最终版本,后续将不再维护。建议大家尽快升级至基于 SpringBoot3版本,以获得更好的性能和支持。
|
||||
|
||||
[](https://github.com/jeecgboot/JeecgBoot/blob/master/LICENSE)
|
||||
[](http://guojusoft.com)
|
||||
[](https://github.com/jeecgboot/JeecgBoot)
|
||||
[](https://jeecg.com)
|
||||
[](https://jeecg.blog.csdn.net)
|
||||
[](https://github.com/jeecgboot/JeecgBoot)
|
||||
[](https://github.com/jeecgboot/JeecgBoot)
|
||||
[](https://github.com/jeecgboot/JeecgBoot)
|
||||
|
||||
@ -19,7 +21,7 @@ JeecgBoot AI低代码平台
|
||||
<h3 align="center">企业级AI低代码平台</h3>
|
||||
|
||||
JeecgBoot是一款企业级低代码平台集成了AI应用平台功能,旨在帮助开发者快速实现低代码开发和构建、部署个性化的 AI 应用。
|
||||
前后端分离架构Ant Design4、Vue3,SpringBoot,SpringCloud Alibaba,Mybatis-plus,Shiro/SpringAuthorizationServer,强大的代码生成器让前后端代码一键生成,无需写任何代码;提供强大的报表和大屏工具,满足企业级数据产品需求!
|
||||
前后端分离架构Ant Design4、Vue3,SpringBoot2/3,SpringCloud Alibaba,Mybatis-plus,Shiro/SpringAuthorizationServer,强大的代码生成器让前后端代码一键生成,无需写任何代码;提供强大的报表和大屏工具,满足企业级数据产品需求!
|
||||
引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE, 帮助Java项目解决80%的重复工作,让开发更多关注业务,提高效率、节省成本,同时又不失灵活性!低代码能力:Online表单、表单设计、流程设计、Online报表、大屏/仪表盘设计、报表设计; AI应用平台功能:AI知识库问答、AI模型管理、AI流程编排、AI聊天等,支持含ChatGPT、DeepSeek、Ollama等多种AI大模型
|
||||
|
||||
|
||||
@ -47,10 +49,10 @@ JeecgBoot低代码平台,可以应用在任何J2EE项目的开发中,支持
|
||||
版本说明
|
||||
-----------------------------------
|
||||
|
||||
|下载 | JDK17 + SpringBoot2.7 | JDK17 + SpringBoot3.3 + Shiro |JDK17 + SpringBoot3.3+ SpringAuthorizationServer |
|
||||
|下载 | JDK17 + SpringBoot3.3 + Shiro |JDK17 + SpringBoot3.3+ SpringAuthorizationServer | JDK17/JDK8 + SpringBoot2.7 |
|
||||
|------|----------------------------------------------------|-----------------------------------------------------------------------------|--------------------------------------------|
|
||||
| Github | [`master`](https://github.com/jeecgboot/JeecgBoot) | [`springboot3`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3) 分支 | [`springboot3_sas`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3_sas) 分支 |
|
||||
| Gitee | [`master`](https://gitee.com/jeecg/JeecgBoot) | [`springboot3`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3/) 分支 | [`springboot3_sas`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3_sas) 分支 |
|
||||
| Github | [`springboot3`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3) | [`springboot3_sas`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3_sas) 分支 |[`master`](https://github.com/jeecgboot/JeecgBoot) 分支|
|
||||
| Gitee | [`springboot3`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3/) | [`springboot3_sas`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3_sas) 分支 |[`master`](https://gitee.com/jeecg/JeecgBoot) 分支 |
|
||||
|
||||
|
||||
- `jeecg-boot` 是后端JAVA源码项目(支持单体和微服务切换).
|
||||
@ -65,30 +67,44 @@ JeecgBoot低代码平台,可以应用在任何J2EE项目的开发中,支持
|
||||
启动项目
|
||||
-----------------------------------
|
||||
|
||||
- [IDEA启动前后端项目](https://help.jeecg.com/java/setup/idea/startup)
|
||||
- [Docker一键启动前后端](https://help.jeecg.com/java/docker/quick)
|
||||
> 默认账号密码: admin/123456
|
||||
|
||||
- [开发环境搭建](https://help.jeecg.com/java/setup/tools)
|
||||
- [IDEA启动前后端(单体模式)](https://help.jeecg.com/java/setup/idea/startup)
|
||||
- [Docker一键启动(单体模式)](https://help.jeecg.com/java/docker/quick)
|
||||
- [IDEA启动前后端(微服务方式)](https://help.jeecg.com/java/springcloud/switchcloud/monomer)
|
||||
- [Docker一键启动(微服务方式)](https://help.jeecg.com/java/docker/quickcloud)
|
||||
|
||||
|
||||
技术文档
|
||||
-----------------------------------
|
||||
|
||||
- 官方网站: [http://www.jeecg.com](http://www.jeecg.com)
|
||||
- 入门指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [开发文档](https://help.jeecg.com) | [AI应用使用手册](https://help.jeecg.com/aigc)
|
||||
- 技术支持: [反馈问题](https://github.com/jeecgboot/JeecgBoot/issues/new?template=bug_report.md) | [视频教程](http://jeecg.com/doc/video) | [低代码体验一分钟](https://jeecg.blog.csdn.net/article/details/106079007)
|
||||
- 在线演示: [平台演示](https://boot3.jeecg.com) | [APP演示](https://jeecg.com/appIndex)
|
||||
- 入门指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [代码生成使用](https://help.jeecg.com/java/codegen/online) | [开发文档](https://help.jeecg.com) | [AI应用手册](https://help.jeecg.com/aigc) | [视频教程](http://jeecg.com/doc/video)
|
||||
- AI编程: [JEECG低代码+AI编程Cursor+GitHub Copilot实现高效编程](https://www.bilibili.com/video/BV11XyaBVEoH)
|
||||
- 技术支持: [反馈问题](https://github.com/jeecgboot/JeecgBoot/issues/new?template=bug_report.md) | [低代码体验一分钟](https://jeecg.blog.csdn.net/article/details/106079007)
|
||||
- QQ交流群 : 964611995、⑩716488839(满)、⑨808791225(满)、其他(满)
|
||||
|
||||
|
||||
AI应用平台功能介绍
|
||||
AI 应用平台介绍
|
||||
-----------------------------------
|
||||
|
||||
一个全栈式 AI 开发平台,旨在帮助开发者快速构建和部署个性化的 AI 应用。
|
||||
|
||||
JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类似`Dify`的`AIGC应用开发平台`+`知识库问答`,是一款基于LLM大语言模型AI应用平台和 RAG 的知识库问答系统。
|
||||
其直观的界面结合了 AI 流程编排、RAG 管道、知识库管理、模型管理、对接向量库、实时运行可观察等,让您可以快速从原型到生产,拥有AI服务能力。 [详细专题介绍,请点击查看](README-AI.md)
|
||||
其直观的界面结合了 AI 流程编排、RAG 管道、知识库管理、模型管理、对接向量库、实时运行可观察等,让您可以快速从原型到生产,拥有AI服务能力。
|
||||
|
||||
- [详细专题介绍,请点击查看](README-AI.md)
|
||||
|
||||
- AI视频介绍
|
||||
|
||||
[](https://www.bilibili.com/video/BV1zmd7YFE4w)
|
||||
|
||||
|
||||
为什么选择JeecgBoot?
|
||||
-----------------------------------
|
||||
- 1.采用最新主流前后分离框架(Spring Boot + MyBatis + Ant Design4 + Vue3),容易上手;代码生成器依赖性低,灵活的扩展能力,可快速实现二次开发。
|
||||
- 1.采用最新主流前后分离框架(Spring Boot3 + MyBatis + Shiro//SpringAuthorizationServer + Ant Design4 + Vue3),容易上手;代码生成器依赖性低,灵活的扩展能力,可快速实现二次开发。
|
||||
- 2.前端大版本换代,最新版采用 Vue3.0 + TypeScript + Vite6 + Ant Design Vue4 等新技术方案。
|
||||
- 3.支持微服务Spring Cloud Alibaba(Nacos、Gateway、Sentinel、Skywalking),提供简易机制,支持单体和微服务自由切换(这样可以满足各类项目需求)。
|
||||
- 4.开发效率高,支持在线建表和AI建表,提供强大代码生成器,单表、树列表、一对多、一对一等数据模型,增删改查功能一键生成,菜单配置直接使用。
|
||||
@ -141,6 +157,9 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
||||
#### 前端
|
||||
|
||||
- 前端环境要求:Node.js要求`Node 20+` 版本以上、pnpm 要求`9+` 版本以上
|
||||
|
||||
> Vite 不再支持已结束生命周期(EOL)的 Node.js 18。现在需要使用 Node.js 20.19+ 或 22.12+。
|
||||
|
||||
- 依赖管理:node、npm、pnpm
|
||||
- 前端IDE建议:IDEA、WebStorm、Vscode
|
||||
- 采用 Vue3.0+TypeScript+Vite6+Ant-Design-Vue4等新技术方案,包括二次封装组件、utils、hooks、动态菜单、权限校验、按钮级别权限控制等功能
|
||||
@ -152,11 +171,11 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
||||
- IDE建议: IDEA (必须安装lombok插件 )
|
||||
- 语言:Java 默认jdk17(支持jdk8、jdk21)
|
||||
- 依赖管理:Maven
|
||||
- 基础框架:Spring Boot 2.7.18
|
||||
- 基础框架:Spring Boot 3.5.5/2.7.18
|
||||
- 微服务框架: Spring Cloud Alibaba 2021.0.6.2
|
||||
- 持久层框架:MybatisPlus 3.5.3.2
|
||||
- 报表工具: JimuReport 1.9.5
|
||||
- 安全框架:Apache Shiro 1.13.0,Jwt 4.5.0
|
||||
- 报表工具: JimuReport 2.1.3
|
||||
- 安全框架:Apache Shiro/SpringAuthorizationServer,Jwt 4.5.0
|
||||
- 微服务技术栈:Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywalking
|
||||
- 数据库连接池:阿里巴巴Druid 1.1.24
|
||||
- AI大模型:支持 `ChatGPT` `DeepSeek`切换
|
||||
@ -186,12 +205,6 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
||||
|
||||
## 微服务解决方案
|
||||
|
||||
> 微服务方式快速启动
|
||||
> - [单体快速切换微服务](https://help.jeecg.com/java/springcloud/switchcloud/monomer)
|
||||
> - [Docker一键启动微服务前后端](https://help.jeecg.com/java/docker/quickcloud)
|
||||
|
||||
|
||||
|
||||
- 1、服务注册和发现 Nacos √
|
||||
- 2、统一配置中心 Nacos √
|
||||
- 3、路由网关 gateway(三种加载方式) √
|
||||
@ -219,9 +232,9 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
||||
开源版与企业版区别?
|
||||
-----------------------------------
|
||||
|
||||
- JeecgBoot开源版采用 [Apache-2.0 license](LICENSE) 开源协议,允许商用使用,不会造成侵权行为,允许基于本平台软件开展业务系统开发(在任何情况下,您不得使用本软件开发可能被认为与本软件竞争的软件).
|
||||
- JeecgBoot开源版采用 [Apache-2.0 license](LICENSE) 协议附加补充条款:允许商用使用,不会造成侵权行为,允许基于本平台软件开展业务系统开发(但在任何情况下,您不得使用本软件开发可能被认为与本软件竞争的软件).
|
||||
- 商业版与开源版主要区别在于商业版提供了技术支持 和 更多的企业级功能(例如:Online图表、流程监控、流程设计、流程审批、表单设计器、表单视图、积木报表企业版、OA办公、商业APP、零代码应用、Online模块源码等功能). [更多商业功能介绍,点击查看](README-Enterprise.md)
|
||||
- JeecgBoot未来发展方向是:零代码平台的建设,也就是团队的另外一款产品 [敲敲云零代码](https://www.qiaoqiaoyun.com) ,无需编码即可通过拖拽快速搭建企业级应用,与JeecgBoot低代码平台形成互补,满足从简单业务到复杂系统的全场景开发需求,目前已经上线,[欢迎注册体验](https://app.qiaoqiaoyun.com)
|
||||
- JeecgBoot未来发展方向是:零代码平台的建设,也就是团队的另外一款产品 [敲敲云零代码](https://www.qiaoqiaoyun.com) ,无需编码即可通过拖拽快速搭建企业级应用,与JeecgBoot低代码平台形成互补,满足从简单业务到复杂系统的全场景开发需求,目前已经开源,[欢迎下载](https://qiaoqiaoyun.com/downloadCode)
|
||||
|
||||
|
||||
|
||||
@ -233,6 +246,10 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
||||
|
||||
|
||||
|
||||
#### 系统功能架构图
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 开源版功能清单
|
||||
@ -241,6 +258,7 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
||||
│ ├─用户管理
|
||||
│ ├─角色管理
|
||||
│ ├─菜单管理
|
||||
│ ├─首页配置
|
||||
│ ├─权限设置(支持按钮权限、数据权限)
|
||||
│ ├─表单权限(控制字段禁用、隐藏)
|
||||
│ ├─部门管理
|
||||
@ -251,7 +269,9 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
||||
│ └─职务管理
|
||||
│ └─通讯录
|
||||
│ ├─多数据源管理
|
||||
│ └─多租户管理(租户管理、租户角色、我的租户)
|
||||
│ ├─白名单管理
|
||||
│ ├─第三方配置(对接钉钉和企业微信)
|
||||
│ └─多租户管理(租户管理、租户角色、我的租户、租户默认套餐管理)
|
||||
├─Online在线开发(低代码)
|
||||
│ ├─Online在线表单
|
||||
│ ├─Online代码生成器
|
||||
@ -259,6 +279,7 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
||||
│ ├─仪表盘设计器
|
||||
│ ├─系统编码规则
|
||||
│ ├─系统校验规则
|
||||
│ ├─APP版本管理
|
||||
├─AI应用平台
|
||||
│ ├─AI知识库问答系统
|
||||
│ ├─AI大模型管理
|
||||
@ -271,10 +292,13 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
||||
│ ├─AI聊天窗口支持移动端
|
||||
│ ├─支持常见大模型ChatGPT和DeepSeek、ollama等等
|
||||
│ ├─AI OCR示例
|
||||
├─积木报表设计器
|
||||
│ ├─打印设计器
|
||||
│ ├─数据报表设计
|
||||
│ ├─图形报表设计(支持echart)
|
||||
├─数据可视化
|
||||
│ ├─报表设计器(支持打印设计)
|
||||
│ ├─大屏设和仪表盘设计
|
||||
├─OpenAPI(基于AK和SK认证鉴权)
|
||||
│ ├─接口管理
|
||||
│ ├─接口授权
|
||||
│ ├─接口文档
|
||||
├─消息中心
|
||||
│ ├─消息管理
|
||||
│ ├─模板管理
|
||||
@ -288,8 +312,10 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
||||
│ ├─平台移动自适应支持
|
||||
│ ├─提供新版uniapp3的代码生成器模板
|
||||
├─系统监控
|
||||
│ ├─基于AK和SK认证鉴权OpenAPI功能
|
||||
│ ├─Gateway路由网关
|
||||
│ ├─基于AK和SK认证鉴权OpenAPI功能
|
||||
│ ├─定时任务
|
||||
│ ├─数据源管理
|
||||
│ ├─性能扫描监控
|
||||
│ │ ├─监控 Redis
|
||||
│ │ ├─Tomcat
|
||||
@ -297,13 +323,11 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
||||
│ │ ├─服务器信息
|
||||
│ │ ├─请求追踪
|
||||
│ │ ├─磁盘监控
|
||||
│ ├─定时任务
|
||||
│ ├─系统日志
|
||||
│ ├─消息中心(支持短信、邮件、微信推送等等)
|
||||
│ ├─数据日志(记录数据快照,可对比快照,查看数据变更情况)
|
||||
│ ├─系统通知
|
||||
│ ├─SQL监控
|
||||
│ ├─swagger-ui(在线接口文档)
|
||||
│ ├─在线用户
|
||||
│─报表示例
|
||||
│ ├─曲线图
|
||||
│ └─饼状图
|
||||
@ -378,9 +402,6 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
||||
|
||||
### 系统效果
|
||||
|
||||
|
||||
|
||||
|
||||
##### PC端
|
||||

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

|
||||

|
||||
|
||||
216
check_jeecgenv.py
Normal file
216
check_jeecgenv.py
Normal file
@ -0,0 +1,216 @@
|
||||
import os
|
||||
import subprocess
|
||||
import re
|
||||
import sys
|
||||
from typing import Tuple, Optional
|
||||
|
||||
def run_command(cmd: str) -> Tuple[int, str]:
|
||||
"""执行命令并返回退出码和输出"""
|
||||
try:
|
||||
result = subprocess.run(cmd, shell=True, check=False,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
text=True)
|
||||
return result.returncode, result.stdout.strip()
|
||||
except Exception as e:
|
||||
return -1, str(e)
|
||||
|
||||
def check_java() -> bool:
|
||||
"""检查JDK 17+是否安装"""
|
||||
print("\n检查JDK 17+...")
|
||||
rc, output = run_command("java -version 2>&1")
|
||||
if rc != 0:
|
||||
print("❌ 未检测到Java,请安装JDK 17+")
|
||||
return False
|
||||
|
||||
version_pattern = r'"(\d+)(?:\.\d+)*(?:_\d+)?'
|
||||
match = re.search(version_pattern, output)
|
||||
if not match:
|
||||
print("❌ 无法解析Java版本")
|
||||
return False
|
||||
|
||||
version = int(match.group(1))
|
||||
if version >= 17:
|
||||
print(f"✅ JDK版本 {version} (满足17+要求)")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ JDK版本 {version} (需要17+)")
|
||||
return False
|
||||
|
||||
def check_maven() -> bool:
|
||||
"""检查Maven是否安装"""
|
||||
print("\n检查Maven...")
|
||||
rc, output = run_command("mvn -v")
|
||||
if rc == 0:
|
||||
print("✅ Maven已安装")
|
||||
return True
|
||||
else:
|
||||
print("❌ Maven未安装")
|
||||
return False
|
||||
|
||||
def check_node() -> bool:
|
||||
"""检查Node.js 20+是否安装"""
|
||||
print("\n检查Node.js 20+...")
|
||||
rc, output = run_command("node -v")
|
||||
if rc != 0:
|
||||
print("❌ Node.js未安装")
|
||||
return False
|
||||
|
||||
version_pattern = r'v(\d+)\.\d+\.\d+'
|
||||
match = re.search(version_pattern, output)
|
||||
if not match:
|
||||
print("❌ 无法解析Node.js版本")
|
||||
return False
|
||||
|
||||
version = int(match.group(1))
|
||||
if version >= 20:
|
||||
print(f"✅ Node.js版本 {version} (满足20+要求)")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Node.js版本 {version} (需要20+)")
|
||||
return False
|
||||
|
||||
def check_pnpm() -> bool:
|
||||
"""检查PNPM 9+是否安装"""
|
||||
print("\n检查PNPM 9+...")
|
||||
rc, output = run_command("pnpm -v")
|
||||
if rc != 0:
|
||||
print("❌ PNPM未安装")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 处理可能的版本号格式:v9.0.0 或 9.0.0 或 9
|
||||
version_str = output.strip().lstrip('v').split('.')[0]
|
||||
version = int(version_str)
|
||||
|
||||
if version >= 9:
|
||||
print(f"✅ PNPM版本 {output.strip()} (满足9+要求)")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ PNPM版本 {output.strip()} (需要9+)")
|
||||
return False
|
||||
except (ValueError, IndexError):
|
||||
print(f"❌ 无法解析PNPM版本: {output.strip()}")
|
||||
return False
|
||||
|
||||
def check_redis_connection() -> bool:
|
||||
"""检查Redis连接"""
|
||||
print("\n检查Redis连接...")
|
||||
print("⚠️ 请确保已配置Redis连接信息并在jeecg-boot项目中正确配置")
|
||||
print("⚠️ 此检查需要根据实际项目配置进行验证")
|
||||
print("⚠️ 配置文件位置: jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml")
|
||||
return True
|
||||
|
||||
def check_mysql_connection() -> bool:
|
||||
"""检查MySQL连接"""
|
||||
print("\n检查MySQL连接...")
|
||||
print("⚠️ 请确保已配置MySQL连接信息并在jeecg-boot项目中正确配置")
|
||||
print("⚠️ 此检查需要根据实际项目配置进行验证")
|
||||
print("⚠️ 配置文件位置: jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml")
|
||||
return True
|
||||
|
||||
def print_mysql_config():
|
||||
"""打印MySQL配置并提示需要修改的位置"""
|
||||
print("\nMySQL配置参考 (请检查以下配置是否正确):")
|
||||
print("""
|
||||
spring.datasource.dynamic.datasource:
|
||||
master:
|
||||
url: jdbc:mysql://127.0.0.1:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
|
||||
username: root # ← 可能需要修改
|
||||
password: root # ← 可能需要修改
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
""")
|
||||
|
||||
def check_ai_vector_db() -> bool:
|
||||
"""检查AI向量库(pgvector)配置"""
|
||||
print("\n检查AI知识库向量库配置...")
|
||||
print("⚠️ 如果需要使用AI知识库功能,请配置pgvector向量库")
|
||||
print("⚠️ 配置文件位置: jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml")
|
||||
print("\n配置参考:")
|
||||
print("""
|
||||
jeecg.ai-rag:
|
||||
embed-store:
|
||||
host: 127.0.0.1 # ← 可能需要修改
|
||||
port: 5432 # ← 可能需要修改
|
||||
database: postgres # ← 可能需要修改
|
||||
user: postgres # ← 可能需要修改
|
||||
password: postgres # ← 可能需要修改
|
||||
table: embeddings # ← 可能需要修改
|
||||
""")
|
||||
print("⚠️ 注意: 请确保已安装PostgreSQL并添加pgvector扩展!docker安装参考:https://help.jeecg.com/aigc/config")
|
||||
return True
|
||||
|
||||
def check_ai_config() -> bool:
|
||||
"""检查AI账号配置"""
|
||||
print("\n检查AI功能配置...")
|
||||
print("⚠️ 如果需要使用AI聊天功能,请配置AI账号信息")
|
||||
print("⚠️ 配置文件位置: jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml")
|
||||
print("\n配置参考:")
|
||||
print("""
|
||||
jeecg:
|
||||
# AI集成
|
||||
ai-chat:
|
||||
enabled: true # ← 启用AI功能
|
||||
model: deepseek-chat # ← 模型名称
|
||||
apiKey: ?? # ← 必须修改为您的API Key
|
||||
apiHost: https://api.deepseek.com/v1 # ← API地址
|
||||
timeout: 60 # ← 超时时间(秒)
|
||||
""")
|
||||
print("⚠️ 注意: 请确保已获取有效的API Key并正确配置!AI账号注册获取参考: https://help.jeecg.com/java/deepSeekSupport")
|
||||
return True
|
||||
|
||||
|
||||
def print_redis_config():
|
||||
"""打印Redis配置并提示需要修改的位置"""
|
||||
print("\nRedis配置参考 (请检查以下配置是否正确):")
|
||||
print("""
|
||||
spring.redis:
|
||||
database: 0
|
||||
host: 127.0.0.1 # ← 可能需要修改
|
||||
port: 6379 # ← 可能需要修改
|
||||
password: '' # ← 如果需要密码请修改
|
||||
""")
|
||||
|
||||
def main():
|
||||
print("="*50)
|
||||
print("JeecgBoot 运行环境检查脚本")
|
||||
print("="*50)
|
||||
|
||||
all_checks_passed = True
|
||||
|
||||
# 检查各项依赖
|
||||
if not check_java():
|
||||
all_checks_passed = False
|
||||
|
||||
if not check_maven():
|
||||
all_checks_passed = False
|
||||
|
||||
if not check_node():
|
||||
all_checks_passed = False
|
||||
|
||||
if not check_pnpm():
|
||||
all_checks_passed = False
|
||||
|
||||
# 数据库提示
|
||||
print("="*50)
|
||||
check_redis_connection()
|
||||
print_redis_config()
|
||||
print("="*50)
|
||||
check_mysql_connection()
|
||||
print_mysql_config()
|
||||
print("="*50)
|
||||
check_ai_config()
|
||||
print("="*50)
|
||||
check_ai_vector_db()
|
||||
|
||||
print("\n" + "="*50)
|
||||
if all_checks_passed:
|
||||
print("✅ 所有基础环境检查通过")
|
||||
print("⚠️ 注意: 请确保Redis和MySQL、AI账号、向量库pgvector 已正确配置并连接成功")
|
||||
else:
|
||||
print("❌ 部分环境检查未通过,请根据上述提示解决问题")
|
||||
|
||||
print("="*50)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
input("\n按回车键退出...") # 等待用户输入
|
||||
@ -18,20 +18,33 @@ services:
|
||||
--max_allowed_packet=128M
|
||||
--default-authentication-plugin=caching_sha2_password
|
||||
ports:
|
||||
- 3306:3306
|
||||
- 13306:3306
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-redis:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/jeecgdocker/redis:5.0
|
||||
ports:
|
||||
- 6379:6379
|
||||
# ports:
|
||||
# - 6379:6379
|
||||
restart: always
|
||||
hostname: jeecg-boot-redis
|
||||
container_name: jeecg-boot-redis
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-pgvector:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/jeecgdocker/pgvector
|
||||
container_name: jeecg-boot-pgvector
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: vector_db
|
||||
ports:
|
||||
- 5432:5432
|
||||
restart: always
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-nacos:
|
||||
restart: always
|
||||
build:
|
||||
@ -96,31 +109,37 @@ services:
|
||||
# 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-boot-sentinel:
|
||||
restart: on-failure
|
||||
build:
|
||||
context: ./jeecg-boot/jeecg-server-cloud/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
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-xxljob:
|
||||
build:
|
||||
context: ./jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob
|
||||
ports:
|
||||
- 9080:9080
|
||||
container_name: jeecg-boot-xxljob
|
||||
hostname: jeecg-boot-xxljob
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-vue:
|
||||
build:
|
||||
context: ./jeecgboot-vue3
|
||||
dockerfile: Dockerfile.cloud
|
||||
container_name: jeecgboot-vue3-nginx
|
||||
image: jeecgboot-vue3
|
||||
depends_on:
|
||||
|
||||
@ -18,20 +18,33 @@ services:
|
||||
--max_allowed_packet=128M
|
||||
--default-authentication-plugin=caching_sha2_password
|
||||
ports:
|
||||
- 3306:3306
|
||||
- 13306:3306
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-redis:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/jeecgdocker/redis:5.0
|
||||
ports:
|
||||
- 6379:6379
|
||||
# ports:
|
||||
# - 3792:6379
|
||||
restart: always
|
||||
hostname: jeecg-boot-redis
|
||||
container_name: jeecg-boot-redis
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-pgvector:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/jeecgdocker/pgvector
|
||||
container_name: jeecg-boot-pgvector
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: vector_db
|
||||
ports:
|
||||
- 5432:5432
|
||||
restart: always
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-system:
|
||||
build:
|
||||
context: ./jeecg-boot/jeecg-module-system/jeecg-system-start
|
||||
|
||||
@ -2,12 +2,12 @@
|
||||
JeecgBoot 低代码开发平台
|
||||
===============
|
||||
|
||||
当前最新版本: 3.8.1(发布日期:2025-06-30)
|
||||
当前最新版本: 3.8.3(发布日期:2025-09-22)
|
||||
|
||||
|
||||
[](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)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
#
|
||||
# XXL-JOB v2.2.0
|
||||
# XXL-JOB v2.4.0
|
||||
# Copyright (c) 2015-present, xuxueli.
|
||||
|
||||
CREATE database if NOT EXISTS `xxl_job` default character set utf8mb4 collate utf8mb4_general_ci;
|
||||
|
||||
BIN
jeecg-boot/db/其他数据库脚本/jeecgboot-oracle11g.dmp
Normal file
BIN
jeecg-boot/db/其他数据库脚本/jeecgboot-oracle11g.dmp
Normal file
Binary file not shown.
29702
jeecg-boot/db/其他数据库脚本/jeecgboot-oracle11g.sql
Normal file
29702
jeecg-boot/db/其他数据库脚本/jeecgboot-oracle11g.sql
Normal file
File diff suppressed because one or more lines are too long
25749
jeecg-boot/db/其他数据库脚本/jeecgboot-postgresql17.sql
Normal file
25749
jeecg-boot/db/其他数据库脚本/jeecgboot-postgresql17.sql
Normal file
File diff suppressed because one or more lines are too long
48117
jeecg-boot/db/其他数据库脚本/jeecgboot-sqlserver2017.sql
Normal file
48117
jeecg-boot/db/其他数据库脚本/jeecgboot-sqlserver2017.sql
Normal file
File diff suppressed because one or more lines are too long
5
jeecg-boot/db/其他数据库脚本/oracle11g dmp说明.txt
Normal file
5
jeecg-boot/db/其他数据库脚本/oracle11g dmp说明.txt
Normal file
@ -0,0 +1,5 @@
|
||||
oracle导出编码: export NLS_LANG=AMERICAN_AMERICA.ZHS16GBK
|
||||
|
||||
导出用户: jeecgbootos
|
||||
|
||||
导入命令: imp scott/tiger@orcl file=jeecgboot-oracle11g.dmp
|
||||
@ -18,20 +18,47 @@ services:
|
||||
--max_allowed_packet=128M
|
||||
--default-authentication-plugin=caching_sha2_password
|
||||
ports:
|
||||
- 3306:3306
|
||||
- 13306:3306
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-redis:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/jeecgdocker/redis:5.0
|
||||
ports:
|
||||
- 6379:6379
|
||||
# ports:
|
||||
# - 6379:6379
|
||||
restart: always
|
||||
hostname: jeecg-boot-redis
|
||||
container_name: jeecg-boot-redis
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-pgvector:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/jeecgdocker/pgvector
|
||||
container_name: jeecg-boot-pgvector
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: vector_db
|
||||
# ports:
|
||||
# - 5432:5432
|
||||
restart: always
|
||||
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
|
||||
# networks:
|
||||
# - jeecg-boot
|
||||
|
||||
jeecg-boot-system:
|
||||
build:
|
||||
context: ./jeecg-module-system/jeecg-system-start
|
||||
@ -46,6 +73,8 @@ services:
|
||||
- 8080:8080
|
||||
networks:
|
||||
- jeecg-boot
|
||||
volumes:
|
||||
- ./config:/jeecg-boot/config
|
||||
|
||||
networks:
|
||||
jeecg-boot:
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-parent</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<version>3.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jeecg-boot-base-core</artifactId>
|
||||
@ -49,16 +49,6 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
<!-- websocket -->
|
||||
<dependency>
|
||||
@ -111,10 +101,15 @@
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<!-- minidao -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework</groupId>
|
||||
<artifactId>minidao-spring-boot-starter</artifactId>
|
||||
<artifactId>minidao-spring-boot-starter-jsqlparser-4.9</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- druid -->
|
||||
@ -142,7 +137,7 @@
|
||||
<!-- sqlserver-->
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>sqljdbc4</artifactId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>${sqljdbc4.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
@ -164,13 +159,13 @@
|
||||
<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 -->
|
||||
<dependency>
|
||||
<groupId>com.dameng</groupId>
|
||||
<artifactId>Dm8JdbcDriver18</artifactId>
|
||||
<artifactId>DmJdbcDriver18</artifactId>
|
||||
<version>${dm8.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -244,17 +239,6 @@
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework</groupId>
|
||||
<artifactId>autopoi-web</artifactId>
|
||||
<version>${autopoi-web.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>xercesImpl</artifactId>
|
||||
<groupId>xerces</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xerces</groupId>
|
||||
@ -320,4 +304,4 @@
|
||||
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.jeecg.common.api;
|
||||
|
||||
import org.jeecg.common.api.dto.AiragFlowDTO;
|
||||
import org.jeecg.common.system.vo.*;
|
||||
|
||||
import java.util.List;
|
||||
@ -144,4 +145,15 @@ 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]解决分布式下表字典跨库无法查询问题------------
|
||||
|
||||
/**
|
||||
* 16 运行AIRag流程
|
||||
* for [QQYUN-13634]在baseapi里面封装方法,方便其他模块调用
|
||||
*
|
||||
* @param airagFlowDTO
|
||||
* @return 流程执行结果,可能是String或者Map
|
||||
* @author chenrui
|
||||
* @date 2025/9/2 11:43
|
||||
*/
|
||||
Object runAiragFlow(AiragFlowDTO airagFlowDTO);
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
package org.jeecg.common.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 调用AI流程入参
|
||||
* for [QQYUN-13634]在baseapi里面封装方法,方便其他模块调用
|
||||
* @author chenrui
|
||||
* @date 2025/9/2 14:11
|
||||
*/
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public class AiragFlowDTO implements Serializable {
|
||||
|
||||
|
||||
private static final long serialVersionUID = 7431775881170684867L;
|
||||
|
||||
/**
|
||||
* 流程id
|
||||
*/
|
||||
private String flowId;
|
||||
|
||||
|
||||
/**
|
||||
* 输入参数
|
||||
*/
|
||||
private Map<String, Object> inputParams;
|
||||
}
|
||||
@ -91,6 +91,12 @@ public class MessageDTO implements Serializable {
|
||||
private Boolean isTimeJob = false;
|
||||
|
||||
//---【邮件相关参数】-------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 枚举:org.jeecg.common.constant.enums.NoticeTypeEnum
|
||||
* 通知类型(system:系统消息、file:知识库、flow:流程、plan:日程计划、meeting:会议)
|
||||
*/
|
||||
private String noticeType;
|
||||
|
||||
public MessageDTO(){
|
||||
}
|
||||
|
||||
@ -143,7 +143,7 @@ public class AutoLogAspect {
|
||||
// https://my.oschina.net/mengzhang6/blog/2395893
|
||||
Object[] arguments = new Object[paramsArray.length];
|
||||
for (int i = 0; i < paramsArray.length; i++) {
|
||||
if (paramsArray[i] instanceof BindingResult || paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile) {
|
||||
if (paramsArray[i] instanceof BindingResult || paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile || paramsArray[i] instanceof MultipartFile[]) {
|
||||
//ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
|
||||
//ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
|
||||
continue;
|
||||
|
||||
@ -303,6 +303,11 @@ public interface CommonConstant {
|
||||
*/
|
||||
String SYS_USER_ID_MAPPING_CACHE = "sys:cache:user:id_mapping";
|
||||
|
||||
/**
|
||||
* 系统角色管理员编码
|
||||
*/
|
||||
String SYS_ROLE_ADMIN = "admin";
|
||||
|
||||
/**
|
||||
* 考勤补卡业务状态 (1:同意 2:不同意)
|
||||
*/
|
||||
@ -428,6 +433,11 @@ public interface CommonConstant {
|
||||
*/
|
||||
String NOTICE_MSG_BUS_TYPE = "NOTICE_MSG_BUS_TYPE";
|
||||
|
||||
/**
|
||||
* 通知类型,用于区分来源 file 知识 flow 流程 plan 日程 system 系统消息
|
||||
*/
|
||||
String NOTICE_TYPE = "noticeType";
|
||||
|
||||
/**
|
||||
* 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值
|
||||
*/
|
||||
@ -629,4 +639,74 @@ public interface CommonConstant {
|
||||
* 修改手机号验证码请求次数超出
|
||||
*/
|
||||
Integer PHONE_SMS_FAIL_CODE = 40002;
|
||||
|
||||
|
||||
/**
|
||||
* 自定义首页关联关系(ROLE:表示角色 USER:表示用户 DEFAULT:默认首页)
|
||||
*
|
||||
*/
|
||||
String HOME_RELATION_ROLE = "ROLE";
|
||||
String HOME_RELATION_USER = "USER";
|
||||
String HOME_RELATION_DEFAULT = "DEFAULT";
|
||||
|
||||
/**
|
||||
* 是否置顶(0否 1是)
|
||||
*/
|
||||
Integer IZ_TOP_1 = 1;
|
||||
Integer IZ_TOP_0 = 0;
|
||||
|
||||
|
||||
//关注流程缓存前缀
|
||||
String FLOW_FOCUS_NOTICE_PREFIX = "flow:runtimeData:focus:notice:";
|
||||
//任务缓办时间缓存前缀
|
||||
String FLOW_TASK_DELAY_PREFIX = "flow:runtimeData:task:delay:";
|
||||
/**
|
||||
* 用户代理类型:离职:quit 代理:agent
|
||||
*/
|
||||
String USER_AGENT_TYPE_QUIT = "quit";
|
||||
String USER_AGENT_TYPE_AGENT = "agent";
|
||||
/**
|
||||
* 督办流程首节点任务taskKey
|
||||
*/
|
||||
String SUPERVISE_FIRST_TASK_KEY = "Task_1bhxpt0";
|
||||
|
||||
/**
|
||||
* wps模板预览数据缓存前缀
|
||||
*/
|
||||
String EOA_WPS_TEMPLATE_VIEW_DATA ="eoa:wps:templateViewData:";
|
||||
|
||||
/**
|
||||
* wps模板预览版本号缓存前缀
|
||||
*/
|
||||
String EOA_WPS_TEMPLATE_VIEW_VERSION ="eoa:wps:templateViewVersion:";
|
||||
/**
|
||||
* 表单设计器oa新增字段
|
||||
* x_oa_timeout_date:逾期时间
|
||||
* x_oa_archive_status:归档状态
|
||||
*/
|
||||
String X_OA_TIMEOUT_DATE ="x_oa_timeout_date";
|
||||
String X_OA_ARCHIVE_STATUS ="x_oa_archive_status";
|
||||
/**
|
||||
* 流程状态
|
||||
* 待提交: 1
|
||||
* 处理中: 2
|
||||
* 已完成: 3
|
||||
* 已作废: 4
|
||||
* 已挂起: 5
|
||||
*/
|
||||
String BPM_STATUS_1 ="1";
|
||||
String BPM_STATUS_2 ="2";
|
||||
String BPM_STATUS_3 ="3";
|
||||
String BPM_STATUS_4 ="4";
|
||||
String BPM_STATUS_5 ="5";
|
||||
|
||||
/**
|
||||
* 默认租户产品包
|
||||
*/
|
||||
String TENANT_PACK_DEFAULT = "default";
|
||||
|
||||
/**
|
||||
* 部门名称redisKey(全路径)
|
||||
*/
|
||||
String DEPART_NAME_REDIS_KEY_PRE = "sys:cache:departPathName:";
|
||||
}
|
||||
|
||||
@ -4,6 +4,20 @@ package org.jeecg.common.constant;
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
public interface DataBaseConstant {
|
||||
|
||||
/**
|
||||
* 内置的系统变量键列表
|
||||
*/
|
||||
public static final String[] SYSTEM_KEYS = {
|
||||
DataBaseConstant.SYS_ORG_CODE, DataBaseConstant.SYS_ORG_CODE_TABLE, DataBaseConstant.SYS_MULTI_ORG_CODE,
|
||||
DataBaseConstant.SYS_MULTI_ORG_CODE_TABLE, DataBaseConstant.SYS_ORG_ID, DataBaseConstant.SYS_ORG_ID_TABLE,
|
||||
DataBaseConstant.SYS_ROLE_CODE, DataBaseConstant.SYS_ROLE_CODE_TABLE, DataBaseConstant.SYS_USER_CODE,
|
||||
DataBaseConstant.SYS_USER_CODE_TABLE, DataBaseConstant.SYS_USER_ID, DataBaseConstant.SYS_USER_ID_TABLE,
|
||||
DataBaseConstant.SYS_USER_NAME, DataBaseConstant.SYS_USER_NAME_TABLE, DataBaseConstant.SYS_DATE,
|
||||
DataBaseConstant.SYS_DATE_TABLE, DataBaseConstant.SYS_TIME, DataBaseConstant.SYS_TIME_TABLE,
|
||||
DataBaseConstant.SYS_BASE_PATH
|
||||
};
|
||||
|
||||
//*********数据库类型****************************************
|
||||
|
||||
/**MYSQL数据库*/
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
package org.jeecg.common.constant;
|
||||
|
||||
/**
|
||||
* @Description: 密码常量类
|
||||
*
|
||||
* @author: wangshuai
|
||||
* @date: 2025/8/27 20:10
|
||||
*/
|
||||
public interface PasswordConstant {
|
||||
|
||||
/**
|
||||
* 导入用户默认密码
|
||||
*/
|
||||
String DEFAULT_PASSWORD = "123456";
|
||||
}
|
||||
@ -121,7 +121,7 @@ public class ProvinceCityArea {
|
||||
|
||||
public void getAreaByCode(String code,List<String> ls){
|
||||
for(Area area: areaList){
|
||||
if(area.getId().equals(code)){
|
||||
if(null != area && area.getId().equals(code)){
|
||||
String pid = area.getPid();
|
||||
ls.add(0,area.getText());
|
||||
getAreaByCode(pid,ls);
|
||||
|
||||
@ -0,0 +1,97 @@
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
|
||||
/**
|
||||
* @Description: 部门类型枚举类
|
||||
*
|
||||
* @author: wangshuai
|
||||
* @date: 2025/8/19 21:37
|
||||
*/
|
||||
public enum DepartCategoryEnum {
|
||||
|
||||
DEPART_CATEGORY_COMPANY("部门类型:公司","公司","1"),
|
||||
DEPART_CATEGORY_DEPART("部门类型:部门","部门","2"),
|
||||
DEPART_CATEGORY_POST("部门类型:岗位","岗位","3"),
|
||||
DEPART_CATEGORY_SUB_COMPANY("部门类型:子公司","子公司","4");
|
||||
|
||||
DepartCategoryEnum(String described, String name, String value) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
this.described = described;
|
||||
}
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String described;
|
||||
/**
|
||||
* 值
|
||||
*/
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
public String getDescribed() {
|
||||
return described;
|
||||
}
|
||||
|
||||
public void setDescribed(String described) {
|
||||
this.described = described;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据值获取名称
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static String getNameByValue(String value){
|
||||
if (oConvertUtils.isEmpty(value)) {
|
||||
return null;
|
||||
}
|
||||
for (DepartCategoryEnum val : values()) {
|
||||
if (val.getValue().equals(value)) {
|
||||
return val.getName();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称获取值
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public static String getValueByName(String name){
|
||||
if (oConvertUtils.isEmpty(name)) {
|
||||
return null;
|
||||
}
|
||||
for (DepartCategoryEnum val : values()) {
|
||||
if (val.getName().equals(name)) {
|
||||
return val.getValue();
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,10 @@ public enum EmailTemplateEnum {
|
||||
* 流程催办
|
||||
*/
|
||||
BPM_CUIBAN_EMAIL("bpm_cuiban_email", "/templates/email/bpm_cuiban_email.ftl"),
|
||||
/**
|
||||
* 流程抄送
|
||||
*/
|
||||
BPM_CC_EMAIL("bpm_cc_email", "/templates/email/bpm_cc_email.ftl"),
|
||||
/**
|
||||
* 流程新任务
|
||||
*/
|
||||
|
||||
@ -8,21 +8,30 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@EnumDict("messageType")
|
||||
public enum MessageTypeEnum {
|
||||
|
||||
/** 系统消息 */
|
||||
XT("system", "系统消息"),
|
||||
/** 邮件消息 */
|
||||
YJ("email", "邮件消息"),
|
||||
/** 钉钉消息 */
|
||||
/**
|
||||
* 系统消息
|
||||
*/
|
||||
XT("system", "系统消息"),
|
||||
/**
|
||||
* 邮件消息
|
||||
*/
|
||||
YJ("email", "邮件消息"),
|
||||
/**
|
||||
* 钉钉消息
|
||||
*/
|
||||
DD("dingtalk", "钉钉消息"),
|
||||
/** 企业微信 */
|
||||
/**
|
||||
* 企业微信
|
||||
*/
|
||||
QYWX("wechat_enterprise", "企业微信");
|
||||
|
||||
MessageTypeEnum(String type, String note){
|
||||
MessageTypeEnum(String type, String note) {
|
||||
this.type = type;
|
||||
this.note = note;
|
||||
}
|
||||
@ -56,12 +65,13 @@ public enum MessageTypeEnum {
|
||||
|
||||
/**
|
||||
* 获取字典数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static List<DictModel> getDictList(){
|
||||
public static List<DictModel> getDictList() {
|
||||
List<DictModel> list = new ArrayList<>();
|
||||
DictModel dictModel = null;
|
||||
for(MessageTypeEnum e: MessageTypeEnum.values()){
|
||||
for (MessageTypeEnum e : MessageTypeEnum.values()) {
|
||||
dictModel = new DictModel();
|
||||
dictModel.setValue(e.getType());
|
||||
dictModel.setText(e.getNote());
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
/**
|
||||
* @Description: 文件类型枚举类
|
||||
*
|
||||
* @author: wangshuai
|
||||
* @date: 2025/6/26 17:29
|
||||
*/
|
||||
public enum NoticeTypeEnum {
|
||||
|
||||
//VUE3专用
|
||||
NOTICE_TYPE_FILE("知识库消息","file"),
|
||||
NOTICE_TYPE_FLOW("工作流消息","flow"),
|
||||
NOTICE_TYPE_PLAN("日程消息","plan"),
|
||||
//暂时没用到
|
||||
NOTICE_TYPE_MEETING("会议消息","meeting"),
|
||||
NOTICE_TYPE_SYSTEM("系统消息","system"),
|
||||
/**
|
||||
* 协同工作
|
||||
* for [JHHB-136]【vue3】协同工作系统消息需要添加一个类型
|
||||
*/
|
||||
NOTICE_TYPE_COLLABORATION("协同工作", "collab"),
|
||||
/**
|
||||
* 督办
|
||||
*/
|
||||
NOTICE_TYPE_SUPERVISE("督办管理", "supe");
|
||||
|
||||
/**
|
||||
* 文件类型名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 文件类型值
|
||||
*/
|
||||
private String value;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
NoticeTypeEnum(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聊天通知类型
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static String getChatNoticeType(String value){
|
||||
return value + "Notice";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取通知名称
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static String getNoticeNameByValue(String value){
|
||||
value = value.replace("Notice","");
|
||||
for (NoticeTypeEnum e : NoticeTypeEnum.values()) {
|
||||
if (e.getValue().equals(value)) {
|
||||
return e.getName();
|
||||
}
|
||||
}
|
||||
return "系统消息";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,180 @@
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 职级枚举类
|
||||
*
|
||||
* 注意:此枚举仅适用于天津临港控股OA项目,职级的名称和等级均为写死(需要与数据库配置一致)
|
||||
* @date 2025-08-26
|
||||
* @author scott
|
||||
*/
|
||||
public enum PositionLevelEnum {
|
||||
|
||||
// 领导层级(等级1-3)
|
||||
CHAIRMAN("董事长", 1, PositionType.LEADER),
|
||||
GENERAL_MANAGER("总经理", 2, PositionType.LEADER),
|
||||
VICE_GENERAL_MANAGER("副总经理", 3, PositionType.LEADER),
|
||||
|
||||
// 职员层级(等级4-6)
|
||||
MINISTER("部长", 4, PositionType.STAFF),
|
||||
VICE_MINISTER("副部长", 5, PositionType.STAFF),
|
||||
STAFF("职员", 6, PositionType.STAFF);
|
||||
|
||||
private final String name;
|
||||
private final int level;
|
||||
private final PositionType type;
|
||||
|
||||
PositionLevelEnum(String name, int level, PositionType type) {
|
||||
this.name = name;
|
||||
this.level = level;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public PositionType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 职级类型枚举
|
||||
*/
|
||||
public enum PositionType {
|
||||
STAFF("职员层级"),
|
||||
LEADER("领导层级");
|
||||
|
||||
private final String desc;
|
||||
|
||||
PositionType(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据职级名称获取枚举
|
||||
* @param name 职级名称
|
||||
* @return 职级枚举
|
||||
*/
|
||||
public static PositionLevelEnum getByName(String name) {
|
||||
for (PositionLevelEnum position : values()) {
|
||||
if (position.getName().equals(name)) {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据职级等级获取枚举
|
||||
* @param level 职级等级
|
||||
* @return 职级枚举
|
||||
*/
|
||||
public static PositionLevelEnum getByLevel(int level) {
|
||||
for (PositionLevelEnum position : values()) {
|
||||
if (position.getLevel() == level) {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据职级名称判断是否为职员层级
|
||||
* @param name 职级名称
|
||||
* @return true-职员层级,false-非职员层级
|
||||
*/
|
||||
public static boolean isStaffLevel(String name) {
|
||||
PositionLevelEnum position = getByName(name);
|
||||
return position != null && position.getType() == PositionType.STAFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据职级名称判断是否为领导层级
|
||||
* @param name 职级名称
|
||||
* @return true-领导层级,false-非领导层级
|
||||
*/
|
||||
public static boolean isLeaderLevel(String name) {
|
||||
PositionLevelEnum position = getByName(name);
|
||||
return position != null && position.getType() == PositionType.LEADER;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个职级的等级高低
|
||||
* @param name1 职级名称1
|
||||
* @param name2 职级名称2
|
||||
* @return 正数表示name1等级更高,负数表示name2等级更高,0表示等级相同
|
||||
*/
|
||||
public static int compareLevel(String name1, String name2) {
|
||||
PositionLevelEnum pos1 = getByName(name1);
|
||||
PositionLevelEnum pos2 = getByName(name2);
|
||||
|
||||
if (pos1 == null || pos2 == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 等级数字越小代表职级越高
|
||||
return pos2.getLevel() - pos1.getLevel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为更高等级
|
||||
* @param currentName 当前职级名称
|
||||
* @param targetName 目标职级名称
|
||||
* @return true-目标职级更高,false-目标职级不高于当前职级
|
||||
*/
|
||||
public static boolean isHigherLevel(String currentName, String targetName) {
|
||||
return compareLevel(targetName, currentName) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有职员层级名称
|
||||
* @return 职员层级名称列表
|
||||
*/
|
||||
public static List<String> getStaffLevelNames() {
|
||||
return Arrays.asList(MINISTER.getName(), VICE_MINISTER.getName(), STAFF.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有领导层级名称
|
||||
* @return 领导层级名称列表
|
||||
*/
|
||||
public static List<String> getLeaderLevelNames() {
|
||||
return Arrays.asList(CHAIRMAN.getName(), GENERAL_MANAGER.getName(), VICE_GENERAL_MANAGER.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有职级名称(按等级排序)
|
||||
* @return 所有职级名称列表
|
||||
*/
|
||||
public static List<String> getAllPositionNames() {
|
||||
return Arrays.asList(
|
||||
CHAIRMAN.getName(), GENERAL_MANAGER.getName(), VICE_GENERAL_MANAGER.getName(),
|
||||
MINISTER.getName(), VICE_MINISTER.getName(), STAFF.getName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定等级范围的职级
|
||||
* @param minLevel 最小等级
|
||||
* @param maxLevel 最大等级
|
||||
* @return 职级名称列表
|
||||
*/
|
||||
public static List<String> getPositionsByLevelRange(int minLevel, int maxLevel) {
|
||||
return Arrays.stream(values())
|
||||
.filter(p -> p.getLevel() >= minLevel && p.getLevel() <= maxLevel)
|
||||
.map(PositionLevelEnum::getName)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
}
|
||||
@ -23,7 +23,25 @@ public enum SysAnnmentTypeEnum {
|
||||
/**
|
||||
* 邀请用户跳转到个人设置
|
||||
*/
|
||||
TENANT_INVITE("tenant_invite", "url", "/system/usersetting");
|
||||
TENANT_INVITE("tenant_invite", "url", "/system/usersetting"),
|
||||
/**
|
||||
* 协同工作-待办通知
|
||||
* for [JHHB-136]【vue3】协同工作系统消息需要添加一个类型
|
||||
*/
|
||||
EOA_CO_NOTIFY("eoa_co_notify", "url", "/collaboration/pending"),
|
||||
/**
|
||||
* 协同工作-催办通知
|
||||
* for [JHHB-136]【vue3】协同工作系统消息需要添加一个类型
|
||||
*/
|
||||
EOA_CO_REMIND("eoa_co_remind", "url", "/collaboration/pending"),
|
||||
/**
|
||||
* 督办管理-催办
|
||||
*/
|
||||
EOA_SUP_REMIND("eoa_sup_remind", "url", "/superivse/list"),
|
||||
/**
|
||||
* 督办管理-通知
|
||||
*/
|
||||
EOA_SUP_NOTIFY("eoa_sup_notify", "url", "/superivse/list");
|
||||
|
||||
/**
|
||||
* 业务类型(email:邮件 bpm:流程)
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package org.jeecg.common.exception;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import io.undertow.server.RequestTooBigException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
@ -180,7 +179,7 @@ public class JeecgBootExceptionHandler {
|
||||
@ExceptionHandler(MultipartException.class)
|
||||
public Result<?> handleMaxUploadSizeExceededException(MultipartException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof IllegalStateException && cause.getCause() instanceof RequestTooBigException) {
|
||||
if (cause instanceof IllegalStateException) {
|
||||
log.error("文件大小超出限制: {}", cause.getMessage(), e);
|
||||
addSysLog(e);
|
||||
return Result.error("文件大小超出限制, 请压缩或降低文件质量!");
|
||||
@ -291,7 +290,7 @@ public class JeecgBootExceptionHandler {
|
||||
boolean isTooBigException = false;
|
||||
if(e instanceof MultipartException){
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof IllegalStateException && cause.getCause() instanceof RequestTooBigException){
|
||||
if (cause instanceof IllegalStateException){
|
||||
isTooBigException = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,11 @@ import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 将枚举类转化成字典数据
|
||||
*
|
||||
* <<使用说明>>
|
||||
* 1. 枚举类需以 `Enum` 结尾,并且在类上添加 `@EnumDict` 注解。
|
||||
* 2. 需要手动将枚举类所在包路径** 添加到 `org.jeecg.common.system.util.ResourceUtil.BASE_SCAN_PACKAGES` 配置数组中。
|
||||
*
|
||||
* @Author taoYan
|
||||
* @Date 2022/7/8 10:34
|
||||
**/
|
||||
|
||||
@ -18,6 +18,7 @@ import org.jeecgframework.poi.excel.entity.ExportParams;
|
||||
import org.jeecgframework.poi.excel.entity.ImportParams;
|
||||
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
|
||||
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
|
||||
import org.jeecgframework.poi.handler.inter.IExcelExportServer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
@ -127,6 +128,53 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
return mv;
|
||||
}
|
||||
|
||||
/**
|
||||
* 大数据导出
|
||||
* @param request
|
||||
* @param object
|
||||
* @param clazz
|
||||
* @param title
|
||||
* @param pageSize 每次查询的数据量
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/11 16:11
|
||||
*/
|
||||
protected ModelAndView exportXlsForBigData(HttpServletRequest request, T object, Class<T> clazz, String title,Integer pageSize) {
|
||||
// 组装查询条件
|
||||
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
// 计算分页数
|
||||
double total = service.count();
|
||||
int count = (int) Math.ceil(total / pageSize);
|
||||
// 过滤选中数据
|
||||
String selections = request.getParameter("selections");
|
||||
if (oConvertUtils.isNotEmpty(selections)) {
|
||||
List<String> selectionList = Arrays.asList(selections.split(","));
|
||||
queryWrapper.in("id", selectionList);
|
||||
}
|
||||
|
||||
// 定义IExcelExportServer
|
||||
IExcelExportServer excelExportServer = (queryParams, pageNum) -> {
|
||||
if (pageNum > count) {
|
||||
return null;
|
||||
}
|
||||
Page<T> page = new Page<T>(pageNum, pageSize);
|
||||
IPage<T> pageList = service.page(page, (QueryWrapper<T>) queryParams);
|
||||
return new ArrayList<>(pageList.getRecords());
|
||||
};
|
||||
|
||||
// AutoPoi 导出Excel
|
||||
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
|
||||
//此处设置的filename无效 ,前端会重更新设置一下
|
||||
mv.addObject(NormalExcelConstants.FILE_NAME, title);
|
||||
mv.addObject(NormalExcelConstants.CLASS, clazz);
|
||||
ExportParams exportParams = new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title, jeecgBaseConfig.getPath().getUpload());
|
||||
mv.addObject(NormalExcelConstants.PARAMS, exportParams);
|
||||
mv.addObject(NormalExcelConstants.EXPORT_SERVER, excelExportServer);
|
||||
mv.addObject(NormalExcelConstants.QUERY_PARAMS, queryWrapper);
|
||||
return mv;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据权限导出excel,传入导出字段参数
|
||||
|
||||
@ -11,6 +11,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||
import org.apache.commons.beanutils.PropertyUtils;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.DataBaseConstant;
|
||||
@ -257,8 +258,69 @@ public class QueryGenerator {
|
||||
if(parameterMap!=null&& parameterMap.containsKey(ORDER_TYPE)) {
|
||||
order = parameterMap.get(ORDER_TYPE)[0];
|
||||
}
|
||||
log.debug("排序规则>>列:" + column + ",排序方式:" + order);
|
||||
|
||||
if(oConvertUtils.isNotEmpty(column)){
|
||||
log.info("单字段排序规则>> column:" + column + ",排序方式:" + order);
|
||||
}
|
||||
|
||||
// 1. 列表多字段排序优先
|
||||
if(parameterMap!=null&& parameterMap.containsKey("sortInfoString")) {
|
||||
// 多字段排序
|
||||
String sortInfoString = parameterMap.get("sortInfoString")[0];
|
||||
log.info("多字段排序规则>> sortInfoString:" + sortInfoString);
|
||||
List<OrderItem> orderItemList = SqlConcatUtil.getQueryConditionOrders(column, order, sortInfoString);
|
||||
log.info(orderItemList.toString());
|
||||
if (orderItemList != null && !orderItemList.isEmpty()) {
|
||||
for (OrderItem item : orderItemList) {
|
||||
// 一、获取排序数据库字段
|
||||
String columnName = item.getColumn();
|
||||
// 1.字典字段,去掉字典翻译文本后缀
|
||||
if(columnName.endsWith(CommonConstant.DICT_TEXT_SUFFIX)) {
|
||||
columnName = columnName.substring(0, column.lastIndexOf(CommonConstant.DICT_TEXT_SUFFIX));
|
||||
}
|
||||
// 2.实体驼峰字段转为数据库字段
|
||||
columnName = SqlInjectionUtil.getSqlInjectSortField(columnName);
|
||||
|
||||
// 二、设置字段排序规则
|
||||
if (item.isAsc()) {
|
||||
queryWrapper.orderByAsc(columnName);
|
||||
} else {
|
||||
queryWrapper.orderByDesc(columnName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 列表单字段默认排序
|
||||
if(oConvertUtils.isEmpty(column) && parameterMap!=null&& parameterMap.containsKey("defSortString")) {
|
||||
// 多字段排序
|
||||
String sortInfoString = parameterMap.get("defSortString")[0];
|
||||
log.info("默认多字段排序规则>> defSortString:" + sortInfoString);
|
||||
List<OrderItem> orderItemList = SqlConcatUtil.getQueryConditionOrders(column, order, sortInfoString);
|
||||
log.info(orderItemList.toString());
|
||||
if (orderItemList != null && !orderItemList.isEmpty()) {
|
||||
for (OrderItem item : orderItemList) {
|
||||
// 一、获取排序数据库字段
|
||||
String columnName = item.getColumn();
|
||||
// 1.字典字段,去掉字典翻译文本后缀
|
||||
if(columnName.endsWith(CommonConstant.DICT_TEXT_SUFFIX)) {
|
||||
columnName = columnName.substring(0, column.lastIndexOf(CommonConstant.DICT_TEXT_SUFFIX));
|
||||
}
|
||||
// 2.实体驼峰字段转为数据库字段
|
||||
columnName = SqlInjectionUtil.getSqlInjectSortField(columnName);
|
||||
|
||||
// 二、设置字段排序规则
|
||||
if (item.isAsc()) {
|
||||
queryWrapper.orderByAsc(columnName);
|
||||
} else {
|
||||
queryWrapper.orderByDesc(columnName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//update-begin-author:scott date:2022-11-07 for:避免用户自定义表无默认字段{创建时间},导致排序报错
|
||||
//TODO 避免用户自定义表无默认字段创建时间,导致排序报错
|
||||
if(DataBaseConstant.CREATE_TIME.equals(column) && !fieldColumnMap.containsKey(DataBaseConstant.CREATE_TIME)){
|
||||
@ -352,9 +414,11 @@ public class QueryGenerator {
|
||||
}
|
||||
// update-begin-author:sunjianlei date:20220119 for: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and
|
||||
List<QueryCondition> filterConditions = conditions.stream().filter(
|
||||
rule -> oConvertUtils.isNotEmpty(rule.getField())
|
||||
&& oConvertUtils.isNotEmpty(rule.getRule())
|
||||
&& oConvertUtils.isNotEmpty(rule.getVal())
|
||||
rule -> (oConvertUtils.isNotEmpty(rule.getField())
|
||||
&& oConvertUtils.isNotEmpty(rule.getRule())
|
||||
&& oConvertUtils.isNotEmpty(rule.getVal())
|
||||
)
|
||||
|| "empty".equals(rule.getRule())
|
||||
).collect(Collectors.toList());
|
||||
if (filterConditions.size() == 0) {
|
||||
return;
|
||||
@ -365,9 +429,12 @@ public class QueryGenerator {
|
||||
queryWrapper.and(andWrapper -> {
|
||||
for (int i = 0; i < filterConditions.size(); i++) {
|
||||
QueryCondition rule = filterConditions.get(i);
|
||||
if (oConvertUtils.isNotEmpty(rule.getField())
|
||||
&& oConvertUtils.isNotEmpty(rule.getRule())
|
||||
&& oConvertUtils.isNotEmpty(rule.getVal())) {
|
||||
if (
|
||||
(
|
||||
oConvertUtils.isNotEmpty(rule.getField()) && oConvertUtils.isNotEmpty(rule.getRule()) && oConvertUtils.isNotEmpty(rule.getVal())
|
||||
)
|
||||
|| "empty".equals(rule.getRule())
|
||||
) {
|
||||
|
||||
log.debug("SuperQuery ==> " + rule.toString());
|
||||
|
||||
@ -654,7 +721,11 @@ public class QueryGenerator {
|
||||
* @param value 查询条件值
|
||||
*/
|
||||
public static void addEasyQuery(QueryWrapper<?> queryWrapper, String name, QueryRuleEnum rule, Object value) {
|
||||
if (name==null || value == null || rule == null || oConvertUtils.isEmpty(value)) {
|
||||
if (
|
||||
(
|
||||
name==null || value == null || rule == null || oConvertUtils.isEmpty(value)
|
||||
)
|
||||
&& !QueryRuleEnum.EMPTY.equals(rule)) {
|
||||
return;
|
||||
}
|
||||
name = oConvertUtils.camelToUnderline(name);
|
||||
@ -666,6 +737,9 @@ public class QueryGenerator {
|
||||
case GE:
|
||||
queryWrapper.ge(name, value);
|
||||
break;
|
||||
case EMPTY:
|
||||
queryWrapper.isNull(name);
|
||||
break;
|
||||
case LT:
|
||||
queryWrapper.lt(name, value);
|
||||
break;
|
||||
|
||||
@ -49,24 +49,24 @@ public class JwtUtil {
|
||||
* @param code
|
||||
* @param errorMsg
|
||||
*/
|
||||
public static void responseError(ServletResponse response, Integer code, String errorMsg) {
|
||||
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
|
||||
// issues/I4YH95浏览器显示乱码问题
|
||||
httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
|
||||
Result jsonResult = new Result(code, errorMsg);
|
||||
jsonResult.setSuccess(false);
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = httpServletResponse.getOutputStream();
|
||||
httpServletResponse.setCharacterEncoding("UTF-8");
|
||||
httpServletResponse.setStatus(code);
|
||||
os.write(new ObjectMapper().writeValueAsString(jsonResult).getBytes("UTF-8"));
|
||||
os.flush();
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
public static void responseError(HttpServletResponse response, Integer code, String errorMsg) {
|
||||
try {
|
||||
Result jsonResult = new Result(code, errorMsg);
|
||||
jsonResult.setSuccess(false);
|
||||
|
||||
// 设置响应头和内容类型
|
||||
response.setStatus(code);
|
||||
response.setHeader("Content-type", "text/html;charset=UTF-8");
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
// 使用 ObjectMapper 序列化为 JSON 字符串
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String json = objectMapper.writeValueAsString(jsonResult);
|
||||
response.getWriter().write(json);
|
||||
response.getWriter().flush();
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验token是否正确
|
||||
@ -99,7 +99,7 @@ public class JwtUtil {
|
||||
DecodedJWT jwt = JWT.decode(token);
|
||||
return jwt.getClaim("username").asString();
|
||||
} catch (JWTDecodeException e) {
|
||||
log.warn(e.getMessage(), e);
|
||||
log.error(e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,31 +13,33 @@ import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 资源加载工具类
|
||||
* 枚举字典数据 资源加载工具类
|
||||
*
|
||||
* @Author taoYan
|
||||
* @Date 2022/7/8 10:40
|
||||
**/
|
||||
@Slf4j
|
||||
public class ResourceUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 多个包扫描根路径
|
||||
*
|
||||
* 之所以让用户手工配置扫描路径,是为了避免不必要的类加载开销,提升启动性能。
|
||||
* 请务必将所有枚举类所在包路径添加到此配置中。
|
||||
*/
|
||||
private final static String[] BASE_SCAN_PACKAGES = {
|
||||
"org.jeecg.common.constant.enums",
|
||||
"org.jeecg.modules.message.enums"
|
||||
};
|
||||
|
||||
/**
|
||||
* 枚举字典数据
|
||||
*/
|
||||
private final static Map<String, List<DictModel>> enumDictData = new HashMap<>(5);
|
||||
|
||||
/**
|
||||
* 所有java类
|
||||
*/
|
||||
private final static String CLASS_PATTERN="/**/*.class";
|
||||
|
||||
/**
|
||||
* 所有枚举java类
|
||||
*/
|
||||
@ -45,9 +47,9 @@ public class ResourceUtil {
|
||||
private final static String CLASS_ENUM_PATTERN="/**/*Enum.class";
|
||||
|
||||
/**
|
||||
* 包路径 org.jeecg
|
||||
* 初始化状态标识
|
||||
*/
|
||||
private final static String BASE_PACKAGE = "org.jeecg";
|
||||
private static volatile boolean initialized = false;
|
||||
|
||||
/**
|
||||
* 枚举类中获取字典数据的方法名
|
||||
@ -55,59 +57,135 @@ public class ResourceUtil {
|
||||
private final static String METHOD_NAME = "getDictList";
|
||||
|
||||
/**
|
||||
* 获取枚举字典数据
|
||||
* 获取枚举类对应的字典数据 SysDictServiceImpl#queryAllDictItems()
|
||||
* @return
|
||||
*
|
||||
* @return 枚举字典数据
|
||||
*/
|
||||
public static Map<String, List<DictModel>> getEnumDictData(){
|
||||
if(enumDictData.keySet().size()>0){
|
||||
return enumDictData;
|
||||
}
|
||||
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
|
||||
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + CLASS_ENUM_PATTERN;
|
||||
try {
|
||||
Resource[] resources = resourcePatternResolver.getResources(pattern);
|
||||
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
|
||||
for (Resource resource : resources) {
|
||||
MetadataReader reader = readerFactory.getMetadataReader(resource);
|
||||
String classname = reader.getClassMetadata().getClassName();
|
||||
Class<?> clazz = Class.forName(classname);
|
||||
EnumDict enumDict = clazz.getAnnotation(EnumDict.class);
|
||||
if (enumDict != null) {
|
||||
EnumDict annotation = clazz.getAnnotation(EnumDict.class);
|
||||
String key = annotation.value();
|
||||
if(oConvertUtils.isNotEmpty(key)){
|
||||
List<DictModel> list = (List<DictModel>) clazz.getDeclaredMethod(METHOD_NAME).invoke(null);
|
||||
enumDictData.put(key, list);
|
||||
}
|
||||
public static Map<String, List<DictModel>> getEnumDictData() {
|
||||
if (!initialized) {
|
||||
synchronized (ResourceUtil.class) {
|
||||
if (!initialized) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("【枚举字典加载】开始初始化枚举字典数据...");
|
||||
|
||||
initEnumDictData();
|
||||
initialized = true;
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
log.info("【枚举字典加载】枚举字典数据初始化完成,共加载 {} 个字典,总耗时: {}ms", enumDictData.size(), endTime - startTime);
|
||||
}
|
||||
}
|
||||
}catch (Exception e){
|
||||
log.error("获取枚举类字典数据异常", e.getMessage());
|
||||
// e.printStackTrace();
|
||||
}
|
||||
return enumDictData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于后端字典翻译 SysDictServiceImpl#queryManyDictByKeys(java.util.List, java.util.List)
|
||||
* @param dictCodeList
|
||||
* @param keys
|
||||
* @return
|
||||
* 使用多包路径扫描方式初始化枚举字典数据
|
||||
*/
|
||||
public static Map<String, List<DictModel>> queryManyDictByKeys(List<String> dictCodeList, List<String> keys){
|
||||
if(enumDictData.keySet().size()==0){
|
||||
getEnumDictData();
|
||||
private static void initEnumDictData() {
|
||||
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
|
||||
|
||||
long scanStartTime = System.currentTimeMillis();
|
||||
List<Resource> allResources = new ArrayList<>();
|
||||
|
||||
// 扫描多个包路径
|
||||
for (String basePackage : BASE_SCAN_PACKAGES) {
|
||||
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(basePackage) + CLASS_ENUM_PATTERN;
|
||||
|
||||
try {
|
||||
Resource[] resources = resourcePatternResolver.getResources(pattern);
|
||||
allResources.addAll(Arrays.asList(resources));
|
||||
log.debug("【枚举字典加载】扫描包 {} 找到 {} 个枚举类文件", basePackage, resources.length);
|
||||
} catch (Exception e) {
|
||||
log.warn("【枚举字典加载】扫描包 {} 时出现异常: {}", basePackage, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
long scanEndTime = System.currentTimeMillis();
|
||||
log.info("【枚举字典加载】文件扫描完成,总共找到 {} 个枚举类文件,扫描耗时: {}ms", allResources.size(), scanEndTime - scanStartTime);
|
||||
|
||||
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
|
||||
|
||||
long processStartTime = System.currentTimeMillis();
|
||||
int processedCount = 0;
|
||||
|
||||
for (Resource resource : allResources) {
|
||||
try {
|
||||
MetadataReader reader = readerFactory.getMetadataReader(resource);
|
||||
String classname = reader.getClassMetadata().getClassName();
|
||||
|
||||
// 提前检查是否有@EnumDict注解,避免不必要的Class.forName
|
||||
if (hasEnumDictAnnotation(reader)) {
|
||||
processEnumClass(classname);
|
||||
processedCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("处理资源异常: {} - {}", resource.getFilename(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
long processEndTime = System.currentTimeMillis();
|
||||
log.info("【枚举字典加载】处理完成,实际处理 {} 个带注解的枚举类,处理耗时: {}ms", processedCount, processEndTime - processStartTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查类是否有EnumDict注解(通过元数据,避免类加载)
|
||||
*/
|
||||
private static boolean hasEnumDictAnnotation(MetadataReader reader) {
|
||||
try {
|
||||
return reader.getAnnotationMetadata().hasAnnotation(EnumDict.class.getName());
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单个枚举类
|
||||
*/
|
||||
private static void processEnumClass(String classname) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(classname);
|
||||
EnumDict enumDict = clazz.getAnnotation(EnumDict.class);
|
||||
|
||||
if (enumDict != null) {
|
||||
String key = enumDict.value();
|
||||
if (oConvertUtils.isNotEmpty(key)) {
|
||||
Method method = clazz.getDeclaredMethod(METHOD_NAME);
|
||||
List<DictModel> list = (List<DictModel>) method.invoke(null);
|
||||
enumDictData.put(key, list);
|
||||
log.debug("成功加载枚举字典: {} -> {}", key, classname);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("处理枚举类异常: {} - {}", classname, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于后端字典翻译 SysDictServiceImpl#queryManyDictByKeys(java.util.List, java.util.List)
|
||||
*
|
||||
* @param dictCodeList 字典编码列表
|
||||
* @param keys 键值列表
|
||||
* @return 字典数据映射
|
||||
*/
|
||||
public static Map<String, List<DictModel>> queryManyDictByKeys(List<String> dictCodeList, List<String> keys) {
|
||||
Map<String, List<DictModel>> enumDict = getEnumDictData();
|
||||
Map<String, List<DictModel>> map = new HashMap<>();
|
||||
for (String code : enumDictData.keySet()) {
|
||||
if(dictCodeList.indexOf(code)>=0){
|
||||
List<DictModel> dictItemList = enumDictData.get(code);
|
||||
for(DictModel dm: dictItemList){
|
||||
|
||||
// 使用更高效的查找方式
|
||||
Set<String> dictCodeSet = new HashSet<>(dictCodeList);
|
||||
Set<String> keySet = new HashSet<>(keys);
|
||||
|
||||
for (String code : enumDict.keySet()) {
|
||||
if (dictCodeSet.contains(code)) {
|
||||
List<DictModel> dictItemList = enumDict.get(code);
|
||||
for (DictModel dm : dictItemList) {
|
||||
String value = dm.getValue();
|
||||
if(keys.indexOf(value)>=0){
|
||||
if (keySet.contains(value)) {
|
||||
List<DictModel> list = new ArrayList<>();
|
||||
list.add(new DictModel(value, dm.getText()));
|
||||
map.put(code,list);
|
||||
map.put(code, list);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -115,22 +193,5 @@ public class ResourceUtil {
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实现类
|
||||
*
|
||||
* @param classPath
|
||||
*/
|
||||
public static Object getImplementationClass(String classPath){
|
||||
try {
|
||||
Class<?> aClass = Class.forName(classPath);
|
||||
return SpringContextUtils.getBean(aClass);
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.error("类没有找到",e);
|
||||
return null;
|
||||
} catch (NoSuchBeanDefinitionException e){
|
||||
log.error(classPath + "没有实现",e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,13 +1,22 @@
|
||||
package org.jeecg.common.system.util;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.DataBaseConstant;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.system.query.QueryRuleEnum;
|
||||
import org.jeecg.common.util.CommonUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -239,5 +248,47 @@ public class SqlConcatUtil {
|
||||
private static String getDbType() {
|
||||
return CommonUtils.getDatabaseType();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前端传过来的 "多字段排序信息: sortInfoString"
|
||||
* @return
|
||||
*/
|
||||
public static List<OrderItem> getQueryConditionOrders(String column, String order, String queryInfoString){
|
||||
List<OrderItem> list = new ArrayList<>();
|
||||
if(oConvertUtils.isEmpty(queryInfoString)){
|
||||
//默认以创建时间倒序查询
|
||||
if(CommonConstant.ORDER_TYPE_DESC.equalsIgnoreCase(order)){
|
||||
list.add(OrderItem.desc(column));
|
||||
}else{
|
||||
list.add(OrderItem.asc(column));
|
||||
}
|
||||
}else{
|
||||
// 【TV360X-967】URL解码(微服务下需要)
|
||||
if (queryInfoString.contains("%22column%22")) {
|
||||
log.info("queryInfoString 原生 = {}", queryInfoString);
|
||||
try {
|
||||
queryInfoString = URLDecoder.decode(queryInfoString, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new JeecgBootException(e);
|
||||
}
|
||||
log.info("queryInfoString 解码 = {}", queryInfoString);
|
||||
}
|
||||
JSONArray array = JSONArray.parseArray(queryInfoString);
|
||||
Iterator it = array.iterator();
|
||||
while(it.hasNext()){
|
||||
JSONObject json = (JSONObject)it.next();
|
||||
String tempColumn = json.getString("column");
|
||||
if(oConvertUtils.isNotEmpty(tempColumn)){
|
||||
String tempOrder = json.getString("order");
|
||||
if(CommonConstant.ORDER_TYPE_DESC.equalsIgnoreCase(tempOrder)){
|
||||
list.add(OrderItem.desc(tempColumn));
|
||||
}else{
|
||||
list.add(OrderItem.asc(tempColumn));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -68,6 +68,12 @@ public class LoginUser {
|
||||
@SensitiveField
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 工号
|
||||
*/
|
||||
@SensitiveField
|
||||
private String workNo;
|
||||
|
||||
/**
|
||||
* 生日
|
||||
*/
|
||||
@ -138,4 +144,8 @@ public class LoginUser {
|
||||
/**设备id uniapp推送用*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 主岗位
|
||||
*/
|
||||
private String mainDepPostId;
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.SQLException;
|
||||
@ -152,9 +153,9 @@ public class CommonUtils {
|
||||
*/
|
||||
public static String uploadLocal(MultipartFile mf,String bizPath,String uploadpath){
|
||||
try {
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
SsrfFileTypeFilter.checkUploadFileType(mf);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
// 文件安全校验,防止上传漏洞文件
|
||||
SsrfFileTypeFilter.checkUploadFileType(mf, bizPath);
|
||||
|
||||
String fileName = null;
|
||||
File file = new File(uploadpath + File.separator + bizPath + File.separator );
|
||||
if (!file.exists()) {
|
||||
@ -163,6 +164,10 @@ public class CommonUtils {
|
||||
}
|
||||
// 获取文件名
|
||||
String orgName = mf.getOriginalFilename();
|
||||
// 无中文情况下进行转码
|
||||
if (orgName != null && !CommonUtils.ifContainChinese(orgName)) {
|
||||
orgName = new String(orgName.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
|
||||
}
|
||||
orgName = CommonUtils.getFileName(orgName);
|
||||
if(orgName.indexOf(SymbolConstant.SPOT)!=-1){
|
||||
fileName = orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.lastIndexOf("."));
|
||||
@ -242,6 +247,10 @@ public class CommonUtils {
|
||||
try {
|
||||
DataSource dataSource = SpringContextUtils.getApplicationContext().getBean(DataSource.class);
|
||||
dbTypeEnum = JdbcUtils.getDbType(dataSource.getConnection().getMetaData().getURL());
|
||||
//【采用SQL_SERVER2005引擎】QQYUN-13298 解决升级mybatisPlus后SqlServer分页使用OFFSET,无排序字段报错问题
|
||||
if (dbTypeEnum == DbType.SQL_SERVER) {
|
||||
dbTypeEnum = DbType.SQL_SERVER2005;
|
||||
}
|
||||
return dbTypeEnum;
|
||||
} catch (SQLException e) {
|
||||
log.warn(e.getMessage(), e);
|
||||
|
||||
@ -13,6 +13,8 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
@ -814,4 +816,44 @@ public class DateUtils extends PropertyEditorSupport {
|
||||
return calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取两个日期之间的所有日期列表,包含开始和结束日期
|
||||
*
|
||||
* @param begin
|
||||
* @param end
|
||||
* @return
|
||||
*/
|
||||
public static List<Date> getDateRangeList(Date begin, Date end) {
|
||||
List<Date> dateList = new ArrayList<>();
|
||||
if (begin == null || end == null) {
|
||||
return dateList;
|
||||
}
|
||||
|
||||
// 清除时间部分,只比较日期
|
||||
Calendar beginCal = Calendar.getInstance();
|
||||
beginCal.setTime(begin);
|
||||
beginCal.set(Calendar.HOUR_OF_DAY, 0);
|
||||
beginCal.set(Calendar.MINUTE, 0);
|
||||
beginCal.set(Calendar.SECOND, 0);
|
||||
beginCal.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
Calendar endCal = Calendar.getInstance();
|
||||
endCal.setTime(end);
|
||||
endCal.set(Calendar.HOUR_OF_DAY, 0);
|
||||
endCal.set(Calendar.MINUTE, 0);
|
||||
endCal.set(Calendar.SECOND, 0);
|
||||
endCal.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
if (endCal.before(beginCal)) {
|
||||
return dateList;
|
||||
}
|
||||
|
||||
dateList.add(beginCal.getTime());
|
||||
while (beginCal.before(endCal)) {
|
||||
beginCal.add(Calendar.DAY_OF_YEAR, 1);
|
||||
dateList.add(beginCal.getTime());
|
||||
}
|
||||
return dateList;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,14 +1,22 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
@ -203,4 +211,150 @@ public class FileDownloadUtils {
|
||||
dir.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 下载单个文件到ZIP流
|
||||
* 核心功能:获取文件流,写入ZIP条目
|
||||
* @param fileUrl 文件URL(可以是HTTP URL或本地路径)
|
||||
* @param fileName ZIP内的文件名
|
||||
* @param zous ZIP输出流
|
||||
*/
|
||||
public static void downLoadSingleFile(String fileUrl, String fileName, String uploadUrl,ZipArchiveOutputStream zous) {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
// 创建ZIP条目:每个文件在ZIP中都是一个独立条目
|
||||
ZipArchiveEntry entry = new ZipArchiveEntry(fileName);
|
||||
zous.putArchiveEntry(entry);
|
||||
|
||||
// 获取文件输入流:区分普通文件和快捷方式
|
||||
if (fileUrl.endsWith(".url")) {
|
||||
// 处理快捷方式:生成.url文件内容
|
||||
inputStream = FileDownloadUtils.createInternetShortcut(fileName, fileUrl, "");
|
||||
} else {
|
||||
// 普通文件下载:从URL或本地路径获取流
|
||||
inputStream = getDownInputStream(fileUrl,uploadUrl);
|
||||
}
|
||||
|
||||
if (inputStream != null) {
|
||||
// 将文件流写入ZIP
|
||||
IOUtils.copy(inputStream, zous);
|
||||
}
|
||||
// 关闭当前ZIP条目
|
||||
zous.closeArchiveEntry();
|
||||
} catch (IOException e) {
|
||||
log.error("文件下载失败: {}", e);
|
||||
} finally {
|
||||
// 确保输入流关闭
|
||||
IoUtil.close(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下载文件输入流
|
||||
* 功能:根据URL类型(HTTP或本地)获取文件流
|
||||
* @param fileUrl 文件URL(支持HTTP和本地路径)
|
||||
* @return 文件输入流,失败返回null
|
||||
*/
|
||||
public static InputStream getDownInputStream(String fileUrl, String uploadUrl) {
|
||||
try {
|
||||
// 处理HTTP URL:通过网络下载
|
||||
if (oConvertUtils.isNotEmpty(fileUrl) && fileUrl.startsWith(CommonConstant.STR_HTTP)) {
|
||||
URL url = new URL(fileUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(5000); // 连接超时5秒
|
||||
connection.setReadTimeout(30000); // 读取超时30秒
|
||||
return connection.getInputStream();
|
||||
} else {
|
||||
// 处理本地文件:直接读取文件系统
|
||||
String downloadFilePath = uploadUrl + File.separator + fileUrl;
|
||||
// 安全检查:防止下载危险文件类型
|
||||
SsrfFileTypeFilter.checkDownloadFileType(downloadFilePath);
|
||||
return new BufferedInputStream(new FileInputStream(downloadFilePath));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 异常时返回null,上层会处理空流情况
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
* 功能:从文件名中提取扩展名
|
||||
* @param fileName 文件名
|
||||
* @return 文件扩展名(不含点),如"txt"、"png"
|
||||
*/
|
||||
public static String getFileExtension(String fileName) {
|
||||
int dotIndex = fileName.lastIndexOf('.');
|
||||
return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建快捷方式(.url文件内容)
|
||||
* 功能:生成Internet快捷方式文件内容
|
||||
* @param name 快捷方式名称
|
||||
* @param url 目标URL地址
|
||||
* @param icon 图标路径(可选)
|
||||
* @return 包含.url文件内容的输入流
|
||||
*/
|
||||
public static InputStream createInternetShortcut(String name, String url, String icon) {
|
||||
StringWriter sw = new StringWriter();
|
||||
try {
|
||||
// 按照Windows快捷方式格式写入内容
|
||||
sw.write("[InternetShortcut]\n");
|
||||
sw.write("URL=" + url + "\n");
|
||||
if (oConvertUtils.isNotEmpty(icon)) {
|
||||
sw.write("IconFile=" + icon + "\n");
|
||||
}
|
||||
// 将字符串内容转换为输入流
|
||||
return new ByteArrayInputStream(sw.toString().getBytes(StandardCharsets.UTF_8));
|
||||
} finally {
|
||||
IoUtil.close(sw);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 从URL中提取文件名
|
||||
* 功能:从HTTP URL或本地路径中提取纯文件名
|
||||
* @param fileUrl 文件URL
|
||||
* @return 文件名(不含路径)
|
||||
*/
|
||||
public static String getFileNameFromUrl(String fileUrl) {
|
||||
try {
|
||||
// 处理HTTP URL:从路径部分提取文件名
|
||||
if (fileUrl.startsWith(CommonConstant.STR_HTTP)) {
|
||||
URL url = new URL(fileUrl);
|
||||
String path = url.getPath();
|
||||
return path.substring(path.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
// 处理本地文件路径:从文件路径提取文件名
|
||||
return fileUrl.substring(fileUrl.lastIndexOf(File.separator) + 1);
|
||||
} catch (Exception e) {
|
||||
// 如果解析失败,使用时间戳作为文件名
|
||||
return "file_" + System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 生成ZIP中的文件名
|
||||
* 功能:避免文件名冲突,为多个文件添加序号
|
||||
* @param fileUrl 文件URL(用于提取原始文件名)
|
||||
* @param index 文件序号(从0开始)
|
||||
* @param total 文件总数
|
||||
* @return 处理后的文件名(带序号)
|
||||
*/
|
||||
public static String generateFileName(String fileUrl, int index, int total) {
|
||||
// 从URL中提取原始文件名
|
||||
String originalFileName = getFileNameFromUrl(fileUrl);
|
||||
|
||||
// 如果只有一个文件,直接使用原始文件名
|
||||
if (total == 1) {
|
||||
return originalFileName;
|
||||
}
|
||||
|
||||
// 多个文件时,使用序号+原始文件名
|
||||
String extension = getFileExtension(originalFileName);
|
||||
String nameWithoutExtension = originalFileName.replace("." + extension, "");
|
||||
|
||||
return String.format("%s_%d.%s", nameWithoutExtension, index + 1, extension);
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,13 +55,11 @@ public class MinioUtil {
|
||||
*/
|
||||
public static String upload(MultipartFile file, String bizPath, String customBucket) throws Exception {
|
||||
String fileUrl = "";
|
||||
//update-begin-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
|
||||
// 业务路径过滤,防止攻击
|
||||
bizPath = StrAttackFilter.filter(bizPath);
|
||||
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
|
||||
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
// 文件安全校验,防止上传漏洞文件
|
||||
SsrfFileTypeFilter.checkUploadFileType(file, bizPath);
|
||||
|
||||
String newBucket = bucketName;
|
||||
if(oConvertUtils.isNotEmpty(customBucket)){
|
||||
|
||||
@ -2,6 +2,7 @@ package org.jeecg.common.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
@ -16,6 +17,7 @@ import java.util.List;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Lazy(false)
|
||||
public class PmsUtil {
|
||||
|
||||
|
||||
|
||||
@ -221,6 +221,63 @@ public class RestUtil {
|
||||
return RT.exchange(url, method, request, responseType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求(支持自定义超时时间)
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param method 请求方式
|
||||
* @param headers 请求头 可空
|
||||
* @param variables 请求url参数 可空
|
||||
* @param params 请求body参数 可空
|
||||
* @param responseType 返回类型
|
||||
* @param timeout 超时时间(毫秒),如果为0或负数则使用默认超时
|
||||
* @return ResponseEntity<responseType>
|
||||
*/
|
||||
public static <T> ResponseEntity<T> request(String url, HttpMethod method, HttpHeaders headers,
|
||||
JSONObject variables, Object params, Class<T> responseType, int timeout) {
|
||||
log.info(" RestUtil --- request --- url = "+ url + ", timeout = " + timeout);
|
||||
|
||||
if (StringUtils.isEmpty(url)) {
|
||||
throw new RuntimeException("url 不能为空");
|
||||
}
|
||||
if (method == null) {
|
||||
throw new RuntimeException("method 不能为空");
|
||||
}
|
||||
if (headers == null) {
|
||||
headers = new HttpHeaders();
|
||||
}
|
||||
|
||||
// 创建自定义RestTemplate(如果需要设置超时)
|
||||
RestTemplate restTemplate = RT;
|
||||
if (timeout > 0) {
|
||||
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
||||
requestFactory.setConnectTimeout(timeout);
|
||||
requestFactory.setReadTimeout(timeout);
|
||||
restTemplate = new RestTemplate(requestFactory);
|
||||
// 解决乱码问题
|
||||
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
// 请求体
|
||||
String body = "";
|
||||
if (params != null) {
|
||||
if (params instanceof JSONObject) {
|
||||
body = ((JSONObject) params).toJSONString();
|
||||
} else {
|
||||
body = params.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// 拼接 url 参数
|
||||
if (variables != null && !variables.isEmpty()) {
|
||||
url += ("?" + asUrlVariables(variables));
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
HttpEntity<String> request = new HttpEntity<>(body, headers);
|
||||
return restTemplate.exchange(url, method, request, responseType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取JSON请求头
|
||||
*/
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.mgt.SecurityManager;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* @date 2025-09-04
|
||||
* @author scott
|
||||
*
|
||||
* @Description: 支持shiro的API,获取当前登录人方法的线程池
|
||||
*/
|
||||
public class ShiroThreadPoolExecutor extends ThreadPoolExecutor {
|
||||
|
||||
public ShiroThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
|
||||
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
SecurityManager securityManager = SecurityUtils.getSecurityManager();
|
||||
super.execute(() -> {
|
||||
try {
|
||||
ThreadContext.bind(securityManager);
|
||||
ThreadContext.bind(subject);
|
||||
command.run();
|
||||
} finally {
|
||||
ThreadContext.unbindSubject();
|
||||
ThreadContext.unbindSecurityManager();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ import org.jeecg.common.constant.ServiceNameConstants;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
@ -16,6 +17,7 @@ import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
* @Description: spring上下文工具类
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Lazy(false)
|
||||
@Component
|
||||
public class SpringContextUtils implements ApplicationContextAware {
|
||||
|
||||
|
||||
@ -65,6 +65,10 @@ public class TokenUtils {
|
||||
if (tenantId == null) {
|
||||
tenantId = oConvertUtils.getString(request.getHeader(CommonConstant.TENANT_ID));
|
||||
}
|
||||
|
||||
if (oConvertUtils.isNotEmpty(tenantId) && "undefined".equals(tenantId)) {
|
||||
return null;
|
||||
}
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ package org.jeecg.common.util.filter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -34,6 +35,7 @@ public class SsrfFileTypeFilter {
|
||||
FILE_TYPE_WHITE_LIST.add("bmp");
|
||||
FILE_TYPE_WHITE_LIST.add("svg");
|
||||
FILE_TYPE_WHITE_LIST.add("ico");
|
||||
FILE_TYPE_WHITE_LIST.add("heic");
|
||||
|
||||
//文本文件
|
||||
FILE_TYPE_WHITE_LIST.add("txt");
|
||||
@ -148,29 +150,38 @@ public class SsrfFileTypeFilter {
|
||||
public static void checkDownloadFileType(String filePath) throws IOException {
|
||||
//文件后缀
|
||||
String suffix = getFileTypeBySuffix(filePath);
|
||||
log.info("suffix:{}", suffix);
|
||||
log.debug(" 【文件下载校验】文件后缀 suffix: {}", suffix);
|
||||
boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
|
||||
//是否允许下载的文件
|
||||
if (!isAllowExtension) {
|
||||
throw new IOException("下载失败,存在非法文件类型:" + suffix);
|
||||
throw new JeecgBootException("下载失败,存在非法文件类型:" + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 上传文件类型过滤
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
public static void checkUploadFileType(MultipartFile file) throws Exception {
|
||||
//获取文件真是后缀
|
||||
String suffix = getFileType(file);
|
||||
|
||||
log.info("suffix:{}", suffix);
|
||||
checkUploadFileType(file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件类型过滤
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
public static void checkUploadFileType(MultipartFile file, String customPath) throws Exception {
|
||||
//1. 路径安全校验
|
||||
validatePathSecurity(customPath);
|
||||
//2. 校验文件后缀和头
|
||||
String suffix = getFileType(file, customPath);
|
||||
log.info("【文件上传校验】文件后缀 suffix: {},customPath:{}", suffix, customPath);
|
||||
boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
|
||||
//是否允许下载的文件
|
||||
if (!isAllowExtension) {
|
||||
throw new Exception("上传失败,存在非法文件类型:" + suffix);
|
||||
throw new JeecgBootException("上传失败,存在非法文件类型:" + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,7 +193,7 @@ public class SsrfFileTypeFilter {
|
||||
* @throws Exception
|
||||
*/
|
||||
|
||||
private static String getFileType(MultipartFile file) throws Exception {
|
||||
private static String getFileType(MultipartFile file, String customPath) throws Exception {
|
||||
//update-begin-author:liusq date:20230404 for: [issue/4672]方法造成的文件被占用,注释掉此方法tomcat就能自动清理掉临时文件
|
||||
String fileExtendName = null;
|
||||
InputStream is = null;
|
||||
@ -202,7 +213,7 @@ public class SsrfFileTypeFilter {
|
||||
break;
|
||||
}
|
||||
}
|
||||
log.info("-----获取到的指定文件类型------"+fileExtendName);
|
||||
log.debug("-----获取到的指定文件类型------"+fileExtendName);
|
||||
// 如果不是上述类型,则判断扩展名
|
||||
if (StringUtils.isBlank(fileExtendName)) {
|
||||
String fileName = file.getOriginalFilename();
|
||||
@ -213,7 +224,6 @@ public class SsrfFileTypeFilter {
|
||||
// 如果有扩展名,则返回扩展名
|
||||
return getFileTypeBySuffix(fileName);
|
||||
}
|
||||
log.info("-----最終的文件类型------"+fileExtendName);
|
||||
is.close();
|
||||
return fileExtendName;
|
||||
} catch (Exception e) {
|
||||
@ -248,4 +258,34 @@ public class SsrfFileTypeFilter {
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径安全校验
|
||||
*/
|
||||
private static void validatePathSecurity(String customPath) throws JeecgBootException {
|
||||
if (customPath == null || customPath.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 统一分隔符为 /
|
||||
String normalized = customPath.replace("\\", "/");
|
||||
|
||||
// 1. 防止路径遍历攻击
|
||||
if (normalized.contains("..") || normalized.contains("~")) {
|
||||
throw new JeecgBootException("上传业务路径包含非法字符!");
|
||||
}
|
||||
|
||||
// 2. 限制路径深度
|
||||
int depth = normalized.split("/").length;
|
||||
if (depth > 5) {
|
||||
throw new JeecgBootException("上传业务路径深度超出限制!");
|
||||
}
|
||||
|
||||
// 3. 限制字符集(只允许字母、数字、下划线、横线、斜杠)
|
||||
if (!normalized.matches("^[a-zA-Z0-9/_-]+$")) {
|
||||
throw new JeecgBootException("上传业务路径包含非法字符!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -474,6 +474,23 @@ public class oConvertUtils {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为JSON格式
|
||||
* @param str
|
||||
* @return
|
||||
*/
|
||||
public static boolean isJson(String str) {
|
||||
if (str == null || str.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
com.alibaba.fastjson.JSON.parse(str);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Map对象
|
||||
*/
|
||||
@ -1132,7 +1149,15 @@ public class oConvertUtils {
|
||||
* @date 2020/9/12 15:50
|
||||
*/
|
||||
public static <T> boolean isIn(T obj, T... objs) {
|
||||
return isIn(obj, objs);
|
||||
if (isEmpty(objs)) {
|
||||
return false;
|
||||
}
|
||||
for (T obj1 : objs) {
|
||||
if (isEqual(obj, obj1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -97,9 +97,8 @@ public class OssBootUtil {
|
||||
* @return oss 中的相对文件路径
|
||||
*/
|
||||
public static String upload(MultipartFile file, String fileDir,String customBucket) throws Exception {
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
// 文件安全校验,防止上传漏洞文件
|
||||
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
|
||||
String filePath = null;
|
||||
initOss(endPoint, accessKeyId, accessKeySecret);
|
||||
|
||||
@ -3,13 +3,14 @@ package org.jeecg.config;
|
||||
import org.jeecgframework.core.util.ApplicationContextUtil;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
/**
|
||||
* @Author: Scott
|
||||
* @Date: 2018/2/7
|
||||
* @description: autopoi 配置类
|
||||
*/
|
||||
|
||||
@Lazy(false)
|
||||
@Configuration
|
||||
public class AutoPoiConfig {
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
* @Version:1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Lazy(false)
|
||||
@Service
|
||||
public class AutoPoiDictConfig implements AutoPoiDictServiceI {
|
||||
final static String EXCEL_SPLIT_TAG = "_";
|
||||
|
||||
@ -70,7 +70,7 @@ public class JeecgBaseConfig {
|
||||
/**
|
||||
* 百度开放API配置
|
||||
*/
|
||||
private BaiduApi baiduApi;
|
||||
private BaiduApi baiduApi;
|
||||
|
||||
public String getCustomResourcePrefixPath() {
|
||||
return customResourcePrefixPath;
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package org.jeecg.config;
|
||||
|
||||
import org.jeecg.config.vo.GaoDeApi;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
/**
|
||||
* 高德账号配置
|
||||
*/
|
||||
@Lazy(false)
|
||||
@Configuration("jeecgGaodeBaseConfig")
|
||||
@ConfigurationProperties(prefix = "jeecg.jmreport")
|
||||
public class JeecgGaodeBaseConfig {
|
||||
|
||||
/**
|
||||
* 高德开放API配置
|
||||
*/
|
||||
private GaoDeApi gaoDeApi;
|
||||
|
||||
public GaoDeApi getGaoDeApi() {
|
||||
return gaoDeApi;
|
||||
}
|
||||
|
||||
public void setGaoDeApi(GaoDeApi gaoDeApi) {
|
||||
this.gaoDeApi = gaoDeApi;
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,12 +2,14 @@ package org.jeecg.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设置静态参数初始化
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Lazy(false)
|
||||
@Component
|
||||
@Data
|
||||
public class StaticConfig {
|
||||
|
||||
@ -68,17 +68,15 @@ public class Swagger3Config implements WebMvcConfigurer {
|
||||
openApi.getPaths().forEach((path, pathItem) -> {
|
||||
//log.debug("path: {}", path);
|
||||
// 检查当前路径是否在排除列表中
|
||||
boolean isExcluded = excludedPaths.stream().anyMatch(excludedPath ->
|
||||
excludedPath.equals(path) ||
|
||||
(excludedPath.endsWith("**") && path.startsWith(excludedPath.substring(0, excludedPath.length() - 2)))
|
||||
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))
|
||||
);
|
||||
pathItem.readOperations().forEach(operation ->
|
||||
operation.addSecurityItem(new SecurityRequirement().addList(CommonConstant.X_ACCESS_TOKEN))
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -90,7 +88,7 @@ public class Swagger3Config implements WebMvcConfigurer {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("JeecgBoot 后台服务API接口文档")
|
||||
.version("3.8.1")
|
||||
.version("3.8.3")
|
||||
.contact(new Contact().name("北京国炬信息技术有限公司").url("www.jeccg.com").email("jeecgos@163.com"))
|
||||
.description( "后台API接口")
|
||||
.termsOfService("NO terms of service")
|
||||
|
||||
@ -88,7 +88,7 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
||||
*/
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
registry.addViewController("/").setViewName("doc.html");
|
||||
registry.addViewController("/").setViewName("redirect:/doc.html");
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@ -11,7 +11,6 @@ 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.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
@ -50,14 +49,12 @@ public class LowCodeModeInterceptor implements HandlerInterceptor {
|
||||
@Resource
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
|
||||
@Autowired(required = false)
|
||||
private CommonAPI commonAPI;
|
||||
|
||||
/**
|
||||
* 在请求处理之前进行调用
|
||||
*/
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
CommonAPI commonAPI = null;
|
||||
log.info("低代码模式,拦截请求路径:" + request.getRequestURI());
|
||||
|
||||
//1、验证是否开启低代码开发模式控制
|
||||
|
||||
@ -1,11 +1,18 @@
|
||||
package org.jeecg.config.mybatis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhyd.oauth.log.Log;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
import org.jeecg.common.config.TenantContext;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.TenantConstant;
|
||||
@ -13,26 +20,27 @@ import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.common.util.TokenUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 单数据源配置(jeecg.datasource.open = false时生效)
|
||||
* @Author zhoujf
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@MapperScan(value={"org.jeecg.**.mapper*"})
|
||||
public class MybatisPlusSaasConfig {
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource;
|
||||
|
||||
/**
|
||||
* 是否开启系统模块的租户隔离
|
||||
* 控制范围:用户、角色、部门、我的部门、字典、分类字典、多数据源、职务、通知公告
|
||||
@ -122,7 +130,23 @@ public class MybatisPlusSaasConfig {
|
||||
//update-begin-author:zyf date:20220425 for:【VUEN-606】注入动态表名适配拦截器解决多表名问题
|
||||
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());
|
||||
//update-end-author:zyf date:20220425 for:【VUEN-606】注入动态表名适配拦截器解决多表名问题
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
|
||||
|
||||
//update-begin---author:scott ---date:2025-08-02 for:【issues/8666】升级mybatisPlus后SqlServer分页使用OFFSET ? ROWS FETCH NEXT ? ROWS ONLY,导致online报表报错---
|
||||
DbType dbType = null;
|
||||
try {
|
||||
dbType = JdbcUtils.getDbType(dataSource.getConnection().getMetaData().getURL());
|
||||
log.info("当前数据库类型: {}", dbType);
|
||||
} catch (SQLException e) {
|
||||
Log.error(e.getMessage(), e);
|
||||
}
|
||||
if (dbType!=null && (dbType == DbType.SQL_SERVER || dbType == DbType.SQL_SERVER2005)) {
|
||||
// 如果是SQL Server则覆盖为2005分页方式
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.SQL_SERVER2005));
|
||||
} else {
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
|
||||
}
|
||||
//update-end---author:scott ---date::2025-08-02 for:【issues/8666】升级mybatisPlus后SqlServer分页使用OFFSET ? ROWS FETCH NEXT ? ROWS ONLY,导致online报表报错---
|
||||
|
||||
//【jeecg-boot/issues/3847】增加@Version乐观锁支持
|
||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||
return interceptor;
|
||||
|
||||
@ -8,11 +8,13 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
/**
|
||||
* Minio文件上传配置文件
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Lazy(false)
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "jeecg.minio", name = "minio_url")
|
||||
|
||||
@ -5,11 +5,13 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
/**
|
||||
* 云存储 配置
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Lazy(false)
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "jeecg.oss", name = "endpoint")
|
||||
public class OssConfiguration {
|
||||
|
||||
@ -109,7 +109,7 @@ public class ShiroConfig {
|
||||
filterChainDefinitionMap.put("/sys/getLoginQrcode/**", "anon"); //登录二维码
|
||||
filterChainDefinitionMap.put("/sys/getQrcodeToken/**", "anon"); //监听扫码
|
||||
filterChainDefinitionMap.put("/sys/checkAuth", "anon"); //授权接口排除
|
||||
|
||||
filterChainDefinitionMap.put("/openapi/call/**", "anon"); // 开放平台接口排除
|
||||
|
||||
//update-begin--Author:scott Date:20221116 for:排除静态资源后缀
|
||||
filterChainDefinitionMap.put("/", "anon");
|
||||
@ -126,6 +126,7 @@ public class ShiroConfig {
|
||||
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:排除静态资源后缀
|
||||
@ -153,7 +154,11 @@ public class ShiroConfig {
|
||||
filterChainDefinitionMap.put("/drag/share/view/**", "anon");
|
||||
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getAllChartData", "anon");
|
||||
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getTotalData", "anon");
|
||||
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getMapDataByCode", "anon");
|
||||
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getTotalDataByCompId", "anon");
|
||||
filterChainDefinitionMap.put("/drag/mock/json/**", "anon");
|
||||
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getDictByCodes", "anon");
|
||||
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/queryAllById", "anon");
|
||||
filterChainDefinitionMap.put("/jimubi/view", "anon");
|
||||
filterChainDefinitionMap.put("/jimubi/share/view/**", "anon");
|
||||
|
||||
@ -169,6 +174,12 @@ public class ShiroConfig {
|
||||
filterChainDefinitionMap.put("/websocket/**", "anon");//系统通知和公告
|
||||
filterChainDefinitionMap.put("/newsWebsocket/**", "anon");//CMS模块
|
||||
filterChainDefinitionMap.put("/vxeSocket/**", "anon");//JVxeTable无痕刷新示例
|
||||
//App vue3版本查询版本接口
|
||||
filterChainDefinitionMap.put("/sys/version/app3version", "anon");
|
||||
//仪表盘(按钮通信)
|
||||
filterChainDefinitionMap.put("/dragChannelSocket/**","anon");
|
||||
//App vue3版本查询版本接口
|
||||
filterChainDefinitionMap.put("/sys/version/app3version", "anon");
|
||||
|
||||
//性能监控——安全隐患泄露TOEKN(durid连接池也有)
|
||||
//filterChainDefinitionMap.put("/actuator/**", "anon");
|
||||
@ -180,8 +191,6 @@ public class ShiroConfig {
|
||||
// 企业微信证书排除
|
||||
filterChainDefinitionMap.put("/WW_verify*", "anon");
|
||||
|
||||
filterChainDefinitionMap.put("/openapi/call/**", "anon");
|
||||
|
||||
// 添加自己的过滤器并且取名为jwt
|
||||
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
|
||||
//如果cloudServer为空 则说明是单体 需要加载跨域配置【微服务跨域切换】
|
||||
@ -222,6 +231,7 @@ public class ShiroConfig {
|
||||
registration.addUrlPatterns("/airag/chat/send");
|
||||
registration.addUrlPatterns("/airag/app/debug");
|
||||
registration.addUrlPatterns("/airag/app/prompt/generate");
|
||||
registration.addUrlPatterns("/airag/chat/receive/**");
|
||||
//支持异步
|
||||
registration.setAsyncSupported(true);
|
||||
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
|
||||
|
||||
@ -106,8 +106,8 @@ public class ShiroRealm extends AuthorizingRealm {
|
||||
try {
|
||||
loginUser = this.checkUserTokenIsEffect(token);
|
||||
} catch (AuthenticationException e) {
|
||||
log.error("—————校验 check token 失败——————————"+ e.getMessage(), e);
|
||||
JwtUtil.responseError(SpringContextUtils.getHttpServletResponse(),401,e.getMessage());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
return new SimpleAuthenticationInfo(loginUser, token, getName());
|
||||
@ -122,7 +122,7 @@ public class ShiroRealm extends AuthorizingRealm {
|
||||
// 解密获得username,用于和数据库进行对比
|
||||
String username = JwtUtil.getUsername(token);
|
||||
if (username == null) {
|
||||
throw new AuthenticationException("token非法无效!");
|
||||
throw new AuthenticationException("Token非法无效!");
|
||||
}
|
||||
|
||||
// 查询用户信息
|
||||
|
||||
@ -56,7 +56,7 @@ public class JwtFilter extends BasicHttpAuthenticationFilter {
|
||||
executeLogin(request, response);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
JwtUtil.responseError(response,401,CommonConstant.TOKEN_IS_INVALID_MSG);
|
||||
JwtUtil.responseError((HttpServletResponse)response,401,CommonConstant.TOKEN_IS_INVALID_MSG);
|
||||
return false;
|
||||
//throw new AuthenticationException("Token失效,请重新登录", e);
|
||||
}
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
package org.jeecg.config.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Description: 高德开放api配置
|
||||
*
|
||||
* @author: wangshuai
|
||||
* @date: 2025/7/17 20:32
|
||||
*/
|
||||
@Data
|
||||
public class GaoDeApi {
|
||||
/**应用key*/
|
||||
private String apiKey;
|
||||
/**应用秘钥*/
|
||||
private String secretKey;
|
||||
}
|
||||
@ -1,3 +1,2 @@
|
||||
springdoc.auto-tag-classes: false
|
||||
springdoc.packages-to-scan: org.jeecg
|
||||
springdoc.default-flat-param-object: true
|
||||
springdoc.packages-to-scan: org.jeecg
|
||||
@ -0,0 +1,104 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="box-content">
|
||||
<div class="info-top">
|
||||
<img src="https://www.jeecg.com/images/logo.png" style="float: left; margin: 0 10px 0 0; width: 32px;height:32px" /><div style="color:#fff"><strong>【重要】流程抄送的通知</strong></div>
|
||||
</div>
|
||||
<div class="info-wrap">
|
||||
<div class="tips" style="padding:15px;">
|
||||
<p style="margin: 10px 0;">
|
||||
您好,您有一个新的流程抄送任务亟待查看,任务内容如下::
|
||||
</p>
|
||||
<table style="width: 400px; border-spacing: 0px; border-collapse: collapse; border: none; margin-top: 20px;"><tbody>
|
||||
<tr style="height: 45px;">
|
||||
<td style="width: 150px; height: 40px; background: #F6F6F6;border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
|
||||
流程名称
|
||||
</td>
|
||||
<td style="width: 250px;height: 40px; border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
|
||||
${bpm_name}<a style="color: #006eff;" href="${url}" target="_blank" rel="noopener">[立刻查看]</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="height: 45px;">
|
||||
<td style="width: 150px;height: 40px; background: #F6F6F6;border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
|
||||
抄送任务
|
||||
</td>
|
||||
<td style="width: 250px;height: 40px; border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
|
||||
${bpm_task}
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="height: 45px;">
|
||||
<td style="width: 150px; height: 40px; background: #F6F6F6;border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
|
||||
抄送时间
|
||||
</td>
|
||||
<td style="width: 250px;height: 40px; border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
|
||||
${datetime}
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="height: 45px;">
|
||||
<td style="width: 150px; height: 40px; background: #F6F6F6;border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
|
||||
抄送内容
|
||||
</td>
|
||||
<td style="width: 250px;height: 40px; border: 1px solid #DBDBDB; font-size: 14px; font-weight: normal; text-align: left; padding-left: 14px;">
|
||||
${remark}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer">北京国炬平台</div>
|
||||
</div>
|
||||
<div style="margin-top: 60px;margin-bottom: 10px;">
|
||||
<span style="font-size: 13px; font-weight: bold; color: #666;">温馨提醒</span>
|
||||
<div style="line-height: 24px; margin-top: 10px;">
|
||||
<div style="font-size: 13px; color: #666;">使用过程中如有任何问题,请联系系统管理员。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 600px; margin: 0 auto; margin-top: 50px; font-size: 12px; -webkit-font-smoothing: subpixel-antialiased; text-size-adjust: 100%;">
|
||||
<p style="text-align: center; line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px !important; color: #7e8890 !important;">
|
||||
<span class="appleLinks">Copyright © 2023-2024 北京国炬信息技术有限公司. 保留所有权利。</span>
|
||||
</p>
|
||||
<p style="text-align: center;line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px; color: #7e8890 !important; margin-top: 10px;">
|
||||
<span class="appleLinks">邮件由系统自动发送,请勿直接回复本邮件!</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<style>
|
||||
.box-content{
|
||||
width: 80%;
|
||||
margin: 20px auto;
|
||||
max-width: 800px;
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
.info-top{
|
||||
padding: 15px 25px;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
background: #4ea3f2;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.info-wrap{
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
border:1px solid #ddd;
|
||||
overflow: hidden;
|
||||
padding: 15px 15px 20px;
|
||||
}
|
||||
|
||||
.footer{
|
||||
text-align: right;
|
||||
color: #999;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
</html>
|
||||
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-module</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<version>3.8.3</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>jeecg-boot-module-airag</artifactId>
|
||||
@ -31,8 +31,6 @@
|
||||
</repositories>
|
||||
|
||||
<properties>
|
||||
<kotlin.version>1.6.21</kotlin.version>
|
||||
<liteflow.version>2.12.4.1</liteflow.version>
|
||||
<langchain4j.version>0.35.0</langchain4j.version>
|
||||
<apache-tika.version>2.9.1</apache-tika.version>
|
||||
</properties>
|
||||
@ -57,7 +55,7 @@
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-aiflow</artifactId>
|
||||
<version>1.1.1</version>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- beigin 这两个依赖太多每个包50M左右,如果你发布需要使用,请把<scope>provided</scope>删掉 -->
|
||||
@ -151,7 +149,7 @@
|
||||
</dependency>
|
||||
<!-- langChain4j vextor support -->
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<groupId>org.jeecgframework</groupId>
|
||||
<artifactId>langchain4j-pgvector</artifactId>
|
||||
<version>${langchain4j.version}</version>
|
||||
</dependency>
|
||||
|
||||
@ -156,6 +156,20 @@ public class AiragChatController {
|
||||
return chatService.clearMessage(conversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 继续接收消息
|
||||
*
|
||||
* @param requestId
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/11 17:49
|
||||
*/
|
||||
@IgnoreAuth
|
||||
@GetMapping(value = "/receive/{requestId}")
|
||||
public SseEmitter receiveByRequestId(@PathVariable(name = "requestId", required = true) String requestId) {
|
||||
return chatService.receiveByRequestId(requestId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据请求ID停止某个请求的处理
|
||||
|
||||
@ -102,4 +102,13 @@ public interface IAiragChatService {
|
||||
* @date 2025/4/21 14:17
|
||||
*/
|
||||
Result<?> initChat(String appId);
|
||||
|
||||
/**
|
||||
* 继续接收消息
|
||||
* @param requestId
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/11 17:39
|
||||
*/
|
||||
SseEmitter receiveByRequestId(String requestId);
|
||||
}
|
||||
|
||||
@ -8,13 +8,13 @@ import dev.langchain4j.service.TokenStream;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.exception.JeecgBootBizTipException;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.jeecg.common.util.*;
|
||||
import org.jeecg.modules.airag.app.consts.AiAppConsts;
|
||||
import org.jeecg.modules.airag.app.entity.AiragApp;
|
||||
import org.jeecg.modules.airag.app.mapper.AiragAppMapper;
|
||||
import org.jeecg.modules.airag.app.service.IAiragAppService;
|
||||
import org.jeecg.modules.airag.app.service.IAiragChatService;
|
||||
import org.jeecg.modules.airag.app.vo.AppDebugParams;
|
||||
import org.jeecg.modules.airag.app.vo.ChatConversation;
|
||||
@ -31,6 +31,8 @@ import org.jeecg.modules.airag.flow.consts.FlowConsts;
|
||||
import org.jeecg.modules.airag.flow.service.IAiragFlowService;
|
||||
import org.jeecg.modules.airag.flow.vo.api.FlowRunParams;
|
||||
import org.jeecg.modules.airag.llm.entity.AiragModel;
|
||||
import org.jeecg.modules.airag.llm.handler.AIChatHandler;
|
||||
import org.jeecg.modules.airag.llm.handler.JeecgToolsProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.BoundValueOperations;
|
||||
@ -41,8 +43,7 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -74,6 +75,14 @@ public class AiragChatServiceImpl implements IAiragChatService {
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Autowired
|
||||
JeecgToolsProvider jeecgToolsProvider;
|
||||
|
||||
/**
|
||||
* 重新接收消息
|
||||
*/
|
||||
private static final ExecutorService SSE_THREAD_POOL = Executors.newFixedThreadPool(10); // 最大10个线程
|
||||
|
||||
@Override
|
||||
public SseEmitter send(ChatSendParams chatSendParams) {
|
||||
AssertUtils.assertNotEmpty("参数异常", chatSendParams);
|
||||
@ -148,7 +157,9 @@ public class AiragChatServiceImpl implements IAiragChatService {
|
||||
// 发送完成事件
|
||||
emitter.send(SseEmitter.event().data(eventData));
|
||||
} catch (Exception e) {
|
||||
log.error("终止会话时发生错误", e);
|
||||
if(!e.getMessage().contains("ResponseBodyEmitter has already completed")){
|
||||
log.error("终止会话时发生错误", e);
|
||||
}
|
||||
try {
|
||||
// 防止异常冒泡
|
||||
emitter.completeWithError(e);
|
||||
@ -250,6 +261,96 @@ public class AiragChatServiceImpl implements IAiragChatService {
|
||||
return Result.ok(app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SseEmitter receiveByRequestId(String requestId) {
|
||||
AssertUtils.assertNotEmpty("请选择会话",requestId);
|
||||
if(AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId) == null){
|
||||
return null;
|
||||
}
|
||||
List<EventData> datas = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE_HISTORY_MSG, requestId);
|
||||
if(null == datas){
|
||||
return null;
|
||||
}
|
||||
SseEmitter emitter = createSSE(requestId);
|
||||
// 120秒
|
||||
final long timeoutMillis = 120_000L;
|
||||
// 使用线程池提交任务
|
||||
SSE_THREAD_POOL.submit(() -> {
|
||||
int lastIndex = 0;
|
||||
long lastActiveTime = System.currentTimeMillis();
|
||||
try {
|
||||
while (true) {
|
||||
if(lastIndex < datas.size()) {
|
||||
try {
|
||||
EventData eventData = datas.get(lastIndex++);
|
||||
String eventStr = JSONObject.toJSONString(eventData);
|
||||
log.debug("[AI应用]继续接收-接收LLM返回消息:{}", eventStr);
|
||||
emitter.send(SseEmitter.event().data(eventStr));
|
||||
// 有新消息,重置计时
|
||||
lastActiveTime = System.currentTimeMillis();
|
||||
} catch (IOException e) {
|
||||
log.error("[AI应用]继续接收-发送消息失败");
|
||||
}
|
||||
} else {
|
||||
// 没有新消息了
|
||||
if (AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId) == null) {
|
||||
// 主线程sse已经被移除,退出线程.
|
||||
log.info("[AI应用]继续接收-SSE消息推送完成: {}", requestId);
|
||||
break;
|
||||
} else if (System.currentTimeMillis() - lastActiveTime > timeoutMillis) {
|
||||
// 主线程未结束,等待超时,
|
||||
log.warn("[AI应用]继续接收-等待消息更新超时,释放线程: {}", requestId);
|
||||
break;
|
||||
} else {
|
||||
// 主线程未结束, 未超时, 休眠一会再查
|
||||
log.warn("[AI应用]继续接收-等待消息更新: {}", requestId);
|
||||
Thread.sleep(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("SSE消息推送异常", e);
|
||||
} finally {
|
||||
try {
|
||||
// 发送完成事件
|
||||
emitter.send(SseEmitter.event().data(new EventData(requestId, null, EventData.EVENT_MESSAGE_END)));
|
||||
} catch (Exception e) {
|
||||
log.error("终止会话时发生错误", e);
|
||||
try {
|
||||
// 防止异常冒泡
|
||||
emitter.completeWithError(e);
|
||||
} catch (Exception ignore) {}
|
||||
} finally {
|
||||
// 关闭emitter
|
||||
try {
|
||||
emitter.complete();
|
||||
} catch (Exception ignore) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
return emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SSE
|
||||
* @param requestId
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/12 15:30
|
||||
*/
|
||||
private static SseEmitter createSSE(String requestId) {
|
||||
SseEmitter emitter = new SseEmitter(-0L);
|
||||
emitter.onError(throwable -> {
|
||||
log.warn("SEE向客户端发送消息失败: {}", throwable.getMessage());
|
||||
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE, requestId);
|
||||
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId);
|
||||
try {
|
||||
emitter.complete();
|
||||
} catch (Exception ignore) {}
|
||||
});
|
||||
return emitter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> deleteConversation(String conversationId) {
|
||||
AssertUtils.assertNotEmpty("请选择要删除的会话", conversationId);
|
||||
@ -522,22 +623,14 @@ public class AiragChatServiceImpl implements IAiragChatService {
|
||||
AiragApp aiApp = chatConversation.getApp();
|
||||
// 每次会话都生成一个新的,用来缓存emitter
|
||||
String requestId = UUIDGenerator.generate();
|
||||
SseEmitter emitter = new SseEmitter(-0L);
|
||||
emitter.onError(throwable -> {
|
||||
log.warn("SEE向客户端发送消息失败: {}", throwable.getMessage());
|
||||
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE, requestId);
|
||||
try {
|
||||
emitter.complete();
|
||||
} catch (Exception ignore) {}
|
||||
});
|
||||
EventData eventRequestId = new EventData(requestId, null, EventData.EVENT_INIT_REQUEST_ID, chatConversation.getId(), topicId);
|
||||
eventRequestId.setData(EventMessageData.builder().message("").build());
|
||||
sendMessage2Client(emitter, eventRequestId);
|
||||
SseEmitter emitter = createSSE(requestId);
|
||||
// 缓存emitter
|
||||
AiragLocalCache.put(AiragConsts.CACHE_TYPE_SSE, requestId, emitter);
|
||||
// 缓存开始发送时间
|
||||
log.info("[AI-CHAT]开始发送消息,requestId:{}", requestId);
|
||||
AiragLocalCache.put(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId, System.currentTimeMillis());
|
||||
// 初始化历史消息缓存
|
||||
AiragLocalCache.put(AiragConsts.CACHE_TYPE_SSE_HISTORY_MSG, requestId, new CopyOnWriteArrayList<>());
|
||||
try {
|
||||
// 组装用户消息
|
||||
UserMessage userMessage = aiChatHandler.buildUserMessage(sendParams.getContent(), sendParams.getImages());
|
||||
@ -561,6 +654,10 @@ public class AiragChatServiceImpl implements IAiragChatService {
|
||||
// 发消息
|
||||
sendWithDefault(requestId, chatConversation, topicId, null, messages, null);
|
||||
}
|
||||
// 发送就绪消息
|
||||
EventData eventRequestId = new EventData(requestId, null, EventData.EVENT_INIT_REQUEST_ID, chatConversation.getId(), topicId);
|
||||
eventRequestId.setData(EventMessageData.builder().message("").build());
|
||||
sendMessage2Client(emitter, eventRequestId);
|
||||
} catch (Throwable e) {
|
||||
log.error(e.getMessage(), e);
|
||||
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
|
||||
@ -725,6 +822,10 @@ public class AiragChatServiceImpl implements IAiragChatService {
|
||||
if (null == aiChatParams) {
|
||||
aiChatParams = new AIChatParams();
|
||||
}
|
||||
// 如果是默认app,加载系统默认工具
|
||||
if(chatConversation.getApp().getId().equals(AiAppConsts.DEFAULT_APP_ID)){
|
||||
aiChatParams.setTools(jeecgToolsProvider.getDefaultTools());
|
||||
}
|
||||
aiChatParams.setKnowIds(chatConversation.getApp().getKnowIds());
|
||||
aiChatParams.setMaxMsgNumber(oConvertUtils.getInt(chatConversation.getApp().getMsgNum(), 5));
|
||||
HttpServletRequest httpRequest = SpringContextUtils.getHttpServletRequest();
|
||||
@ -739,6 +840,19 @@ public class AiragChatServiceImpl implements IAiragChatService {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
// sse
|
||||
SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
|
||||
if (null == emitter) {
|
||||
log.warn("[AI应用]接收LLM返回会话已关闭{}", requestId);
|
||||
return;
|
||||
}
|
||||
String errMsg = "调用大模型接口失败,详情请查看后台日志。";
|
||||
if(e instanceof JeecgBootException){
|
||||
errMsg = e.getMessage();
|
||||
}
|
||||
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
|
||||
eventData.setData(EventFlowData.builder().success(false).message(errMsg).build());
|
||||
closeSSE(emitter, eventData);
|
||||
throw new JeecgBootBizTipException("调用大模型接口失败:" + e.getMessage());
|
||||
}
|
||||
/**
|
||||
@ -808,7 +922,7 @@ public class AiragChatServiceImpl implements IAiragChatService {
|
||||
// 异常结束
|
||||
log.error("调用模型异常:" + respText);
|
||||
if (respText.contains("insufficient Balance")) {
|
||||
respText = "大预言模型账号余额不足!";
|
||||
respText = "大语言模型账号余额不足!";
|
||||
}
|
||||
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
|
||||
eventData.setData(EventFlowData.builder().success(false).message(respText).build());
|
||||
@ -836,7 +950,15 @@ public class AiragChatServiceImpl implements IAiragChatService {
|
||||
closeSSE(emitter, eventData);
|
||||
//update-end---author:chenrui ---date:20250425 for:[QQYUN-12203]AI 聊天,超时或者服务器报错,给个友好提示------------
|
||||
} else {
|
||||
errMsg = "调用大模型接口失败:" + errMsg;
|
||||
errMsg = "调用大模型接口失败,详情请查看后台日志。";
|
||||
// 根据常见异常关键字做细致翻译
|
||||
for (Map.Entry<String, String> entry : AIChatHandler.MODEL_ERROR_MAP.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
if (error.getMessage().contains(key)) {
|
||||
errMsg = value;
|
||||
}
|
||||
}
|
||||
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
|
||||
eventData.setData(EventFlowData.builder().success(false).message(errMsg).build());
|
||||
closeSSE(emitter, eventData);
|
||||
@ -858,6 +980,12 @@ public class AiragChatServiceImpl implements IAiragChatService {
|
||||
String eventStr = JSONObject.toJSONString(eventData);
|
||||
log.debug("[AI应用]接收LLM返回消息:{}", eventStr);
|
||||
emitter.send(SseEmitter.event().data(eventStr));
|
||||
List<EventData> historyMsg = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE_HISTORY_MSG, eventData.getRequestId());
|
||||
if (null == historyMsg) {
|
||||
historyMsg = new CopyOnWriteArrayList<>();
|
||||
AiragLocalCache.put(AiragConsts.CACHE_TYPE_SSE_HISTORY_MSG, eventData.getRequestId(), historyMsg);
|
||||
}
|
||||
historyMsg.add(eventData);
|
||||
} catch (IOException e) {
|
||||
log.error("发送消息失败", e);
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.util.AssertUtils;
|
||||
import org.jeecg.common.util.TokenUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
|
||||
import org.jeecg.modules.airag.llm.consts.LLMConsts;
|
||||
import org.jeecg.modules.airag.llm.entity.AiragModel;
|
||||
@ -77,6 +78,11 @@ public class AiragModelController extends JeecgController<AiragModel, IAiragMode
|
||||
AssertUtils.assertNotEmpty("模型名称不能为空", airagModel.getName());
|
||||
AssertUtils.assertNotEmpty("模型类型不能为空", airagModel.getModelType());
|
||||
AssertUtils.assertNotEmpty("基础模型不能为空", airagModel.getModelName());
|
||||
// 默认未激活
|
||||
if(oConvertUtils.isObjectEmpty(airagModel.getActivateFlag())){
|
||||
airagModel.setActivateFlag(0);
|
||||
}
|
||||
airagModel.setActivateFlag(0);
|
||||
airagModelService.save(airagModel);
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
@ -164,7 +170,7 @@ public class AiragModelController extends JeecgController<AiragModel, IAiragMode
|
||||
AssertUtils.assertNotEmpty("基础模型不能为空", airagModel.getModelName());
|
||||
try {
|
||||
if(LLMConsts.MODEL_TYPE_LLM.equals(airagModel.getModelType())){
|
||||
aiChatHandler.completions(airagModel, Collections.singletonList(UserMessage.from("test connection")), null);
|
||||
aiChatHandler.completions(airagModel, Collections.singletonList(UserMessage.from("To test whether it can be successfully called, simply return success")), null);
|
||||
}else{
|
||||
AiModelOptions aiModelOptions = EmbeddingHandler.buildModelOptions(airagModel);
|
||||
EmbeddingModel embeddingModel = AiModelFactory.createEmbeddingModel(aiModelOptions);
|
||||
@ -172,9 +178,12 @@ public class AiragModelController extends JeecgController<AiragModel, IAiragMode
|
||||
}
|
||||
}catch (Exception e){
|
||||
log.error("测试模型连接失败", e);
|
||||
return Result.error("测试模型连接失败" + e.getMessage());
|
||||
return Result.error("测试模型连接失败:" + e.getMessage());
|
||||
}
|
||||
return Result.OK("测试模型连接成功");
|
||||
// 测试成功激活数据
|
||||
airagModel.setActivateFlag(1);
|
||||
airagModelService.updateById(airagModel);
|
||||
return Result.OK("");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -121,4 +121,11 @@ public class AiragModel implements Serializable {
|
||||
@Excel(name = "模型参数", width = 15)
|
||||
@Schema(description = "模型参数")
|
||||
private String modelParams;
|
||||
|
||||
/**
|
||||
* 是否激活(0=未激活,1=已激活)
|
||||
*/
|
||||
@Excel(name = "是否激活", width = 15)
|
||||
@Schema(description = "是否激活")
|
||||
private Integer activateFlag;
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import dev.langchain4j.rag.query.router.QueryRouter;
|
||||
import dev.langchain4j.service.TokenStream;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.ai.handler.LLMHandler;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.AssertUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.airag.common.handler.AIChatParams;
|
||||
@ -22,9 +23,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
@ -83,6 +82,7 @@ public class AIChatHandler implements IAIChatHandler {
|
||||
AssertUtils.assertNotEmpty("请选择模型", modelId);
|
||||
|
||||
AiragModel airagModel = airagModelMapper.getByIdIgnoreTenant(modelId);
|
||||
AssertUtils.assertSame("模型未激活,请先在[AI模型配置]中[测试激活]模型", airagModel.getActivateFlag(), 1);
|
||||
return completions(airagModel, messages, params);
|
||||
}
|
||||
|
||||
@ -98,7 +98,25 @@ public class AIChatHandler implements IAIChatHandler {
|
||||
*/
|
||||
public String completions(AiragModel airagModel, List<ChatMessage> messages, AIChatParams params) {
|
||||
params = mergeParams(airagModel, params);
|
||||
String resp = llmHandler.completions(messages, params);
|
||||
String resp;
|
||||
try {
|
||||
resp = llmHandler.completions(messages, params);
|
||||
} catch (Exception e) {
|
||||
// langchain4j 异常友好提示
|
||||
String errMsg = "调用大模型接口失败,详情请查看后台日志。";
|
||||
if (oConvertUtils.isNotEmpty(e.getMessage())) {
|
||||
// // 根据常见异常关键字做细致翻译
|
||||
// for (Map.Entry<String, String> entry : MODEL_ERROR_MAP.entrySet()) {
|
||||
// String key = entry.getKey();
|
||||
// String value = entry.getValue();
|
||||
// if (errMsg.contains(key)) {
|
||||
// errMsg = value;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
log.error("AI模型调用异常: {}", errMsg, e);
|
||||
throw new JeecgBootException(errMsg);
|
||||
}
|
||||
if (resp.contains("</think>")
|
||||
&& (null == params.getNoThinking() || params.getNoThinking())) {
|
||||
String[] thinkSplit = resp.split("</think>");
|
||||
@ -151,6 +169,7 @@ public class AIChatHandler implements IAIChatHandler {
|
||||
AssertUtils.assertNotEmpty("请选择模型", modelId);
|
||||
|
||||
AiragModel airagModel = airagModelMapper.getByIdIgnoreTenant(modelId);
|
||||
AssertUtils.assertSame("模型未激活,请先在[AI模型配置]中[测试激活]模型", airagModel.getActivateFlag(), 1);
|
||||
return chat(airagModel, messages, params);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
package org.jeecg.modules.airag.llm.handler;
|
||||
|
||||
import dev.langchain4j.agent.tool.ToolSpecification;
|
||||
import dev.langchain4j.service.tool.ToolExecutor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* for [QQYUN-13565]【AI助手】新增创建用户和查询用户的工具扩展
|
||||
* @Description: jeecg llm工具提供者
|
||||
* @Author: chenrui
|
||||
* @Date: 2025/8/26 18:06
|
||||
*/
|
||||
public interface JeecgToolsProvider {
|
||||
|
||||
/**
|
||||
* 获取默认的工具列表
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/27 09:49
|
||||
*/
|
||||
public Map<ToolSpecification, ToolExecutor> getDefaultTools();
|
||||
|
||||
/**
|
||||
* jeecgLlm工具类
|
||||
* @author chenrui
|
||||
* @date 2025/8/27 09:49
|
||||
*/
|
||||
@Getter
|
||||
class JeecgLlmTools{
|
||||
ToolSpecification toolSpecification;
|
||||
ToolExecutor toolExecutor;
|
||||
|
||||
public JeecgLlmTools(ToolSpecification toolSpecification, ToolExecutor toolExecutor) {
|
||||
this.toolSpecification = toolSpecification;
|
||||
this.toolExecutor = toolExecutor;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,82 +1,82 @@
|
||||
package org.jeecg.modules.airag.test;
|
||||
|
||||
import dev.langchain4j.data.document.Document;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.tika.parser.AutoDetectParser;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.airag.llm.document.TikaDocumentParser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.wildfly.common.Assert;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @Description: 文件解析测试
|
||||
* @Author: chenrui
|
||||
* @Date: 2025/2/11 16:11
|
||||
*/
|
||||
@Slf4j
|
||||
public class TestFileParse {
|
||||
|
||||
@Test
|
||||
public void testParseTxt() {
|
||||
readFile("test.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsePdf() {
|
||||
readFile("test.pdf");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseMd() {
|
||||
readFile("test.md");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDoc() {
|
||||
readFile("test.docx");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDoc2003() {
|
||||
readFile("test.doc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseExcel() {
|
||||
readFile("test.xlsx");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseExcel2003() {
|
||||
readFile("test.xls");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsePPT() {
|
||||
readFile("test.pptx");
|
||||
}
|
||||
@Test
|
||||
public void testParsePPT2003() {
|
||||
readFile("test.ppt");
|
||||
}
|
||||
|
||||
private static void readFile(String filePath) {
|
||||
try {
|
||||
ClassPathResource resource = new ClassPathResource(filePath);
|
||||
File file = resource.getFile();
|
||||
TikaDocumentParser parser = new TikaDocumentParser(AutoDetectParser::new, null, null, null);
|
||||
Document document = parser.parse(file);
|
||||
Assert.assertNotNull(document);
|
||||
System.out.println(filePath + "----" + document.text());
|
||||
Assert.assertTrue(oConvertUtils.isNotEmpty(document));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
//package org.jeecg.modules.airag.test;
|
||||
//
|
||||
//import dev.langchain4j.data.document.Document;
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import org.apache.tika.parser.AutoDetectParser;
|
||||
//import org.jeecg.common.util.oConvertUtils;
|
||||
//import org.jeecg.modules.airag.llm.document.TikaDocumentParser;
|
||||
//import org.junit.jupiter.api.Test;
|
||||
//import org.springframework.core.io.ClassPathResource;
|
||||
//import org.wildfly.common.Assert;
|
||||
//
|
||||
//import java.io.File;
|
||||
//import java.io.IOException;
|
||||
//
|
||||
///**
|
||||
// * @Description: 文件解析测试
|
||||
// * @Author: chenrui
|
||||
// * @Date: 2025/2/11 16:11
|
||||
// */
|
||||
//@Slf4j
|
||||
//public class TestFileParse {
|
||||
//
|
||||
// @Test
|
||||
// public void testParseTxt() {
|
||||
// readFile("test.txt");
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testParsePdf() {
|
||||
// readFile("test.pdf");
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testParseMd() {
|
||||
// readFile("test.md");
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testParseDoc() {
|
||||
// readFile("test.docx");
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testParseDoc2003() {
|
||||
// readFile("test.doc");
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testParseExcel() {
|
||||
// readFile("test.xlsx");
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testParseExcel2003() {
|
||||
// readFile("test.xls");
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testParsePPT() {
|
||||
// readFile("test.pptx");
|
||||
// }
|
||||
// @Test
|
||||
// public void testParsePPT2003() {
|
||||
// readFile("test.ppt");
|
||||
// }
|
||||
//
|
||||
// private static void readFile(String filePath) {
|
||||
// try {
|
||||
// ClassPathResource resource = new ClassPathResource(filePath);
|
||||
// File file = resource.getFile();
|
||||
// TikaDocumentParser parser = new TikaDocumentParser(AutoDetectParser::new, null, null, null);
|
||||
// Document document = parser.parse(file);
|
||||
// Assert.assertNotNull(document);
|
||||
// System.out.println(filePath + "----" + document.text());
|
||||
// Assert.assertTrue(oConvertUtils.isNotEmpty(document));
|
||||
// } catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
//}
|
||||
|
||||
@ -1,54 +1,54 @@
|
||||
package org.jeecg.modules.airag.test;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @Description: 流程测试
|
||||
* @Author: chenrui
|
||||
* @Date: 2025/2/11 16:11
|
||||
*/
|
||||
@Slf4j
|
||||
public class TestFlows {
|
||||
|
||||
@Test
|
||||
public void testRunFlow(){
|
||||
String id = "1889499701976358913";
|
||||
// String id = "1889571074002247682"; //switch
|
||||
// String id = "1889608218175463425"; //脚本
|
||||
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3Mzk1NDY0NDIsInVzZXJuYW1lIjoiamVlY2cifQ.CFIV79PUYmOAiqBKT3yjwihHWwf954DvS-4oKERmJVU";
|
||||
String request = request(id,token);
|
||||
System.out.println(request);
|
||||
}
|
||||
|
||||
private String request(String id,String token) {
|
||||
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url("http://localhost:7008/airag/airagFlow/flow/run/" + id + "?field1=%25E5%2593%2588%25E5%2593%2588&field2=%25E4%25B8%25AD%25E5%259B%25BD")
|
||||
.get()
|
||||
.addHeader("X-Access-Token", token)
|
||||
.addHeader("Accept", "*/*")
|
||||
.addHeader("Accept-Encoding", "gzip, deflate, br")
|
||||
.addHeader("User-Agent", "PostmanRuntime-ApipostRuntime/1.1.0")
|
||||
.addHeader("Connection", "keep-alive")
|
||||
.addHeader("Cookie", "JSESSIONID=442C48D3D1D0B2878A597AB6EBF2A07E")
|
||||
.build();
|
||||
|
||||
try {
|
||||
Response response = client.newCall(request).execute();
|
||||
return response.body().string();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO author: chenrui for:完善用例,使用java方式调用 date:2025/2/14
|
||||
|
||||
}
|
||||
//package org.jeecg.modules.airag.test;
|
||||
//
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import okhttp3.OkHttpClient;
|
||||
//import okhttp3.Request;
|
||||
//import okhttp3.Response;
|
||||
//import org.junit.jupiter.api.Test;
|
||||
//
|
||||
//import java.io.IOException;
|
||||
//
|
||||
///**
|
||||
// * @Description: 流程测试
|
||||
// * @Author: chenrui
|
||||
// * @Date: 2025/2/11 16:11
|
||||
// */
|
||||
//@Slf4j
|
||||
//public class TestFlows {
|
||||
//
|
||||
// @Test
|
||||
// public void testRunFlow(){
|
||||
// String id = "1889499701976358913";
|
||||
//// String id = "1889571074002247682"; //switch
|
||||
//// String id = "1889608218175463425"; //脚本
|
||||
// String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3Mzk1NDY0NDIsInVzZXJuYW1lIjoiamVlY2cifQ.CFIV79PUYmOAiqBKT3yjwihHWwf954DvS-4oKERmJVU";
|
||||
// String request = request(id,token);
|
||||
// System.out.println(request);
|
||||
// }
|
||||
//
|
||||
// private String request(String id,String token) {
|
||||
//
|
||||
// OkHttpClient client = new OkHttpClient();
|
||||
//
|
||||
// Request request = new Request.Builder()
|
||||
// .url("http://localhost:7008/airag/airagFlow/flow/run/" + id + "?field1=%25E5%2593%2588%25E5%2593%2588&field2=%25E4%25B8%25AD%25E5%259B%25BD")
|
||||
// .get()
|
||||
// .addHeader("X-Access-Token", token)
|
||||
// .addHeader("Accept", "*/*")
|
||||
// .addHeader("Accept-Encoding", "gzip, deflate, br")
|
||||
// .addHeader("User-Agent", "PostmanRuntime-ApipostRuntime/1.1.0")
|
||||
// .addHeader("Connection", "keep-alive")
|
||||
// .addHeader("Cookie", "JSESSIONID=442C48D3D1D0B2878A597AB6EBF2A07E")
|
||||
// .build();
|
||||
//
|
||||
// try {
|
||||
// Response response = client.newCall(request).execute();
|
||||
// return response.body().string();
|
||||
// } catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // TODO author: chenrui for:完善用例,使用java方式调用 date:2025/2/14
|
||||
//
|
||||
//}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>jeecg-boot-module</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>3.8.1</version>
|
||||
<version>3.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ public class JeecgDemoController extends JeecgController<JeecgDemo, IJeecgDemoSe
|
||||
*/
|
||||
@Operation(summary = "获取Demo数据列表")
|
||||
@GetMapping(value = "/list")
|
||||
@PermissionData(pageComponent = "jeecg/JeecgDemoList")
|
||||
@PermissionData(pageComponent = "system/examples/demo/index")
|
||||
public Result<?> list(JeecgDemo jeecgDemo, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<JeecgDemo> queryWrapper = QueryGenerator.initQueryWrapper(jeecgDemo, req.getParameterMap());
|
||||
|
||||
@ -409,4 +409,30 @@ public class DlMockController {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取车辆最后一个位置
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/findLatestCarLngLat")
|
||||
public List findLatestCarLngLat() {
|
||||
// 模拟JSON数据路径
|
||||
String path = "classpath:org/jeecg/modules/dlglong/json/CarLngLat.json";
|
||||
// 读取JSON数据
|
||||
return readJsonData(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取车辆最后一个位置
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/findCarTrace")
|
||||
public List findCarTrace() {
|
||||
// 模拟JSON数据路径
|
||||
String path = "classpath:org/jeecg/modules/dlglong/json/CarTrace.json";
|
||||
// 读取JSON数据
|
||||
return readJsonData(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"id": "6891ba44421aa907bcb7390c",
|
||||
"alarm": "0",
|
||||
"altitude": "13",
|
||||
"direction": "0",
|
||||
"latitude": "38.918739",
|
||||
"longitude": "117.758737",
|
||||
"speed": "11",
|
||||
"status": "4980739",
|
||||
"timestamp": "2025-08-05T16:01:07",
|
||||
"imei": "18441136860"
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>jeecg-boot-parent</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>3.8.1</version>
|
||||
<version>3.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>jeecg-system-api</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>3.8.1</version>
|
||||
<version>3.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -2,16 +2,19 @@ package org.jeecg.common.system.api;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.api.dto.AiragFlowDTO;
|
||||
import org.jeecg.common.api.dto.DataLogDTO;
|
||||
import org.jeecg.common.api.dto.OnlineAuthDTO;
|
||||
import org.jeecg.common.api.dto.message.*;
|
||||
import org.jeecg.common.constant.ServiceNameConstants;
|
||||
import org.jeecg.common.constant.enums.DySmsEnum;
|
||||
import org.jeecg.common.constant.enums.EmailTemplateEnum;
|
||||
import org.jeecg.common.desensitization.annotation.SensitiveDecode;
|
||||
import org.jeecg.common.system.api.factory.SysBaseAPIFallbackFactory;
|
||||
import org.jeecg.common.system.vo.*;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@ -523,6 +526,16 @@ public interface ISysBaseAPI extends CommonAPI {
|
||||
*/
|
||||
@GetMapping("/sys/api/sendHtmlTemplateEmail")
|
||||
void sendHtmlTemplateEmail(@RequestParam("email") String email, @RequestParam("title") String title, @RequestParam("emailEnum") EmailTemplateEnum emailTemplateEnum, @RequestParam("params") JSONObject params);
|
||||
/**
|
||||
/**
|
||||
* 发送短信消息
|
||||
*
|
||||
* @param phone 手机号码
|
||||
* @param params 模版参数
|
||||
* @param dySmsEnum 短信模版枚举
|
||||
*/
|
||||
@GetMapping("/sys/api/sendSmsMsg")
|
||||
void sendSmsMsg(@RequestParam("phone") String phone, @RequestParam("params") JSONObject params,@RequestParam("dySmsEnum") DySmsEnum dySmsEnum);
|
||||
/**
|
||||
* 41 获取公司下级部门和公司下所有用户id
|
||||
* @param orgCode 部门编号
|
||||
@ -670,7 +683,7 @@ public interface ISysBaseAPI extends CommonAPI {
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/sys/api/getRoleCode")
|
||||
String getRoleCodeById(String id);
|
||||
String getRoleCodeById(@RequestParam(name = "id") String id);
|
||||
|
||||
/**
|
||||
* 根据roleCode查询角色信息,可逗号分隔多个
|
||||
@ -698,7 +711,7 @@ public interface ISysBaseAPI extends CommonAPI {
|
||||
* @return JSONObject
|
||||
*/
|
||||
@GetMapping("/sys/api/queryUserById")
|
||||
JSONObject queryUserById(String id);
|
||||
JSONObject queryUserById(@RequestParam(name="id") String id);
|
||||
|
||||
|
||||
/**
|
||||
@ -759,7 +772,7 @@ public interface ISysBaseAPI extends CommonAPI {
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/sys/api/queryUserIdsByPositionIds")
|
||||
List<String> queryUserIdsByPositionIds(List<String> positionIds);
|
||||
List<String> queryUserIdsByPositionIds(@RequestParam("positionIds") List<String> positionIds);
|
||||
|
||||
/**
|
||||
* 根据部门和子部门下的所有用户账号
|
||||
@ -791,5 +804,69 @@ public interface ISysBaseAPI extends CommonAPI {
|
||||
@RequestParam("tableOrDictCode") String tableOrDictCode,
|
||||
@RequestParam(value = "fields", required = false) String... fields
|
||||
);
|
||||
/**
|
||||
* 自动发布通告
|
||||
*
|
||||
* @param dataId 通告ID
|
||||
* @param currentUserName 发送人
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/sys/api/announcementAutoRelease")
|
||||
void announcementAutoRelease(
|
||||
@RequestParam("dataId") String dataId,
|
||||
@RequestParam(value = "currentUserName") String currentUserName
|
||||
);
|
||||
|
||||
/**
|
||||
* 根据部门编码查询公司信息
|
||||
* @param orgCode 部门编码
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/12 14:45
|
||||
*/
|
||||
@GetMapping(value = "/sys/api/queryCompByOrgCode")
|
||||
SysDepartModel queryCompByOrgCode(@RequestParam(name = "sysCode") String orgCode);
|
||||
|
||||
/**
|
||||
* 根据部门编码和层次查询上级公司
|
||||
*
|
||||
* @param orgCode 部门编码
|
||||
* @param level 可以传空 默认为1级 最小值为1
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/sys/api/queryCompByOrgCodeAndLevel")
|
||||
SysDepartModel queryCompByOrgCodeAndLevel(@RequestParam("orgCode") String orgCode, @RequestParam("level") Integer level);
|
||||
|
||||
/**
|
||||
* 16 运行AIRag流程
|
||||
* for [QQYUN-13634]在baseapi里面封装方法,方便其他模块调用
|
||||
*
|
||||
* @param airagFlowDTO
|
||||
* @return 流程执行结果,可能是String或者Map
|
||||
* @author chenrui
|
||||
* @date 2025/9/2 11:43
|
||||
*/
|
||||
@PostMapping(value = "/sys/api/runAiragFlow")
|
||||
Object runAiragFlow(@RequestBody AiragFlowDTO airagFlowDTO);
|
||||
|
||||
/**
|
||||
* 根据部门code或部门id获取部门名称(当前和上级部门)
|
||||
*
|
||||
* @param orgCode 部门编码
|
||||
* @param depId 部门id
|
||||
* @return String 部门名称
|
||||
*/
|
||||
@GetMapping("/getDepartPathNameByOrgCode")
|
||||
String getDepartPathNameByOrgCode(@RequestParam(name = "orgCode", required = false) String orgCode, @RequestParam(name = "depId", required = false) String depId);
|
||||
|
||||
|
||||
/**
|
||||
* 根据部门ID查询部门及其子部门下用户ID <br/>
|
||||
* @param deptIds
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/09/08 15:28
|
||||
*/
|
||||
@GetMapping("/sys/api/queryUserIdsByCascadeDeptIds")
|
||||
List<String> queryUserIdsByCascadeDeptIds(@RequestParam("deptIds") List<String> deptIds);
|
||||
}
|
||||
|
||||
@ -3,9 +3,11 @@ package org.jeecg.common.system.api.fallback;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.dto.AiragFlowDTO;
|
||||
import org.jeecg.common.api.dto.DataLogDTO;
|
||||
import org.jeecg.common.api.dto.OnlineAuthDTO;
|
||||
import org.jeecg.common.api.dto.message.*;
|
||||
import org.jeecg.common.constant.enums.DySmsEnum;
|
||||
import org.jeecg.common.constant.enums.EmailTemplateEnum;
|
||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||
import org.jeecg.common.system.vo.*;
|
||||
@ -341,6 +343,11 @@ public class SysBaseAPIFallback implements ISysBaseAPI {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendSmsMsg(String phone, JSONObject params, DySmsEnum dySmsEnum) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map> getDeptUserByOrgCode(String orgCode) {
|
||||
return null;
|
||||
@ -464,4 +471,33 @@ public class SysBaseAPIFallback implements ISysBaseAPI {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void announcementAutoRelease(String dataId, String currentUserName) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysDepartModel queryCompByOrgCode(String orgCode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysDepartModel queryCompByOrgCodeAndLevel(String orgCode, Integer level) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object runAiragFlow(AiragFlowDTO airagFlowDTO) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDepartPathNameByOrgCode(String orgCode, String depId) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> queryUserIdsByCascadeDeptIds(List<String> deptIds) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>jeecg-system-api</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>3.8.1</version>
|
||||
<version>3.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -2,11 +2,14 @@ package org.jeecg.common.system.api;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.api.dto.AiragFlowDTO;
|
||||
import org.jeecg.common.api.dto.DataLogDTO;
|
||||
import org.jeecg.common.api.dto.OnlineAuthDTO;
|
||||
import org.jeecg.common.api.dto.message.*;
|
||||
import org.jeecg.common.constant.enums.DySmsEnum;
|
||||
import org.jeecg.common.constant.enums.EmailTemplateEnum;
|
||||
import org.jeecg.common.system.vo.*;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -398,7 +401,13 @@ public interface ISysBaseAPI extends CommonAPI {
|
||||
* @return List<Map>
|
||||
*/
|
||||
List<Map> getDeptUserByOrgCode(String orgCode);
|
||||
|
||||
/**
|
||||
* 42 发送短信消息
|
||||
* @param phone 手机号
|
||||
* @param param 模版参数
|
||||
* @param dySmsEnum 短信模版
|
||||
*/
|
||||
void sendSmsMsg(String phone, JSONObject param, DySmsEnum dySmsEnum);
|
||||
/**
|
||||
* 查询分类字典翻译
|
||||
* @param ids 多个分类字典id
|
||||
@ -497,6 +506,14 @@ public interface ISysBaseAPI extends CommonAPI {
|
||||
* @return
|
||||
*/
|
||||
List<String> queryUserIdsByDeptIds(List<String> deptIds);
|
||||
|
||||
|
||||
/**
|
||||
* 根据部门ID查询部门及其子部门下用户ID <br/>
|
||||
* @param deptIds
|
||||
* @return
|
||||
*/
|
||||
List<String> queryUserIdsByCascadeDeptIds(List<String> deptIds);
|
||||
|
||||
/**
|
||||
* 根据部门ID查询用户账号
|
||||
@ -544,4 +561,48 @@ public interface ISysBaseAPI extends CommonAPI {
|
||||
*/
|
||||
boolean dictTableWhiteListCheckByDict(String tableOrDictCode, String... fields);
|
||||
|
||||
/**
|
||||
* 消息自动发布
|
||||
* @param dataId
|
||||
* @param currentUserName
|
||||
*/
|
||||
void announcementAutoRelease(String dataId, String currentUserName);
|
||||
|
||||
/**
|
||||
* 根据部门编码查询公司信息
|
||||
* @param orgCode 部门编码
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/12 14:53
|
||||
*/
|
||||
SysDepartModel queryCompByOrgCode(@RequestParam(name = "sysCode") String orgCode);
|
||||
|
||||
/**
|
||||
* 根据部门编码和层次查询上级公司
|
||||
*
|
||||
* @param orgCode 部门编码
|
||||
* @param level 可以传空 默认为1级 最小值为1
|
||||
* @return
|
||||
*/
|
||||
SysDepartModel queryCompByOrgCodeAndLevel(String orgCode, Integer level);
|
||||
|
||||
/**
|
||||
* 16 运行AIRag流程
|
||||
* for [QQYUN-13634]在baseapi里面封装方法,方便其他模块调用
|
||||
*
|
||||
* @param airagFlowDTO
|
||||
* @return 流程执行结果,可能是String或者Map
|
||||
* @author chenrui
|
||||
* @date 2025/9/2 11:43
|
||||
*/
|
||||
Object runAiragFlow(AiragFlowDTO airagFlowDTO);
|
||||
|
||||
/**
|
||||
* 根据部门code或部门id获取部门名称(当前和上级部门)
|
||||
*
|
||||
* @param orgCode 部门编码
|
||||
* @param depId 部门id
|
||||
* @return String 部门名称
|
||||
*/
|
||||
String getDepartPathNameByOrgCode(String orgCode, String depId);
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>jeecg-module-system</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>3.8.1</version>
|
||||
<version>3.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-module-system</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<version>3.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@ -40,10 +40,16 @@
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
<artifactId>jimureport-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- mongo、redis和文件数据集支持包,按需引入 -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
<artifactId>jimureport-nosql-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- 后台导出接口Echart图表支持包,按需引入
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
<artifactId>jimureport-echarts-starter</artifactId>
|
||||
</dependency> -->
|
||||
<!-- 积木BI -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
|
||||
@ -57,7 +57,7 @@ public class CodeTemplateInitListener implements ApplicationListener<Application
|
||||
|
||||
// 非jar模式不生成模板
|
||||
// 不生成目录,只生成具体模板文件
|
||||
if (!filepath.contains(".jar!/BOOT-INF/lib/") || !createFilePath.contains(".")) {
|
||||
if ((!filepath.contains(".jar!/BOOT-INF/lib/") && !filepath.contains(".jar/!BOOT-INF/lib/")) || !createFilePath.contains(".")) {
|
||||
continue;
|
||||
}
|
||||
if (!FileUtil.exist(createFilePath)) {
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
package org.jeecg.config.init;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Shiro缓存清理
|
||||
* 在应用启动时清除所有的Shiro授权缓存
|
||||
* 主要用于解决重启项目,用户未重新登录,按钮权限不生效的问题
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ShiroCacheClearRunner implements ApplicationRunner {
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
// 清空所有授权redis缓存
|
||||
log.info("——— Service restart, clearing all user shiro authorization cache ——— ");
|
||||
redisUtil.removeAll(CommonConstant.PREFIX_USER_SHIRO_CACHE);
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,33 +1,33 @@
|
||||
//package org.jeecg.config.init;
|
||||
//
|
||||
//import org.apache.catalina.Context;
|
||||
//import org.apache.tomcat.util.scan.StandardJarScanner;
|
||||
//import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//
|
||||
///**
|
||||
// * @Description: TomcatFactoryConfig
|
||||
// * @author: scott
|
||||
// * @date: 2021年01月25日 11:40
|
||||
// */
|
||||
//@Configuration
|
||||
//public class TomcatFactoryConfig {
|
||||
// /**
|
||||
// * tomcat-embed-jasper引用后提示jar找不到的问题
|
||||
// */
|
||||
// @Bean
|
||||
// public TomcatServletWebServerFactory tomcatFactory() {
|
||||
// TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory() {
|
||||
// @Override
|
||||
// protected void postProcessContext(Context context) {
|
||||
// ((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
|
||||
// }
|
||||
// };
|
||||
// factory.addConnectorCustomizers(connector -> {
|
||||
// connector.setProperty("relaxedPathChars", "[]{}");
|
||||
// connector.setProperty("relaxedQueryChars", "[]{}");
|
||||
// });
|
||||
// return factory;
|
||||
// }
|
||||
//}
|
||||
package org.jeecg.config.init;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.tomcat.util.scan.StandardJarScanner;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @Description: TomcatFactoryConfig
|
||||
* @author: scott
|
||||
* @date: 2021年01月25日 11:40
|
||||
*/
|
||||
@Configuration
|
||||
public class TomcatFactoryConfig {
|
||||
/**
|
||||
* tomcat-embed-jasper引用后提示jar找不到的问题
|
||||
*/
|
||||
@Bean
|
||||
public TomcatServletWebServerFactory tomcatFactory() {
|
||||
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory() {
|
||||
@Override
|
||||
protected void postProcessContext(Context context) {
|
||||
((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
|
||||
}
|
||||
};
|
||||
factory.addConnectorCustomizers(connector -> {
|
||||
connector.setProperty("relaxedPathChars", "[]{}");
|
||||
connector.setProperty("relaxedQueryChars", "[]{}");
|
||||
});
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,42 +1,48 @@
|
||||
package org.jeecg.config.init;
|
||||
|
||||
import io.undertow.server.DefaultByteBufferPool;
|
||||
import io.undertow.server.handlers.BlockingHandler;
|
||||
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
|
||||
import org.jeecg.modules.monitor.actuator.undertow.CustomUndertowMetricsHandler;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Undertow配置
|
||||
*
|
||||
* 解决启动提示: WARN io.undertow.websockets.jsr:68 - UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used
|
||||
*/
|
||||
@Configuration
|
||||
public class UndertowConfiguration implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
|
||||
|
||||
/**
|
||||
* 自定义undertow监控指标工具类
|
||||
* for [QQYUN-11902]tomcat 替换undertow 这里的功能还没修改
|
||||
*/
|
||||
@Autowired
|
||||
private CustomUndertowMetricsHandler customUndertowMetricsHandler;
|
||||
|
||||
@Override
|
||||
public void customize(UndertowServletWebServerFactory factory) {
|
||||
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
|
||||
|
||||
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
|
||||
|
||||
// 设置合理的参数
|
||||
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 8192));
|
||||
|
||||
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
|
||||
|
||||
// 添加自定义 监控 handler
|
||||
deploymentInfo.addInitialHandlerChainWrapper(next -> new BlockingHandler(customUndertowMetricsHandler.wrap(next)));
|
||||
});
|
||||
}
|
||||
}
|
||||
//package org.jeecg.config.init;
|
||||
//
|
||||
//import io.undertow.UndertowOptions;
|
||||
//import io.undertow.server.DefaultByteBufferPool;
|
||||
//import io.undertow.server.handlers.BlockingHandler;
|
||||
//import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
|
||||
//import org.jeecg.modules.monitor.actuator.undertow.CustomUndertowMetricsHandler;
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
||||
//import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//
|
||||
///**
|
||||
// * Undertow配置
|
||||
// *
|
||||
// * 解决启动提示: WARN io.undertow.websockets.jsr:68 - UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used
|
||||
// */
|
||||
//@Configuration
|
||||
//public class UndertowConfiguration implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
|
||||
//
|
||||
// /**
|
||||
// * 自定义undertow监控指标工具类
|
||||
// * for [QQYUN-11902]tomcat 替换undertow 这里的功能还没修改
|
||||
// */
|
||||
// @Autowired
|
||||
// private CustomUndertowMetricsHandler customUndertowMetricsHandler;
|
||||
//
|
||||
// @Override
|
||||
// public void customize(UndertowServletWebServerFactory factory) {
|
||||
// // 设置 Undertow 服务器参数(底层网络配置)
|
||||
// factory.addBuilderCustomizers(builder -> {
|
||||
// builder.setServerOption(UndertowOptions.MAX_HEADER_SIZE, 65536); // header 最大64KB
|
||||
// builder.setServerOption(UndertowOptions.MAX_PARAMETERS, 10000); // 最大参数数
|
||||
// });
|
||||
// factory.addDeploymentInfoCustomizers(deploymentInfo -> {
|
||||
//
|
||||
// WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
|
||||
//
|
||||
// // 设置合理的参数
|
||||
// webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 8192));
|
||||
//
|
||||
// deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
|
||||
//
|
||||
// // 添加自定义 监控 handler
|
||||
// deploymentInfo.addInitialHandlerChainWrapper(next -> new BlockingHandler(customUndertowMetricsHandler.wrap(next)));
|
||||
// });
|
||||
// }
|
||||
//}
|
||||
@ -0,0 +1,258 @@
|
||||
package org.jeecg.modules.airag;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import dev.langchain4j.agent.tool.JsonSchemaProperty;
|
||||
import dev.langchain4j.agent.tool.ToolSpecification;
|
||||
import dev.langchain4j.service.tool.ToolExecutor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||
import org.jeecg.common.util.PasswordUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.airag.llm.handler.JeecgToolsProvider;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.jeecg.modules.system.controller.SysUserController;
|
||||
import org.jeecg.modules.system.entity.SysRole;
|
||||
import org.jeecg.modules.system.entity.SysUser;
|
||||
import org.jeecg.modules.system.mapper.SysUserMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* for [QQYUN-13565]【AI助手】新增创建用户和查询用户的工具扩展
|
||||
* @Description: jeecg llm工具提供者
|
||||
* @Author: chenrui
|
||||
* @Date: 2025/8/26 18:06
|
||||
*/
|
||||
@Component
|
||||
public class JeecgBizToolsProvider implements JeecgToolsProvider {
|
||||
|
||||
@Autowired
|
||||
SysUserController sysUserController;
|
||||
|
||||
@Autowired
|
||||
SysUserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private BaseCommonService baseCommonService;
|
||||
|
||||
@Autowired
|
||||
private org.jeecg.modules.system.service.ISysRoleService sysRoleService;
|
||||
|
||||
@Autowired
|
||||
private org.jeecg.modules.system.service.ISysUserRoleService sysUserRoleService;
|
||||
|
||||
@Autowired
|
||||
private org.jeecg.modules.system.service.ISysUserService sysUserService;
|
||||
|
||||
public Map<ToolSpecification, ToolExecutor> getDefaultTools(){
|
||||
Map<ToolSpecification, ToolExecutor> tools = new HashMap<>();
|
||||
JeecgLlmTools userTool = queryUserTool();
|
||||
tools.put(userTool.getToolSpecification(), userTool.getToolExecutor());
|
||||
JeecgLlmTools addUser = addUserTool();
|
||||
tools.put(addUser.getToolSpecification(), addUser.getToolExecutor());
|
||||
// 新增:查询所有角色
|
||||
JeecgLlmTools queryRoles = queryAllRolesTool();
|
||||
tools.put(queryRoles.getToolSpecification(), queryRoles.getToolExecutor());
|
||||
// 新增:给用户授予角色
|
||||
JeecgLlmTools grantRoles = grantUserRolesTool();
|
||||
tools.put(grantRoles.getToolSpecification(), grantRoles.getToolExecutor());
|
||||
return tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/27 09:51
|
||||
*/
|
||||
private JeecgLlmTools addUserTool(){
|
||||
ToolSpecification toolSpecification = ToolSpecification.builder()
|
||||
.name("add_user")
|
||||
.description("添加用户,返回添加结果;" +
|
||||
"\n\n - 缺少必要字段时,请向用户索要." +
|
||||
"\n\n - 你应该提前判断用户的输入是否合法,比如用户名是否符合规范,手机号和邮箱是否正确等." +
|
||||
"\n\n - 提前使用用户名查询用户是否存在,如果存在则不能添加." +
|
||||
"\n\n - 添加成功后返回成功消息,如果失败则返回失败原因." +
|
||||
"\n\n - 用户名,工号,邮箱,手机号均要求唯一,提前通过查询用户工具确认唯一性." )
|
||||
.addParameter("username", JsonSchemaProperty.STRING, JsonSchemaProperty.description("用户名,必填,只允许使用字母、数字、下划线,且必须以字母开头,唯一"))
|
||||
.addParameter("password", JsonSchemaProperty.STRING, JsonSchemaProperty.description("用户密码,必填"))
|
||||
.addParameter("realname", JsonSchemaProperty.STRING, JsonSchemaProperty.description("真实姓名,必填"))
|
||||
.addParameter("workNo", JsonSchemaProperty.STRING, JsonSchemaProperty.description("工号,必填,唯一"))
|
||||
.addParameter("email", JsonSchemaProperty.STRING, JsonSchemaProperty.description("邮箱,必填,唯一"))
|
||||
.addParameter("phone", JsonSchemaProperty.STRING, JsonSchemaProperty.description("手机号,必填,唯一"))
|
||||
.build();
|
||||
ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {
|
||||
JSONObject arguments = JSONObject.parseObject(toolExecutionRequest.arguments());
|
||||
arguments.put("confirmPassword",arguments.get("password"));
|
||||
arguments.put("userIdentity",1);
|
||||
arguments.put("activitiSync",1);
|
||||
arguments.put("departIds","");
|
||||
String selectedRoles = arguments.getString("selectedroles");
|
||||
String selectedDeparts = arguments.getString("selecteddeparts");
|
||||
String msg = "添加用户失败";
|
||||
try {
|
||||
SysUser user = JSON.parseObject(arguments.toJSONString(), SysUser.class);
|
||||
user.setCreateTime(new Date());//设置创建时间
|
||||
String salt = oConvertUtils.randomGen(8);
|
||||
user.setSalt(salt);
|
||||
String passwordEncode = PasswordUtil.encrypt(user.getUsername(), user.getPassword(), salt);
|
||||
user.setPassword(passwordEncode);
|
||||
user.setStatus(1);
|
||||
user.setDelFlag(CommonConstant.DEL_FLAG_0);
|
||||
//用户表字段org_code不能在这里设置他的值
|
||||
user.setOrgCode(null);
|
||||
// 保存用户走一个service 保证事务
|
||||
//获取租户ids
|
||||
String relTenantIds = arguments.getString("relTenantIds");
|
||||
sysUserService.saveUser(user, selectedRoles, selectedDeparts, relTenantIds, false);
|
||||
baseCommonService.addLog("添加用户,username: " +user.getUsername() ,CommonConstant.LOG_TYPE_2, 2);
|
||||
msg = "添加用户成功";
|
||||
// 用户变更,触发同步工作流
|
||||
} catch (Exception e) {
|
||||
msg = "添加用户失败";
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
return new JeecgLlmTools(toolSpecification,toolExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户信息
|
||||
*
|
||||
* @return 用户列表JSON字符串
|
||||
* @author chenrui
|
||||
* @date 2025/8/26 18:52
|
||||
*/
|
||||
private JeecgLlmTools queryUserTool() {
|
||||
ToolSpecification toolSpecification = ToolSpecification.builder()
|
||||
.name("query_user_by_name")
|
||||
.description("查询用户详细信息,返回json数组。支持用户名、真实姓名、邮箱、手机号、工号多字段组合查询,用户名、真实姓名、邮箱、手机号均为模糊查询,工号为精确查询。无条件则返回全部用户。")
|
||||
.addParameter("username", JsonSchemaProperty.STRING, JsonSchemaProperty.description("用户名"))
|
||||
.addParameter("realname", JsonSchemaProperty.STRING, JsonSchemaProperty.description("真实姓名"))
|
||||
.addParameter("email", JsonSchemaProperty.STRING, JsonSchemaProperty.description("电子邮件"))
|
||||
.addParameter("phone", JsonSchemaProperty.STRING, JsonSchemaProperty.description("手机号"))
|
||||
.addParameter("workNo", JsonSchemaProperty.STRING, JsonSchemaProperty.description("工号"))
|
||||
.build();
|
||||
ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {
|
||||
SysUser args = JSONObject.parseObject(toolExecutionRequest.arguments(), SysUser.class);
|
||||
QueryWrapper<SysUser> qw = new QueryWrapper<>();
|
||||
if (StringUtils.isNotBlank(args.getUsername())) {
|
||||
qw.like("username", args.getUsername());
|
||||
}
|
||||
if (StringUtils.isNotBlank(args.getRealname())) {
|
||||
qw.like("realname", args.getRealname());
|
||||
}
|
||||
if (StringUtils.isNotBlank(args.getEmail())) {
|
||||
qw.like("email", args.getEmail());
|
||||
}
|
||||
if (StringUtils.isNotBlank(args.getPhone())) {
|
||||
qw.like("phone", args.getPhone());
|
||||
}
|
||||
if (StringUtils.isNotBlank(args.getWorkNo())) {
|
||||
qw.eq("work_no", args.getWorkNo());
|
||||
}
|
||||
qw.eq("del_flag", 0);
|
||||
List<SysUser> users = userMapper.selectList(qw);
|
||||
users.forEach(u -> { u.setPassword(null); u.setSalt(null); });
|
||||
return JSONObject.toJSONString(users);
|
||||
};
|
||||
return new JeecgLlmTools(toolSpecification, toolExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有角色
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/27 09:52
|
||||
*/
|
||||
private JeecgLlmTools queryAllRolesTool() {
|
||||
ToolSpecification spec = ToolSpecification.builder()
|
||||
.name("query_all_roles")
|
||||
.description("查询所有角色,返回json数组。包含字段:id、roleName、roleCode;默认按创建时间/排序号规则由后端决定。")
|
||||
.addParameter("roleName", JsonSchemaProperty.STRING, JsonSchemaProperty.description("角色姓名"))
|
||||
.addParameter("roleCode", JsonSchemaProperty.STRING, JsonSchemaProperty.description("角色编码"))
|
||||
.build();
|
||||
ToolExecutor exec = (toolExecutionRequest, memoryId) -> {
|
||||
// 做租户隔离查询(若开启)
|
||||
SysRole sysRole = JSONObject.parseObject(toolExecutionRequest.arguments(), SysRole.class);
|
||||
QueryWrapper<SysRole> qw = Wrappers.query();
|
||||
if (StringUtils.isNotBlank(sysRole.getRoleName())) {
|
||||
qw.like("role_name", sysRole.getRoleName());
|
||||
}
|
||||
if (StringUtils.isNotBlank(sysRole.getRoleCode())) {
|
||||
qw.like("role_code", sysRole.getRoleCode());
|
||||
}
|
||||
// 未删除
|
||||
List<org.jeecg.modules.system.entity.SysRole> roles = sysRoleService.list(qw);
|
||||
// 仅返回核心字段
|
||||
JSONArray arr = new JSONArray();
|
||||
for (org.jeecg.modules.system.entity.SysRole r : roles) {
|
||||
JSONObject o = new JSONObject();
|
||||
o.put("id", r.getId());
|
||||
o.put("roleName", r.getRoleName());
|
||||
o.put("roleCode", r.getRoleCode());
|
||||
arr.add(o);
|
||||
}
|
||||
return arr.toJSONString();
|
||||
};
|
||||
return new JeecgLlmTools(spec, exec);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给用户授予角色
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/27 09:52
|
||||
*/
|
||||
private JeecgLlmTools grantUserRolesTool() {
|
||||
ToolSpecification spec = ToolSpecification.builder()
|
||||
.name("grant_user_roles")
|
||||
.description("给用户授予角色,支持一次授予多个角色;如果关系已存在则跳过。返回授予结果统计。")
|
||||
.addParameter("userId", JsonSchemaProperty.STRING, JsonSchemaProperty.description("用户ID,必填"))
|
||||
.addParameter("roleIds", JsonSchemaProperty.STRING, JsonSchemaProperty.description("角色ID列表,必填,使用英文逗号分隔"))
|
||||
.build();
|
||||
ToolExecutor exec = (toolExecutionRequest, memoryId) -> {
|
||||
JSONObject args = JSONObject.parseObject(toolExecutionRequest.arguments());
|
||||
String userId = args.getString("userId");
|
||||
String roleIdsStr = args.getString("roleIds");
|
||||
if (org.apache.commons.lang3.StringUtils.isAnyBlank(userId, roleIdsStr)) {
|
||||
return "参数缺失:userId 或 roleIds";
|
||||
}
|
||||
org.jeecg.modules.system.entity.SysUser user = sysUserService.getById(userId);
|
||||
if (user == null) {
|
||||
return "用户不存在:" + userId;
|
||||
}
|
||||
String[] roleIds = roleIdsStr.split(",");
|
||||
int added = 0, existed = 0, invalid = 0;
|
||||
for (String roleId : roleIds) {
|
||||
roleId = roleId.trim();
|
||||
if (roleId.isEmpty()) continue;
|
||||
org.jeecg.modules.system.entity.SysRole role = sysRoleService.getById(roleId);
|
||||
if (role == null) { invalid++; continue; }
|
||||
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<org.jeecg.modules.system.entity.SysUserRole> q = new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
|
||||
q.eq("role_id", roleId).eq("user_id", userId);
|
||||
org.jeecg.modules.system.entity.SysUserRole one = sysUserRoleService.getOne(q);
|
||||
if (one == null) {
|
||||
org.jeecg.modules.system.entity.SysUserRole rel = new org.jeecg.modules.system.entity.SysUserRole(userId, roleId);
|
||||
boolean ok = sysUserRoleService.save(rel);
|
||||
if (ok) { added++; } else { invalid++; }
|
||||
} else {
|
||||
existed++;
|
||||
}
|
||||
}
|
||||
return String.format("授予完成:新增%d,已存在%d,无效/失败%d", added, existed, invalid);
|
||||
};
|
||||
return new JeecgLlmTools(spec, exec);
|
||||
}
|
||||
}
|
||||
@ -2,10 +2,13 @@ package org.jeecg.modules.api.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.dto.AiragFlowDTO;
|
||||
import org.jeecg.common.api.dto.DataLogDTO;
|
||||
import org.jeecg.common.api.dto.OnlineAuthDTO;
|
||||
import org.jeecg.common.api.dto.message.*;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.enums.DySmsEnum;
|
||||
import org.jeecg.common.constant.enums.EmailTemplateEnum;
|
||||
import org.jeecg.common.desensitization.util.SensitiveInfoUtil;
|
||||
import org.jeecg.common.system.vo.*;
|
||||
import org.jeecg.modules.system.service.ISysUserService;
|
||||
@ -578,6 +581,27 @@ public class SystemApiController {
|
||||
public void sendEmailMsg(@RequestParam("email")String email,@RequestParam("title")String title,@RequestParam("content")String content){
|
||||
this.sysBaseApi.sendEmailMsg(email,title,content);
|
||||
};
|
||||
/**
|
||||
* 发送html模版邮件消息
|
||||
* @param email
|
||||
* @param title
|
||||
* @param emailTemplateEnum 邮件模版枚举
|
||||
* @param params 模版参数
|
||||
*/
|
||||
@GetMapping("/sendHtmlTemplateEmail")
|
||||
public void sendHtmlTemplateEmail(@RequestParam("email")String email, @RequestParam("title")String title, @RequestParam("emailEnum") EmailTemplateEnum emailTemplateEnum, @RequestParam("params") JSONObject params){
|
||||
this.sysBaseApi.sendHtmlTemplateEmail(email,title,emailTemplateEnum,params);
|
||||
};
|
||||
/**
|
||||
* 发送短信消息
|
||||
* @param phone 手机号码
|
||||
* @param params 模版参数
|
||||
* @param dySmsEnum 短信模版枚举
|
||||
*/
|
||||
@GetMapping("/sendSmsMsg")
|
||||
public void sendSmsMsg(@RequestParam("phone")String phone, @RequestParam("params") JSONObject params, @RequestParam("dySmsEnum") DySmsEnum dySmsEnum){
|
||||
this.sysBaseApi.sendSmsMsg(phone,params,dySmsEnum);
|
||||
};
|
||||
/**
|
||||
* 41 获取公司下级部门和公司下所有用户信息
|
||||
* @param orgCode
|
||||
@ -976,5 +1000,76 @@ public class SystemApiController {
|
||||
) {
|
||||
return sysBaseApi.dictTableWhiteListCheckByDict(tableOrDictCode, fields);
|
||||
}
|
||||
/**
|
||||
* 自动发布通告
|
||||
*
|
||||
* @param dataId 通告ID
|
||||
* @param currentUserName 发送人
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/announcementAutoRelease")
|
||||
public void announcementAutoRelease(
|
||||
@RequestParam("dataId") String dataId,
|
||||
@RequestParam(value = "currentUserName", required = false) String currentUserName
|
||||
) {
|
||||
sysBaseApi.announcementAutoRelease(dataId, currentUserName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门编码查询公司信息
|
||||
* @param orgCode 部门编码
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/12 14:45
|
||||
*/
|
||||
@GetMapping(value = "/queryCompByOrgCode")
|
||||
SysDepartModel queryCompByOrgCode(@RequestParam(name = "sysCode") String orgCode) {
|
||||
return sysBaseApi.queryCompByOrgCode(orgCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门编码和层次查询上级公司
|
||||
*
|
||||
* @param orgCode 部门编码
|
||||
* @param level 可以传空 默认为1级 最小值为1
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/queryCompByOrgCodeAndLevel")
|
||||
SysDepartModel queryCompByOrgCodeAndLevel(@RequestParam("orgCode") String orgCode, @RequestParam("level") Integer level){
|
||||
return sysBaseApi.queryCompByOrgCodeAndLevel(orgCode,level);
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行AIRag流程
|
||||
* for [QQYUN-13634]在baseapi里面封装方法,方便其他模块调用
|
||||
* @param airagFlowDTO
|
||||
* @return 流程执行结果,可能是String或者Map
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/runAiragFlow")
|
||||
Object runAiragFlow(@RequestBody AiragFlowDTO airagFlowDTO) {
|
||||
return sysBaseApi.runAiragFlow(airagFlowDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门code或部门id获取部门名称(当前和上级部门)
|
||||
*
|
||||
* @param orgCode 部门编码
|
||||
* @param depId 部门id
|
||||
* @return String 部门名称
|
||||
*/
|
||||
@GetMapping(value = "/getDepartPathNameByOrgCode")
|
||||
String getDepartPathNameByOrgCode(@RequestParam(name = "orgCode", required = false) String orgCode, @RequestParam(name = "depId", required = false) String depId) {
|
||||
return sysBaseApi.getDepartPathNameByOrgCode(orgCode, depId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门ID查询用户ID
|
||||
* @param deptIds
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/queryUserIdsByCascadeDeptIds")
|
||||
public List<String> queryUserIdsByCascadeDeptIds(@RequestParam("deptIds") List<String> deptIds){
|
||||
return sysBaseApi.queryUserIdsByCascadeDeptIds(deptIds);
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,9 @@ public class SysMessageTemplate extends JeecgEntity{
|
||||
/**模板类型*/
|
||||
@Excel(name = "模板类型", width = 15)
|
||||
private java.lang.String templateType;
|
||||
/**模板分类*/
|
||||
@Excel(name = "模板类型(notice通知公告 other其他)", width = 15)
|
||||
private java.lang.String templateCategory;
|
||||
|
||||
/**已经应用/未应用 1是0否*/
|
||||
@Excel(name = "应用状态", width = 15)
|
||||
|
||||
@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject;
|
||||
import org.jeecg.common.api.dto.message.MessageDTO;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.WebsocketConst;
|
||||
import org.jeecg.common.constant.enums.NoticeTypeEnum;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
@ -74,11 +75,13 @@ public class SystemSendMsgHandle implements ISendMsgHandle {
|
||||
Map<String,Object> data = messageDTO.getData();
|
||||
String[] arr = messageDTO.getToUser().split(",");
|
||||
for(String username: arr){
|
||||
doSend(title, content, fromUser, username, data);
|
||||
//update-begin---author:wangshuai---date:2025-06-26---for:【QQYUN-12162】OA项目改造,系统重消息拆分,目前消息都在一起 需按分类进行拆分---
|
||||
doSend(title, content, fromUser, username, data, messageDTO.getNoticeType());
|
||||
//update-end---author:wangshuai---date:2025-06-26---for:【QQYUN-12162】OA项目改造,系统重消息拆分,目前消息都在一起 需按分类进行拆分---
|
||||
}
|
||||
}
|
||||
|
||||
private void doSend(String title, String msgContent, String fromUser, String toUser, Map<String, Object> data){
|
||||
private void doSend(String title, String msgContent, String fromUser, String toUser, Map<String, Object> data, String noticeType){
|
||||
SysAnnouncement announcement = new SysAnnouncement();
|
||||
if(data!=null){
|
||||
//摘要信息
|
||||
@ -91,12 +94,14 @@ public class SystemSendMsgHandle implements ISendMsgHandle {
|
||||
if(taskId!=null){
|
||||
announcement.setBusId(taskId.toString());
|
||||
announcement.setBusType(Vue3MessageHrefEnum.BPM_TASK.getBusType());
|
||||
noticeType = NoticeTypeEnum.NOTICE_TYPE_FLOW.getValue();
|
||||
}
|
||||
|
||||
// 流程内消息节点 发消息会传一个busType
|
||||
Object busType = data.get(CommonConstant.NOTICE_MSG_BUS_TYPE);
|
||||
if(busType!=null){
|
||||
announcement.setBusType(busType.toString());
|
||||
noticeType = NoticeTypeEnum.NOTICE_TYPE_FLOW.getValue();
|
||||
}
|
||||
}
|
||||
announcement.setTitile(title);
|
||||
@ -109,6 +114,11 @@ public class SystemSendMsgHandle implements ISendMsgHandle {
|
||||
//系统消息
|
||||
announcement.setMsgCategory("2");
|
||||
announcement.setDelFlag(String.valueOf(CommonConstant.DEL_FLAG_0));
|
||||
if(oConvertUtils.isEmpty(noticeType)){
|
||||
noticeType = NoticeTypeEnum.NOTICE_TYPE_SYSTEM.getValue();
|
||||
}
|
||||
announcement.setNoticeType(noticeType);
|
||||
announcement.setIzTop(CommonConstant.IZ_TOP_0);
|
||||
sysAnnouncementMapper.insert(announcement);
|
||||
// 2.插入用户通告阅读标记表记录
|
||||
String userId = toUser;
|
||||
@ -130,6 +140,7 @@ public class SystemSendMsgHandle implements ISendMsgHandle {
|
||||
obj.put(WebsocketConst.MSG_USER_ID, sysUser.getId());
|
||||
obj.put(WebsocketConst.MSG_ID, announcement.getId());
|
||||
obj.put(WebsocketConst.MSG_TXT, announcement.getTitile());
|
||||
obj.put(CommonConstant.NOTICE_TYPE,noticeType);
|
||||
webSocket.sendMessage(sysUser.getId(), obj.toJSONString());
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user