mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2026-01-23 03:27:26 +08:00
Compare commits
89 Commits
5bcb649384
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 97c76675e7 | |||
| d335ba8612 | |||
| 3cd987b2e9 | |||
| 03f27067d4 | |||
| e94cf00ec0 | |||
| c71e6a6d25 | |||
| 030dc503eb | |||
| 918b59120e | |||
| e2402c75b0 | |||
| 3735ca1687 | |||
| be466f0b03 | |||
| 4092eed2a2 | |||
| 901f05ed21 | |||
| 41877a6e8b | |||
| b7a3da89ca | |||
| de4a8ce652 | |||
| e533af285c | |||
| 23dc7b3f03 | |||
| e57aef0708 | |||
| 42087c0bf8 | |||
| 606edcc82f | |||
| 9082e986f1 | |||
| 40cd525bba | |||
| d6b6cf079e | |||
| 1b688e7cd2 | |||
| 58915a6410 | |||
| b67096dc54 | |||
| 67795493bd | |||
| e1c8f00bf2 | |||
| 17a81e89a5 | |||
| bcbf775756 | |||
| 462365890e | |||
| b686f9fbd1 | |||
| 872f84d006 | |||
| 26087172df | |||
| 281c3ff3c8 | |||
| 38d44c2487 | |||
| 8c88f8adf5 | |||
| 526734c5a5 | |||
| 44b48ad916 | |||
| 1a3ae4f61c | |||
| 859c509f08 | |||
| 0704f187af | |||
| 199d2b439e | |||
| 5f898ed034 | |||
| 5a9cb05c86 | |||
| 98936680d5 | |||
| fc043fd5f3 | |||
| 54531002a7 | |||
| 728a95c00d | |||
| 13c9951c1f | |||
| 8dfaa3c3e1 | |||
| 050c478dce | |||
| 2688e8b6e2 | |||
| a9f30f0ca5 | |||
| 1bf4a0595a | |||
| 74cd57fd99 | |||
| c9ac4c9945 | |||
| 3b3371ee1a | |||
| 1d3bde9fe7 | |||
| 90b50a51a7 | |||
| 668ac59a5c | |||
| 5f01bdd29b | |||
| 82bfcc7b14 | |||
| ef210a2242 | |||
| 435445cb4e | |||
| fdf7cd1f6b | |||
| 92a38f41b0 | |||
| 7ed3bcc912 | |||
| 2706c0a519 | |||
| f4b80365a9 | |||
| 1108aa5288 | |||
| 9919ae2bc5 | |||
| 1f73837b7d | |||
| 9571e0b169 | |||
| 1a923596db | |||
| 62549e0a1c | |||
| 2740a2f419 | |||
| 899264250c | |||
| 0be7d00eb2 | |||
| 7152ae9e49 | |||
| 58b41db786 | |||
| d715c7a0ac | |||
| aca407e1ce | |||
| cfea79a187 | |||
| 7848d1fb33 | |||
| 91fa645878 | |||
| adc191f03e | |||
| f6f2ef6316 |
@ -3,9 +3,6 @@ AIGC应用平台介绍
|
|||||||
|
|
||||||
一个全栈式 AI 开发平台,旨在帮助开发者快速构建和部署个性化的 AI 应用。
|
一个全栈式 AI 开发平台,旨在帮助开发者快速构建和部署个性化的 AI 应用。
|
||||||
|
|
||||||
> JDK说明:AI流程编排引擎暂时不支持jdk21,所以目前只能使用jdk8或者jdk17启动项目。
|
|
||||||
|
|
||||||
|
|
||||||
JeecgBoot平台的AIGC功能模块,是一套类似`Dify`的`AIGC应用开发平台`+`知识库问答`,是一款基于LLM大语言模型AI应用平台和 RAG 的知识库问答系统。
|
JeecgBoot平台的AIGC功能模块,是一套类似`Dify`的`AIGC应用开发平台`+`知识库问答`,是一款基于LLM大语言模型AI应用平台和 RAG 的知识库问答系统。
|
||||||
其直观的界面结合了 AI 流程编排、RAG 管道、知识库管理、模型管理、对接向量库、实时运行可观察等,让您可以快速从原型到生产,拥有AI服务能力。
|
其直观的界面结合了 AI 流程编排、RAG 管道、知识库管理、模型管理、对接向量库、实时运行可观察等,让您可以快速从原型到生产,拥有AI服务能力。
|
||||||
|
|
||||||
@ -109,6 +106,10 @@ JeecgBoot平台的AIGC功能模块,是一套类似`Dify`的`AIGC应用开发
|
|||||||
| ChatGTP | √ |
|
| ChatGTP | √ |
|
||||||
| Qwq | √ |
|
| Qwq | √ |
|
||||||
| 智库 | √ |
|
| 智库 | √ |
|
||||||
|
| claude | √ |
|
||||||
|
| vl模型 | √ |
|
||||||
|
| 千帆大模型 | √ |
|
||||||
|
| 通义千问 | √ |
|
||||||
| Ollama本地搭建大模型 | √ |
|
| Ollama本地搭建大模型 | √ |
|
||||||
| 等等。。 | √ |
|
| 等等。。 | √ |
|
||||||
|
|
||||||
|
|||||||
@ -1,124 +0,0 @@
|
|||||||
|
|
||||||
JeecgBoot低代码平台(商业版介绍)
|
|
||||||
===============
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
项目介绍
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
<h3 align="center">企业级AI低代码平台</h3>
|
|
||||||
|
|
||||||
|
|
||||||
JeecgBoot是一款集成AI应用的,基于BPM流程的低代码平台,旨在帮助企业快速实现低代码开发和构建个性化AI应用!前后端分离架构Ant Design&Vue3,SpringBoot,SpringCloud Alibaba,Mybatis-plus,Shiro。强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE, 帮助Java项目解决80%的重复工作,让开发更多关注业务,提高效率、节省成本,同时又不失灵活性!低代码能力:Online表单、表单设计、流程设计、Online报表、大屏/仪表盘设计、报表设计; AI应用平台功能:AI知识库问答、AI模型管理、AI流程编排、AI聊天等,支持含ChatGPT、DeepSeek、Ollama等多种AI大模型
|
|
||||||
|
|
||||||
JeecgBoot 提供了一系列 `低代码能力`,实现`真正的零代码`在线开发:Online表单开发、Online报表、复杂报表设计、打印设计、在线图表设计、仪表盘设计、大屏设计、移动图表能力、表单设计器、在线设计流程、流程自动化配置、插件能力(可插拔)
|
|
||||||
|
|
||||||
`AI赋能低代码:` 目前提供了AI应用、AI模型管理、AI流程编排、AI对话助手,AI建表、AI写文章、AI知识库问答、AI字段建议等功能;支持各种AI大模型ChatGPT、DeepSeek、Ollama、智普、千问等.
|
|
||||||
|
|
||||||
`JEECG宗旨是:` 简单功能由OnlineCoding配置实现,做到`零代码开发`;复杂功能由代码生成器生成进行手工Merge 实现`低代码开发`,既保证了`智能`又兼顾`灵活`;实现了低代码开发的同时又支持灵活编码,解决了当前低代码产品普遍不灵活的弊端!
|
|
||||||
|
|
||||||
`JEECG业务流程:` 采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计(松耦合)、并支持任务节点灵活配置,既保证了公司流程的保密性,又减少了开发人员的工作量。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### JeecgBoot商业版与同类产品区别
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
- 灵活性:jeecgboot基于开源技术栈,设计初考虑到可插拔性和集成灵活性,确保平台的智能性与灵活性,避免因平台过于庞大而导致的扩展困难。
|
|
||||||
- 流程管理:支持一个表单挂接多个流程,同时一个流程可以连接多个表单,增强了流程的灵活性和复杂性管理。
|
|
||||||
- 符合中国国情的流程:针对中国市场的特定需求,jeecgboot能够实现各种符合中国国情的业务流程。
|
|
||||||
- 强大的表单设计器:jeecgboot的表单设计器与敲敲云共享,具备高质量和智能化的特点,能够满足零代码应用的需求,业内同类产品中不多见。
|
|
||||||
- 报表功能:自主研发的报表工具,拥有独立知识产权,功能上比业内老牌产品如帆软更智能,操作简便。
|
|
||||||
- BI产品整合:提供大屏、仪表盘、门户等功能,完美解决这些需求,并支持移动面板的设计与渲染。
|
|
||||||
- 自主研发的模块:jeecgboot的所有模块均为自主研发,具有独立的知识产权。
|
|
||||||
- 颗粒度和功能细致:在功能细致度和颗粒度上,jeecgboot远超同类产品,尤其在零代码能力方面表现突出。
|
|
||||||
- 零代码应用管理:最新版支持与敲敲云的零代码应用管理能力的集成,使得jeecgboot既具备低代码,又具备零代码的应用能力,业内独一无二。
|
|
||||||
- 强大的代码生成器:作为开源代码生成器的先锋,jeecgboot在代码生成的智能化和在线低代码与代码生成的结合方面,优势明显。
|
|
||||||
- 精细化权限管理:提供行级和列级的数据权限控制,满足企业在ERP和OA领域对权限管理的严格需求。
|
|
||||||
- 多平台支持的APP:目前采用uniapp3实现,支持小程序、H5、App及鸿蒙、鸿蒙Next、Electron桌面应用等多种终端。
|
|
||||||
|
|
||||||
> 综上所述,jeecgboot不仅在功能上具备丰富性和灵活性,还在技术架构、权限管理和用户体验等方面展现出明显的优势,是一个综合性能强大的低代码平台。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
商业版演示
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
JeecgBoot vs 敲敲云
|
|
||||||
> - JeecgBoot是低代码产品拥有系列低代码能力,比如流程设计、表单设计、大屏设计,代码生成器,适合半开发模式(开发+低代码结合),也可以集成零代码应用管理模块.
|
|
||||||
> - 敲敲云是零代码产品,完全不写代码,通过配置搭建业务系统,其在jeecgboot基础上研发而成,删除了online、代码生成、OA等需要编码功能,只保留应用管理功能和聊天、日程、文件三个OA组件.
|
|
||||||
|
|
||||||
|
|
||||||
- JeecgBoot低代码: https://boot3.jeecg.com
|
|
||||||
- 敲敲云零代码:https://app.qiaoqiaoyun.com
|
|
||||||
- APP演示(多端): http://jeecg.com/appIndex
|
|
||||||
|
|
||||||
|
|
||||||
### 流程视频介绍
|
|
||||||
|
|
||||||
[](https://www.bilibili.com/video/BV1Nk4y1o7Qc)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 商业版功能简述
|
|
||||||
|
|
||||||
> 详细的功能介绍,[请联系官方](https://jeecg.com/vip)
|
|
||||||
|
|
||||||
```
|
|
||||||
│─更多商业功能
|
|
||||||
│ ├─流程设计器
|
|
||||||
│ ├─简流设计器(类钉钉版)
|
|
||||||
│ ├─门户设计(NEW)
|
|
||||||
│ ├─表单设计器
|
|
||||||
│ ├─大屏设计器
|
|
||||||
│ └─我的任务
|
|
||||||
│ └─历史流程
|
|
||||||
│ └─历史流程
|
|
||||||
│ └─流程实例管理
|
|
||||||
│ └─流程监听管理
|
|
||||||
│ └─流程表达式
|
|
||||||
│ └─我发起的流程
|
|
||||||
│ └─我的抄送
|
|
||||||
│ └─流程委派、抄送、跳转
|
|
||||||
│ └─OA办公组件
|
|
||||||
│ └─零代码应用管理(无需编码,在线搭建应用系统)
|
|
||||||
│ ├─积木报表企业版(含jimureport、jimubi)
|
|
||||||
│ ├─AI流程设计器源码
|
|
||||||
│ ├─Online全模块功能和源码
|
|
||||||
│ ├─AI写文章(CMS)
|
|
||||||
│ ├─AI表单字段建议(表单设计器)
|
|
||||||
│ ├─OA办公协同组件
|
|
||||||
│ ├─在线聊天功能
|
|
||||||
│ ├─设计表单移动适配
|
|
||||||
│ ├─设计表单支持外部填报
|
|
||||||
│ ├─设计表单AI字段建议
|
|
||||||
│ ├─设计表单视图功能(支持多种类型含日历、表格、看板、甘特图)
|
|
||||||
│ └─。。。
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### 流程设计
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### 表单设计器
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
|
[中文](./README.md) | English
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -7,12 +7,12 @@
|
|||||||
JEECG BOOT AI Low Code Platform
|
JEECG BOOT AI Low Code Platform
|
||||||
===============
|
===============
|
||||||
|
|
||||||
Current version: 3.8.3 (Release date: 2025-10-09)
|
Current version: 3.9.1 (Release date: 2026-01-22)
|
||||||
|
|
||||||
|
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||||
[](http://www.jeecg.com)
|
[](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)
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||||
|
|
||||||
40
README.md
40
README.md
@ -1,14 +1,15 @@
|
|||||||
|
中文 | [English](./README.en-US.md)
|
||||||
|
|
||||||
JeecgBoot AI低代码平台
|
JeecgBoot AI低代码平台
|
||||||
===============
|
===============
|
||||||
|
|
||||||
当前最新版本: 3.8.3(发布日期:2025-10-09)
|
当前最新版本: 3.9.1(发布日期:2026-01-22)
|
||||||
|
|
||||||
|
|
||||||
[](https://github.com/jeecgboot/JeecgBoot/blob/master/LICENSE)
|
[](https://github.com/jeecgboot/JeecgBoot/blob/master/LICENSE)
|
||||||
[](https://jeecg.com)
|
[](https://jeecg.com)
|
||||||
[](https://jeecg.blog.csdn.net)
|
[](https://jeecg.blog.csdn.net)
|
||||||
[](https://github.com/jeecgboot/JeecgBoot)
|
[](https://github.com/jeecgboot/JeecgBoot)
|
||||||
[](https://github.com/jeecgboot/JeecgBoot)
|
[](https://github.com/jeecgboot/JeecgBoot)
|
||||||
[](https://github.com/jeecgboot/JeecgBoot)
|
[](https://github.com/jeecgboot/JeecgBoot)
|
||||||
|
|
||||||
@ -19,14 +20,17 @@ JeecgBoot AI低代码平台
|
|||||||
|
|
||||||
<h3 align="center">企业级AI低代码平台</h3>
|
<h3 align="center">企业级AI低代码平台</h3>
|
||||||
|
|
||||||
JeecgBoot 是一款基于BPM流程和代码生成的AI低代码平台,助力企业快速实现低代码开发和构建AI应用。
|
JeecgBoot 是一款融合代码生成与AI应用的低代码开发平台,助力企业快速实现低代码开发和构建AI应用。平台支持MCP和插件扩展,提供聊天式业务操作(如“一句话创建用户”),大幅提升开发效率与用户便捷性。
|
||||||
|
|
||||||
采用前后端分离架构(Ant Design&Vue3,SpringBoot3,SpringCloud Alibaba,Mybatis-plus),强大代码生成器实现前后端一键生成,无需手写代码。
|
采用前后端分离架构(Ant Design&Vue3,SpringBoot3,SpringCloud Alibaba,Mybatis-plus),强大代码生成器实现前后端一键生成,无需手写代码。
|
||||||
平台引领AI低代码开发模式:AI生成→在线编码→代码生成→手工合并,解决Java项目80%重复工作,提升效率,节省成本,兼顾灵活性。
|
平台引领AI低代码开发模式:AI生成→在线编码→代码生成→手工合并,解决Java项目80%重复工作,提升效率,节省成本,兼顾灵活性。
|
||||||
具备强大且颗粒化的权限控制,支持按钮权限和数据权限设置,满足大型业务系统需求。功能涵盖在线表单、表单设计、流程设计、门户设计、报表与大屏设计、OA办公、AI应用、AI知识库、大模型管理、AI流程编排、AI聊天,支持ChatGPT、DeepSeek、Ollama等多种AI大模型。
|
具备强大且颗粒化的权限控制,支持按钮权限和数据权限设置,满足大型业务系统需求。功能涵盖在线表单、表单设计、流程设计、门户设计、报表与大屏设计、OA办公、AI应用、AI知识库、大模型管理、AI流程编排、AI聊天,支持ChatGPT、DeepSeek、Ollama等多种AI大模型。
|
||||||
|
|
||||||
`AI赋能报表:` 积木报表是一款自主研发的强大开源企业级Web报表与大屏工具。它通过零编码的拖拽式操作,赋能用户如同搭积木般轻松构建各类复杂报表和数据大屏,全面满足企业数据可视化与分析需求,助力企业级数据产品的高效打造与应用。
|
`傻瓜式报表:` JimuReport是一款自主研发的强大开源企业级Web报表工具。它通过零编码的拖拽式操作,赋能用户如同搭积木般轻松构建各类复杂报表,全面满足企业数据可视化与分析需求,助力企业级数据产品的高效打造与应用。
|
||||||
|
|
||||||
`AI赋能低代码:` 提供完善成熟的AI应用平台,涵盖AI应用管理、AI模型管理、智能对话助手、知识库问答、流程编排与设计器、AI建表等多项功能。平台兼容多种主流大模型,包括ChatGPT、DeepSeek、Ollama、智普、千问等,助力企业高效构建智能化应用,推动低代码开发与AI深度融合。
|
`傻瓜式大屏:` JimuBI一款自主研发的强大的大屏和仪表盘设计工具。专注数字孪生与数据可视化,支持交互式大屏、仪表盘、门户和移动端,实现“一次开发,多端适配”。 大屏设计类Word风格,支持多屏切换,自由拖拽,轻松打造炫酷动态界面。
|
||||||
|
|
||||||
|
`成熟AI应用功能:` 提供一套完善AI应用平台: 涵盖AI应用管理、AI模型管理、智能对话助手、知识库问答、流程编排与设计器、AI建表、MCP插件配置等功能。平台兼容主流大模型,包括ChatGPT、DeepSeek、Ollama、智普、千问等,助力企业高效构建智能化应用,推动低代码开发与AI深度融合。
|
||||||
|
|
||||||
`JEECG宗旨是:` JEECG旨在通过OnlineCoding平台实现简单功能的零代码快速搭建,同时针对复杂功能采用代码生成器生成代码并手工合并,打造智能且灵活的低代码开发模式,有效解决了当前低代码产品普遍缺乏灵活性的问题,提升开发效率的同时兼顾系统的扩展性和定制化能力。
|
`JEECG宗旨是:` JEECG旨在通过OnlineCoding平台实现简单功能的零代码快速搭建,同时针对复杂功能采用代码生成器生成代码并手工合并,打造智能且灵活的低代码开发模式,有效解决了当前低代码产品普遍缺乏灵活性的问题,提升开发效率的同时兼顾系统的扩展性和定制化能力。
|
||||||
|
|
||||||
@ -50,15 +54,16 @@ JeecgBoot低代码平台兼容所有J2EE项目开发,支持信创国产化,
|
|||||||
版本说明
|
版本说明
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
|下载 | SpringBoot3.5 + Shiro |SpringBoot3.5+ SpringAuthorizationServer | SpringBoot3.5 + Sa-Token | SpringBoot2.7(JDK17/JDK8) |
|
|下载 | SpringBoot3.5 + Shiro |SpringBoot3.5+ SpringAuthorizationServer | SpringBoot3.5 + Sa-Token | SpringBoot2.7(JDK17/JDK8) |
|
||||||
|------|----------------|----------------------------|-------------------|--------------------------------------------|
|
|------|---------------------------------------------------------|----------------------------|-------------------|--------------------------------------------|
|
||||||
| Github | [`springboot3`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3) | [`springboot3_sas`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3_sas) 分支 | [`springboot3-satoken`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3-satoken) 分支|[`master`](https://github.com/jeecgboot/JeecgBoot) 分支|
|
| Github | [`main`](https://github.com/jeecgboot/JeecgBoot) | [`springboot3_sas`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3_sas) 分支 | [`springboot3-satoken`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3-satoken) 分支|[`springboot2`](https://github.com/jeecgboot/JeecgBoot/tree/springboot2) 分支|
|
||||||
| Gitee | [`springboot3`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3/) | [`springboot3_sas`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3_sas) 分支| [`springboot3-satoken`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3-satoken) 分支|[`master`](https://gitee.com/jeecg/JeecgBoot) 分支 |
|
| Gitee | [`main`](https://github.com/jeecgboot/JeecgBoot) | [`springboot3_sas`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3_sas) 分支| [`springboot3-satoken`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3-satoken) 分支|[`springboot2`](https://github.com/jeecgboot/JeecgBoot/tree/springboot2) 分支 |
|
||||||
|
|
||||||
|
|
||||||
- `jeecg-boot` 是后端JAVA源码项目Springboot3+SpringCloudAlibaba(支持单体和微服务切换).
|
- `jeecg-boot` 是后端JAVA源码项目Springboot3+Shiro+Mybatis+SpringCloudAlibaba(支持单体和微服务切换).
|
||||||
- `jeecgboot-vue3` 是前端VUE3源码项目(vue3+vite6+ts最新技术栈).
|
- `jeecgboot-vue3` 是前端VUE3源码项目(vue3+vite6+ts最新技术栈).
|
||||||
- `JeecgUniapp` 是[配套APP框架](https://github.com/jeecgboot/JeecgUniapp) 适配多个终端,支持APP、小程序、H5、鸿蒙、鸿蒙Next.
|
- `JeecgUniapp` 是[配套APP框架](https://github.com/jeecgboot/JeecgUniapp) 适配多个终端,支持APP、小程序、H5、鸿蒙、鸿蒙Next.
|
||||||
|
- `jeecg-boot-starter` 是[jeecg-boot对应的底层封装starter](https://github.com/jeecgboot/jeecg-boot-starter) :微服务启动、xxljob、分布式锁starter、rabbitmq、分布式事务、分库分表shardingsphere等.
|
||||||
- 参考 [文档](https://help.jeecg.com/ui/2dev/mini) 可以删除不需要的demo,制作一个精简版本
|
- 参考 [文档](https://help.jeecg.com/ui/2dev/mini) 可以删除不需要的demo,制作一个精简版本
|
||||||
|
|
||||||
|
|
||||||
@ -83,6 +88,7 @@ JeecgBoot低代码平台兼容所有J2EE项目开发,支持信创国产化,
|
|||||||
- 官方网站: [http://www.jeecg.com](http://www.jeecg.com)
|
- 官方网站: [http://www.jeecg.com](http://www.jeecg.com)
|
||||||
- 在线演示: [平台演示](https://boot3.jeecg.com) | [APP演示](https://jeecg.com/appIndex)
|
- 在线演示: [平台演示](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)
|
- 入门指南: [快速入门](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低代码与Cursor+GitHub Copilot实现AI高效编程实战](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)
|
- 技术支持: [反馈问题](https://github.com/jeecgboot/JeecgBoot/issues/new?template=bug_report.md) | [低代码体验一分钟](https://jeecg.blog.csdn.net/article/details/106079007)
|
||||||
- QQ交流群 : 964611995、⑩716488839(满)、⑨808791225(满)、其他(满)
|
- QQ交流群 : 964611995、⑩716488839(满)、⑨808791225(满)、其他(满)
|
||||||
|
|
||||||
@ -227,20 +233,6 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块,是一套类
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
开源版与企业版区别?
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
- JeecgBoot开源版采用 [Apache-2.0 license](LICENSE) 协议附加补充条款:允许商用使用,不会造成侵权行为,允许基于本平台软件开展业务系统开发(但在任何情况下,您不得使用本软件开发可能被认为与本软件竞争的软件).
|
|
||||||
- 商业版与开源版主要区别在于商业版提供了技术支持 和 更多的企业级功能(例如:Online图表、流程监控、流程设计、流程审批、表单设计器、表单视图、积木报表企业版、OA办公、商业APP、零代码应用、Online模块源码等功能). [更多商业功能介绍,点击查看](README-Enterprise.md)
|
|
||||||
- JeecgBoot未来发展方向是:零代码平台的建设,也就是团队的另外一款产品 [敲敲云零代码](https://www.qiaoqiaoyun.com) ,无需编码即可通过拖拽快速搭建企业级应用,与JeecgBoot低代码平台形成互补,满足从简单业务到复杂系统的全场景开发需求,目前已经开源,[欢迎下载](https://qiaoqiaoyun.com/downloadCode)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Jeecg Boot 产品功能蓝图
|
### Jeecg Boot 产品功能蓝图
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
|
|
||||||
JeecgBoot 低代码开发平台
|
JeecgBoot 低代码开发平台
|
||||||
===============
|
===============
|
||||||
|
|
||||||
当前最新版本: 3.8.3(发布日期:2025-10-09)
|
当前最新版本: 3.9.1(发布日期: 2026-01-22)
|
||||||
|
|
||||||
|
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||||
[](http://jeecg.com/aboutusIndex)
|
[](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)
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -63,6 +63,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./jeecg-module-system/jeecg-system-start
|
context: ./jeecg-module-system/jeecg-system-start
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
|
mac_address: 02:42:ac:11:00:02
|
||||||
depends_on:
|
depends_on:
|
||||||
- jeecg-boot-mysql
|
- jeecg-boot-mysql
|
||||||
- jeecg-boot-redis
|
- jeecg-boot-redis
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.jeecgframework.boot3</groupId>
|
<groupId>org.jeecgframework.boot3</groupId>
|
||||||
<artifactId>jeecg-boot-parent</artifactId>
|
<artifactId>jeecg-boot-parent</artifactId>
|
||||||
<version>3.8.3</version>
|
<version>3.9.1</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>jeecg-boot-base-core</artifactId>
|
<artifactId>jeecg-boot-base-core</artifactId>
|
||||||
@ -44,6 +44,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.boot3</groupId>
|
<groupId>org.jeecgframework.boot3</groupId>
|
||||||
<artifactId>jeecg-boot-common</artifactId>
|
<artifactId>jeecg-boot-common</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-logging</groupId>
|
||||||
|
<artifactId>commons-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!--集成springmvc框架并实现自动配置 -->
|
<!--集成springmvc框架并实现自动配置 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -173,7 +179,6 @@
|
|||||||
<artifactId>DmDialect-for-hibernate5.0</artifactId>
|
<artifactId>DmDialect-for-hibernate5.0</artifactId>
|
||||||
<version>${dm8.version}</version>
|
<version>${dm8.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Quartz定时任务 -->
|
<!-- Quartz定时任务 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@ -223,6 +228,12 @@
|
|||||||
<artifactId>shiro-core</artifactId>
|
<artifactId>shiro-core</artifactId>
|
||||||
<classifier>jakarta</classifier>
|
<classifier>jakarta</classifier>
|
||||||
<version>${shiro.version}</version>
|
<version>${shiro.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-beanutils</groupId>
|
||||||
|
<artifactId>commons-beanutils</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.shiro</groupId>
|
<groupId>org.apache.shiro</groupId>
|
||||||
@ -253,13 +264,6 @@
|
|||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- <dependency>
|
|
||||||
<groupId>com.github.xiaoymin</groupId>
|
|
||||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
|
||||||
<version>${knife4j-spring-boot-starter.version}</version>
|
|
||||||
</dependency>-->
|
|
||||||
<!-- knife4j 升级springboot3.4.5报错 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.xiaoymin</groupId>
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
<artifactId>knife4j-openapi3-ui</artifactId>
|
<artifactId>knife4j-openapi3-ui</artifactId>
|
||||||
@ -291,8 +295,8 @@
|
|||||||
|
|
||||||
<!-- AutoPoi Excel工具类-->
|
<!-- AutoPoi Excel工具类-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.boot3</groupId>
|
<groupId>org.jeecgframework</groupId>
|
||||||
<artifactId>autopoi-web</artifactId>
|
<artifactId>autopoi-spring-boot-3-starter</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>xerces</groupId>
|
<groupId>xerces</groupId>
|
||||||
@ -301,7 +305,7 @@
|
|||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- mini文件存储服务 -->
|
<!-- minio文件存储服务 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.minio</groupId>
|
<groupId>io.minio</groupId>
|
||||||
<artifactId>minio</artifactId>
|
<artifactId>minio</artifactId>
|
||||||
@ -310,6 +314,14 @@
|
|||||||
<artifactId>checker-qual</artifactId>
|
<artifactId>checker-qual</artifactId>
|
||||||
<groupId>org.checkerframework</groupId>
|
<groupId>org.checkerframework</groupId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_annotations</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-compress</artifactId>
|
||||||
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@ -367,5 +379,21 @@
|
|||||||
<groupId>org.jeecgframework.boot3</groupId>
|
<groupId>org.jeecgframework.boot3</groupId>
|
||||||
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 腾讯云 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.tencentcloudapi</groupId>
|
||||||
|
<artifactId>tencentcloud-sdk-java-sms</artifactId>
|
||||||
|
<version>${tencentcloud-sdk-java-sms.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>javax.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.squareup.okio</groupId>
|
||||||
|
<artifactId>okio</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@ -132,7 +132,6 @@ public interface CommonAPI {
|
|||||||
*/
|
*/
|
||||||
Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys);
|
Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys);
|
||||||
|
|
||||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
/**
|
/**
|
||||||
* 15 字典表的 翻译,可批量
|
* 15 字典表的 翻译,可批量
|
||||||
* @param table
|
* @param table
|
||||||
@ -143,7 +142,6 @@ public interface CommonAPI {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
List<DictModel> translateDictFromTableByKeys(String table, String text, String code, String keys, String dataSource);
|
List<DictModel> translateDictFromTableByKeys(String table, String text, String code, String keys, String dataSource);
|
||||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 16 运行AIRag流程
|
* 16 运行AIRag流程
|
||||||
|
|||||||
@ -33,4 +33,10 @@ public class AiragFlowDTO implements Serializable {
|
|||||||
* 输入参数
|
* 输入参数
|
||||||
*/
|
*/
|
||||||
private Map<String, Object> inputParams;
|
private Map<String, Object> inputParams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否流式返回
|
||||||
|
*/
|
||||||
|
private boolean isStream;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
package org.jeecg.common.api.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程审批意见DTO
|
||||||
|
* @author scott
|
||||||
|
* @date 2025-01-29
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ApprovalCommentDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务ID
|
||||||
|
*/
|
||||||
|
private String taskId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务名称
|
||||||
|
*/
|
||||||
|
private String taskName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人ID
|
||||||
|
*/
|
||||||
|
private String approverId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人姓名
|
||||||
|
*/
|
||||||
|
private String approverName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批意见
|
||||||
|
*/
|
||||||
|
private String approvalComment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批时间
|
||||||
|
*/
|
||||||
|
private Date approvalTime;
|
||||||
|
}
|
||||||
|
|
||||||
@ -30,12 +30,10 @@ public class OnlineAuthDTO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String onlineFormUrl;
|
private String onlineFormUrl;
|
||||||
|
|
||||||
//update-begin---author:chenrui ---date:20240123 for:[QQYUN-7992]【online】工单申请下的online表单,未配置online表单开发菜单,操作报错无权限------------
|
|
||||||
/**
|
/**
|
||||||
* online工单的地址
|
* online工单的地址
|
||||||
*/
|
*/
|
||||||
private String onlineWorkOrderUrl;
|
private String onlineWorkOrderUrl;
|
||||||
//update-end---author:chenrui ---date:20240123 for:[QQYUN-7992]【online】工单申请下的online表单,未配置online表单开发菜单,操作报错无权限------------
|
|
||||||
|
|
||||||
public OnlineAuthDTO(){
|
public OnlineAuthDTO(){
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,55 @@
|
|||||||
|
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.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动端消息推送
|
||||||
|
* @author liusq
|
||||||
|
* @date 2025/11/12 14:11
|
||||||
|
*/
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
public class PushMessageDTO implements Serializable {
|
||||||
|
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 7431775881170684867L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送形式:all:全推送 single:单用户推送
|
||||||
|
*/
|
||||||
|
private String pushType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名usernameList
|
||||||
|
*/
|
||||||
|
List<String> usernames;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名idList
|
||||||
|
*/
|
||||||
|
List<String> userIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息附加参数
|
||||||
|
*/
|
||||||
|
Map<String,Object> payload;
|
||||||
|
}
|
||||||
@ -121,9 +121,8 @@ public class AutoLogAspect {
|
|||||||
if (operateType > 0) {
|
if (operateType > 0) {
|
||||||
return operateType;
|
return operateType;
|
||||||
}
|
}
|
||||||
//update-begin---author:wangshuai ---date:20220331 for:阿里云代码扫描规范(不允许任何魔法值出现在代码中)------------
|
// 代码逻辑说明: 阿里云代码扫描规范(不允许任何魔法值出现在代码中)------------
|
||||||
return OperateTypeEnum.getTypeByMethodName(methodName);
|
return OperateTypeEnum.getTypeByMethodName(methodName);
|
||||||
//update-end---author:wangshuai ---date:20220331 for:阿里云代码扫描规范(不允许任何魔法值出现在代码中)------------
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -143,14 +142,15 @@ public class AutoLogAspect {
|
|||||||
// https://my.oschina.net/mengzhang6/blog/2395893
|
// https://my.oschina.net/mengzhang6/blog/2395893
|
||||||
Object[] arguments = new Object[paramsArray.length];
|
Object[] arguments = new Object[paramsArray.length];
|
||||||
for (int i = 0; i < paramsArray.length; i++) {
|
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)
|
//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
|
//ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
|
||||||
|
//MultipartFile和MultipartFile[]不能序列化,从入参里排除
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
arguments[i] = paramsArray[i];
|
arguments[i] = paramsArray[i];
|
||||||
}
|
}
|
||||||
//update-begin-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
|
// 代码逻辑说明: 日志数据太长的直接过滤掉
|
||||||
PropertyFilter profilter = new PropertyFilter() {
|
PropertyFilter profilter = new PropertyFilter() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Object o, String name, Object value) {
|
public boolean apply(Object o, String name, Object value) {
|
||||||
@ -165,14 +165,13 @@ public class AutoLogAspect {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
params = JSONObject.toJSONString(arguments, profilter);
|
params = JSONObject.toJSONString(arguments, profilter);
|
||||||
//update-end-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
|
|
||||||
} else {
|
} else {
|
||||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||||
Method method = signature.getMethod();
|
Method method = signature.getMethod();
|
||||||
// 请求的方法参数值
|
// 请求的方法参数值
|
||||||
Object[] args = joinPoint.getArgs();
|
Object[] args = joinPoint.getArgs();
|
||||||
// 请求的方法参数名称
|
// 请求的方法参数名称
|
||||||
StandardReflectionParameterNameDiscoverer u=new StandardReflectionParameterNameDiscoverer();
|
StandardReflectionParameterNameDiscoverer u= new StandardReflectionParameterNameDiscoverer();
|
||||||
String[] paramNames = u.getParameterNames(method);
|
String[] paramNames = u.getParameterNames(method);
|
||||||
if (args != null && paramNames != null) {
|
if (args != null && paramNames != null) {
|
||||||
for (int i = 0; i < args.length; i++) {
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
|||||||
@ -105,29 +105,24 @@ public class DictAspect {
|
|||||||
Map<String, List<String>> dataListMap = new HashMap<>(5);
|
Map<String, List<String>> dataListMap = new HashMap<>(5);
|
||||||
//取出结果集
|
//取出结果集
|
||||||
List<Object> records=((IPage) ((Result) result).getResult()).getRecords();
|
List<Object> records=((IPage) ((Result) result).getResult()).getRecords();
|
||||||
//update-begin--Author:zyf -- Date:20220606 ----for:【VUEN-1230】 判断是否含有字典注解,没有注解返回-----
|
// 代码逻辑说明: 【VUEN-1230】 判断是否含有字典注解,没有注解返回-----
|
||||||
Boolean hasDict= checkHasDict(records);
|
Boolean hasDict= checkHasDict(records);
|
||||||
if(!hasDict){
|
if(!hasDict){
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug(" __ 进入字典翻译切面 DictAspect —— " );
|
log.debug(" __ 进入字典翻译切面 DictAspect —— " );
|
||||||
//update-end--Author:zyf -- Date:20220606 ----for:【VUEN-1230】 判断是否含有字典注解,没有注解返回-----
|
|
||||||
for (Object record : records) {
|
for (Object record : records) {
|
||||||
String json="{}";
|
String json="{}";
|
||||||
try {
|
try {
|
||||||
//update-begin--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
|
|
||||||
//解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
|
//解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
|
||||||
json = objectMapper.writeValueAsString(record);
|
json = objectMapper.writeValueAsString(record);
|
||||||
//update-end--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
|
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
log.error("json解析失败"+e.getMessage(),e);
|
log.error("json解析失败"+e.getMessage(),e);
|
||||||
}
|
}
|
||||||
//update-begin--Author:scott -- Date:20211223 ----for:【issues/3303】restcontroller返回json数据后key顺序错乱 -----
|
// 代码逻辑说明: 【issues/3303】restcontroller返回json数据后key顺序错乱 -----
|
||||||
JSONObject item = JSONObject.parseObject(json, Feature.OrderedField);
|
JSONObject item = JSONObject.parseObject(json, Feature.OrderedField);
|
||||||
//update-end--Author:scott -- Date:20211223 ----for:【issues/3303】restcontroller返回json数据后key顺序错乱 -----
|
|
||||||
|
|
||||||
//update-begin--Author:scott -- Date:20190603 ----for:解决继承实体字段无法翻译问题------
|
|
||||||
//for (Field field : record.getClass().getDeclaredFields()) {
|
//for (Field field : record.getClass().getDeclaredFields()) {
|
||||||
// 遍历所有字段,把字典Code取出来,放到 map 里
|
// 遍历所有字段,把字典Code取出来,放到 map 里
|
||||||
for (Field field : oConvertUtils.getAllFields(record)) {
|
for (Field field : oConvertUtils.getAllFields(record)) {
|
||||||
@ -135,7 +130,6 @@ public class DictAspect {
|
|||||||
if (oConvertUtils.isEmpty(value)) {
|
if (oConvertUtils.isEmpty(value)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
//update-end--Author:scott -- Date:20190603 ----for:解决继承实体字段无法翻译问题------
|
|
||||||
if (field.getAnnotation(Dict.class) != null) {
|
if (field.getAnnotation(Dict.class) != null) {
|
||||||
if (!dictFieldList.contains(field)) {
|
if (!dictFieldList.contains(field)) {
|
||||||
dictFieldList.add(field);
|
dictFieldList.add(field);
|
||||||
@ -143,26 +137,22 @@ public class DictAspect {
|
|||||||
String code = field.getAnnotation(Dict.class).dicCode();
|
String code = field.getAnnotation(Dict.class).dicCode();
|
||||||
String text = field.getAnnotation(Dict.class).dicText();
|
String text = field.getAnnotation(Dict.class).dicText();
|
||||||
String table = field.getAnnotation(Dict.class).dictTable();
|
String table = field.getAnnotation(Dict.class).dictTable();
|
||||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
// 代码逻辑说明: [issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
String dataSource = field.getAnnotation(Dict.class).ds();
|
String dataSource = field.getAnnotation(Dict.class).ds();
|
||||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
List<String> dataList;
|
List<String> dataList;
|
||||||
String dictCode = code;
|
String dictCode = code;
|
||||||
if (!StringUtils.isEmpty(table)) {
|
if (!StringUtils.isEmpty(table)) {
|
||||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
// 代码逻辑说明: [issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
dictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
|
dictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
|
||||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
}
|
}
|
||||||
dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
||||||
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
|
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
|
||||||
}
|
}
|
||||||
//date类型默认转换string格式化日期
|
//date类型默认转换string格式化日期
|
||||||
//update-begin--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
|
|
||||||
//if (JAVA_UTIL_DATE.equals(field.getType().getName())&&field.getAnnotation(JsonFormat.class)==null&&item.get(field.getName())!=null){
|
//if (JAVA_UTIL_DATE.equals(field.getType().getName())&&field.getAnnotation(JsonFormat.class)==null&&item.get(field.getName())!=null){
|
||||||
//SimpleDateFormat aDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
//SimpleDateFormat aDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
// item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
|
// item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
|
||||||
//}
|
//}
|
||||||
//update-end--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
|
|
||||||
}
|
}
|
||||||
items.add(item);
|
items.add(item);
|
||||||
}
|
}
|
||||||
@ -176,15 +166,12 @@ public class DictAspect {
|
|||||||
String code = field.getAnnotation(Dict.class).dicCode();
|
String code = field.getAnnotation(Dict.class).dicCode();
|
||||||
String text = field.getAnnotation(Dict.class).dicText();
|
String text = field.getAnnotation(Dict.class).dicText();
|
||||||
String table = field.getAnnotation(Dict.class).dictTable();
|
String table = field.getAnnotation(Dict.class).dictTable();
|
||||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
// 自定义的字典表数据源
|
// 自定义的字典表数据源
|
||||||
String dataSource = field.getAnnotation(Dict.class).ds();
|
String dataSource = field.getAnnotation(Dict.class).ds();
|
||||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
String fieldDictCode = code;
|
String fieldDictCode = code;
|
||||||
if (!StringUtils.isEmpty(table)) {
|
if (!StringUtils.isEmpty(table)) {
|
||||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
// 代码逻辑说明: [issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
fieldDictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
|
fieldDictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
|
||||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String value = record.getString(field.getName());
|
String value = record.getString(field.getName());
|
||||||
@ -286,25 +273,20 @@ public class DictAspect {
|
|||||||
String[] arr = dictCode.split(",");
|
String[] arr = dictCode.split(",");
|
||||||
String table = arr[0], text = arr[1], code = arr[2];
|
String table = arr[0], text = arr[1], code = arr[2];
|
||||||
String values = String.join(",", needTranslDataTable);
|
String values = String.join(",", needTranslDataTable);
|
||||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
// 自定义的数据源
|
// 自定义的数据源
|
||||||
String dataSource = null;
|
String dataSource = null;
|
||||||
if (arr.length > 3) {
|
if (arr.length > 3) {
|
||||||
dataSource = arr[3];
|
dataSource = arr[3];
|
||||||
}
|
}
|
||||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
log.debug("translateDictFromTableByKeys.dictCode:" + dictCode);
|
log.debug("translateDictFromTableByKeys.dictCode:" + dictCode);
|
||||||
log.debug("translateDictFromTableByKeys.values:" + values);
|
log.debug("translateDictFromTableByKeys.values:" + values);
|
||||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
|
|
||||||
//update-begin---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
|
// 代码逻辑说明: 微服务下为空报错没有参数需要传递空字符串---
|
||||||
if(null == dataSource){
|
if(null == dataSource){
|
||||||
dataSource = "";
|
dataSource = "";
|
||||||
}
|
}
|
||||||
//update-end---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
|
|
||||||
|
|
||||||
List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values, dataSource);
|
List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values, dataSource);
|
||||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
log.debug("translateDictFromTableByKeys.result:" + texts);
|
log.debug("translateDictFromTableByKeys.result:" + texts);
|
||||||
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
||||||
list.addAll(texts);
|
list.addAll(texts);
|
||||||
@ -313,10 +295,8 @@ public class DictAspect {
|
|||||||
for (DictModel dict : texts) {
|
for (DictModel dict : texts) {
|
||||||
String redisKey = String.format("sys:cache:dictTable::SimpleKey [%s,%s]", dictCode, dict.getValue());
|
String redisKey = String.format("sys:cache:dictTable::SimpleKey [%s,%s]", dictCode, dict.getValue());
|
||||||
try {
|
try {
|
||||||
// update-begin-author:taoyan date:20211012 for: 字典表翻译注解缓存未更新 issues/3061
|
|
||||||
// 保留5分钟
|
// 保留5分钟
|
||||||
redisTemplate.opsForValue().set(redisKey, dict.getText(), 300, TimeUnit.SECONDS);
|
redisTemplate.opsForValue().set(redisKey, dict.getText(), 300, TimeUnit.SECONDS);
|
||||||
// update-end-author:taoyan date:20211012 for: 字典表翻译注解缓存未更新 issues/3061
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn(e.getMessage(), e);
|
log.warn(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
@ -400,7 +380,7 @@ public class DictAspect {
|
|||||||
if (k.trim().length() == 0) {
|
if (k.trim().length() == 0) {
|
||||||
continue; //跳过循环
|
continue; //跳过循环
|
||||||
}
|
}
|
||||||
//update-begin--Author:scott -- Date:20210531 ----for: !56 优化微服务应用下存在表字段需要字典翻译时加载缓慢问题-----
|
// 代码逻辑说明: !56 优化微服务应用下存在表字段需要字典翻译时加载缓慢问题-----
|
||||||
if (!StringUtils.isEmpty(table)){
|
if (!StringUtils.isEmpty(table)){
|
||||||
log.debug("--DictAspect------dicTable="+ table+" ,dicText= "+text+" ,dicCode="+code);
|
log.debug("--DictAspect------dicTable="+ table+" ,dicText= "+text+" ,dicCode="+code);
|
||||||
String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s,%s,%s]",table,text,code,k.trim());
|
String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s,%s,%s]",table,text,code,k.trim());
|
||||||
@ -425,7 +405,6 @@ public class DictAspect {
|
|||||||
tmpValue = commonApi.translateDict(code, k.trim());
|
tmpValue = commonApi.translateDict(code, k.trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-end--Author:scott -- Date:20210531 ----for: !56 优化微服务应用下存在表字段需要字典翻译时加载缓慢问题-----
|
|
||||||
|
|
||||||
if (tmpValue != null) {
|
if (tmpValue != null) {
|
||||||
if (!"".equals(textValue.toString())) {
|
if (!"".equals(textValue.toString())) {
|
||||||
|
|||||||
@ -57,7 +57,6 @@ public class PermissionDataAspect {
|
|||||||
String requestMethod = request.getMethod();
|
String requestMethod = request.getMethod();
|
||||||
String requestPath = request.getRequestURI().substring(request.getContextPath().length());
|
String requestPath = request.getRequestURI().substring(request.getContextPath().length());
|
||||||
requestPath = filterUrl(requestPath);
|
requestPath = filterUrl(requestPath);
|
||||||
//update-begin-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
|
|
||||||
//先判断是否online报表请求
|
//先判断是否online报表请求
|
||||||
if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0 || requestPath.indexOf(UrlMatchEnum.CGREPORT_ONLY_DATA.getMatchUrl())>=0){
|
if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0 || requestPath.indexOf(UrlMatchEnum.CGREPORT_ONLY_DATA.getMatchUrl())>=0){
|
||||||
// 获取地址栏参数
|
// 获取地址栏参数
|
||||||
@ -66,7 +65,6 @@ public class PermissionDataAspect {
|
|||||||
requestPath+="?"+urlParamString;
|
requestPath+="?"+urlParamString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
|
|
||||||
log.debug("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod);
|
log.debug("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod);
|
||||||
String username = JwtUtil.getUserNameByToken(request);
|
String username = JwtUtil.getUserNameByToken(request);
|
||||||
//查询数据权限信息
|
//查询数据权限信息
|
||||||
|
|||||||
@ -41,7 +41,6 @@ public @interface Dict {
|
|||||||
String dictTable() default "";
|
String dictTable() default "";
|
||||||
|
|
||||||
|
|
||||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
/**
|
/**
|
||||||
* 方法描述: 数据字典表所在数据源名称
|
* 方法描述: 数据字典表所在数据源名称
|
||||||
* 作 者: chenrui
|
* 作 者: chenrui
|
||||||
@ -50,5 +49,4 @@ public @interface Dict {
|
|||||||
* @return 返回类型: String
|
* @return 返回类型: String
|
||||||
*/
|
*/
|
||||||
String ds() default "";
|
String ds() default "";
|
||||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,6 +91,23 @@ public interface CommonConstant {
|
|||||||
public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:";
|
public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:";
|
||||||
/** 登录用户Token令牌缓存KEY前缀 */
|
/** 登录用户Token令牌缓存KEY前缀 */
|
||||||
String PREFIX_USER_TOKEN = "prefix_user_token:";
|
String PREFIX_USER_TOKEN = "prefix_user_token:";
|
||||||
|
/** 登录用户Token令牌作废提示信息,比如 “不允许同一账号多地同时登录,会往这个变量存提示信息” */
|
||||||
|
String PREFIX_USER_TOKEN_ERROR_MSG = "prefix_user_token:error:msg_";
|
||||||
|
|
||||||
|
/**============================== 【是否允许同一账号多地同时登录】登录客户端类型常量 ==============================*/
|
||||||
|
/** 客户端类型:PC端 */
|
||||||
|
String CLIENT_TYPE_PC = "PC";
|
||||||
|
/** 客户端类型:APP端 */
|
||||||
|
String CLIENT_TYPE_APP = "APP";
|
||||||
|
/** 客户端类型:手机号登录 */
|
||||||
|
String CLIENT_TYPE_PHONE = "PHONE";
|
||||||
|
String PREFIX_USER_TOKEN_PC = "prefix_user_token:single_login:pc:";
|
||||||
|
/** 单点登录:用户在APP端的Token缓存KEY前缀 (username -> token) */
|
||||||
|
String PREFIX_USER_TOKEN_APP = "prefix_user_token:single_login:app:";
|
||||||
|
/** 单点登录:用户在手机号登录的Token缓存KEY前缀 (username -> token) */
|
||||||
|
String PREFIX_USER_TOKEN_PHONE = "prefix_user_token:single_login:phone:";
|
||||||
|
/**============================== 【是否允许同一账号多地同时登录】登录客户端类型常量 ==============================*/
|
||||||
|
|
||||||
// /** Token缓存时间:3600秒即一小时 */
|
// /** Token缓存时间:3600秒即一小时 */
|
||||||
// int TOKEN_EXPIRE_TIME = 3600;
|
// int TOKEN_EXPIRE_TIME = 3600;
|
||||||
|
|
||||||
@ -308,6 +325,10 @@ public interface CommonConstant {
|
|||||||
*/
|
*/
|
||||||
String SYS_ROLE_ADMIN = "admin";
|
String SYS_ROLE_ADMIN = "admin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考勤补卡业务状态 (0:处理中)
|
||||||
|
*/
|
||||||
|
String SIGN_PATCH_BIZ_STATUS_0 = "0";
|
||||||
/**
|
/**
|
||||||
* 考勤补卡业务状态 (1:同意 2:不同意)
|
* 考勤补卡业务状态 (1:同意 2:不同意)
|
||||||
*/
|
*/
|
||||||
@ -591,7 +612,6 @@ public interface CommonConstant {
|
|||||||
String ORDER_TYPE_DESC = "DESC";
|
String ORDER_TYPE_DESC = "DESC";
|
||||||
|
|
||||||
|
|
||||||
//update-begin---author:scott ---date:2023-09-10 for:积木报表常量----
|
|
||||||
/**
|
/**
|
||||||
* 报表允许设计开发的角色
|
* 报表允许设计开发的角色
|
||||||
*/
|
*/
|
||||||
@ -606,9 +626,7 @@ public interface CommonConstant {
|
|||||||
* 数据隔离模式: 按照租户隔离
|
* 数据隔离模式: 按照租户隔离
|
||||||
*/
|
*/
|
||||||
public static final String SAAS_MODE_TENANT = "tenant";
|
public static final String SAAS_MODE_TENANT = "tenant";
|
||||||
//update-end---author:scott ---date::2023-09-10 for:积木报表常量----
|
|
||||||
|
|
||||||
//update-begin---author:wangshuai---date:2024-04-07---for:修改手机号常量---
|
|
||||||
/**
|
/**
|
||||||
* 修改手机号短信验证码redis-key的前缀
|
* 修改手机号短信验证码redis-key的前缀
|
||||||
*/
|
*/
|
||||||
@ -633,7 +651,6 @@ public interface CommonConstant {
|
|||||||
* 修改手机号
|
* 修改手机号
|
||||||
*/
|
*/
|
||||||
String UPDATE_PHONE = "updatePhone";
|
String UPDATE_PHONE = "updatePhone";
|
||||||
//update-end---author:wangshuai---date:2024-04-07---for:修改手机号常量---
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改手机号验证码请求次数超出
|
* 修改手机号验证码请求次数超出
|
||||||
@ -709,4 +726,19 @@ public interface CommonConstant {
|
|||||||
* 部门名称redisKey(全路径)
|
* 部门名称redisKey(全路径)
|
||||||
*/
|
*/
|
||||||
String DEPART_NAME_REDIS_KEY_PRE = "sys:cache:departPathName:";
|
String DEPART_NAME_REDIS_KEY_PRE = "sys:cache:departPathName:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认用户排序值
|
||||||
|
*/
|
||||||
|
Integer DEFAULT_USER_SORT = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送短信方式:腾讯
|
||||||
|
*/
|
||||||
|
String SMS_SEND_TYPE_TENCENT = "tencent";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送短信方式:阿里云
|
||||||
|
*/
|
||||||
|
String SMS_SEND_TYPE_ALI_YUN = "aliyun";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,20 +39,18 @@ public class ProvinceCityArea {
|
|||||||
this.initAreaList();
|
this.initAreaList();
|
||||||
if(areaList!=null && areaList.size()>0){
|
if(areaList!=null && areaList.size()>0){
|
||||||
for(int i=areaList.size()-1;i>=0;i--){
|
for(int i=areaList.size()-1;i>=0;i--){
|
||||||
//update-begin-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
|
// 代码逻辑说明: VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
|
||||||
String areaText = areaList.get(i).getText();
|
String areaText = areaList.get(i).getText();
|
||||||
String cityText = areaList.get(i).getAheadText();
|
String cityText = areaList.get(i).getAheadText();
|
||||||
if(text.indexOf(areaText)>=0 && (cityText!=null && text.indexOf(cityText)>=0)){
|
if(text.indexOf(areaText)>=0 && (cityText!=null && text.indexOf(cityText)>=0)){
|
||||||
return areaList.get(i).getId();
|
return areaList.get(i).getId();
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update-begin-author:sunjianlei date:20220121 for:【JTC-704】数据导入错误 省市区组件,文件中为北京市,导入后,导为了山西省
|
|
||||||
/**
|
/**
|
||||||
* 获取省市区code,精准匹配
|
* 获取省市区code,精准匹配
|
||||||
* @param texts 文本数组,省,市,区
|
* @param texts 文本数组,省,市,区
|
||||||
@ -117,7 +115,6 @@ public class ProvinceCityArea {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// update-end-author:sunjianlei date:20220121 for:【JTC-704】数据导入错误 省市区组件,文件中为北京市,导入后,导为了山西省
|
|
||||||
|
|
||||||
public void getAreaByCode(String code,List<String> ls){
|
public void getAreaByCode(String code,List<String> ls){
|
||||||
for(Area area: areaList){
|
for(Area area: areaList){
|
||||||
@ -154,9 +151,8 @@ public class ProvinceCityArea {
|
|||||||
for(String areaKey:areaJson.keySet()){
|
for(String areaKey:areaJson.keySet()){
|
||||||
//System.out.println("········"+areaKey);
|
//System.out.println("········"+areaKey);
|
||||||
Area area = new Area(areaKey,areaJson.getString(areaKey),cityKey);
|
Area area = new Area(areaKey,areaJson.getString(areaKey),cityKey);
|
||||||
//update-begin-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
|
// 代码逻辑说明: VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
|
||||||
area.setAheadText(cityJson.getString(cityKey));
|
area.setAheadText(cityJson.getString(cityKey));
|
||||||
//update-end-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
|
|
||||||
this.areaList.add(area);
|
this.areaList.add(area);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,4 +47,8 @@ public interface TenantConstant {
|
|||||||
*/
|
*/
|
||||||
String APP_ADMIN = "appAdmin";
|
String APP_ADMIN = "appAdmin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加SignatureCheck注解POST请求的URL
|
||||||
|
*/
|
||||||
|
String[] SIGNATURE_CHECK_POST_URL = { "/sys/tenant/joinTenantByHouseNumber", "/sys/tenant/invitationUser" };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,11 @@ public enum NoticeTypeEnum {
|
|||||||
/**
|
/**
|
||||||
* 督办
|
* 督办
|
||||||
*/
|
*/
|
||||||
NOTICE_TYPE_SUPERVISE("督办管理", "supe");
|
NOTICE_TYPE_SUPERVISE("督办管理", "supe"),
|
||||||
|
/**
|
||||||
|
* 考勤
|
||||||
|
*/
|
||||||
|
NOTICE_TYPE_ATTENDANCE("考勤消息", "attendance");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件类型名称
|
* 文件类型名称
|
||||||
|
|||||||
@ -0,0 +1,82 @@
|
|||||||
|
package org.jeecg.common.constant.enums;
|
||||||
|
|
||||||
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UniPush 消息推送枚举
|
||||||
|
* @author: jeecg-boot
|
||||||
|
*/
|
||||||
|
public enum UniPushTypeEnum {
|
||||||
|
/**
|
||||||
|
* 聊天
|
||||||
|
*/
|
||||||
|
CHAT("chat", "聊天消息", "收到%s发来的聊天消息"),
|
||||||
|
/**
|
||||||
|
* 流程跳转到我的任务
|
||||||
|
*/
|
||||||
|
BPM("bpm_task", "待办任务", "收到%s待办任务"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程抄送任务
|
||||||
|
*/
|
||||||
|
BPM_VIEW("bpm_cc", "知会任务", "收到%s知会任务"),
|
||||||
|
/**
|
||||||
|
* 系统消息
|
||||||
|
*/
|
||||||
|
SYS_MSG("system", "系统消息", "收到一条系统通告");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务类型(chat:聊天 bpm_task:流程 bpm_cc:流程抄送)
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
/**
|
||||||
|
* 消息标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
/**
|
||||||
|
* 消息内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
UniPushTypeEnum(String type, String title, String content) {
|
||||||
|
this.type = type;
|
||||||
|
this.title = title;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String openType) {
|
||||||
|
this.title = openType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UniPushTypeEnum getByType(String type) {
|
||||||
|
if (oConvertUtils.isEmpty(type)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (UniPushTypeEnum val : values()) {
|
||||||
|
if (val.getType().equals(type)) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -73,7 +73,7 @@ public class SensitiveDataAspect {
|
|||||||
SensitiveInfoUtil.handleNestedObject(result, entity, isEncode);
|
SensitiveInfoUtil.handleNestedObject(result, entity, isEncode);
|
||||||
}
|
}
|
||||||
long endTime=System.currentTimeMillis();
|
long endTime=System.currentTimeMillis();
|
||||||
log.info((isEncode ? "加密操作," : "解密操作,") + "Aspect程序耗时:" + (endTime - startTime) + "ms");
|
log.debug((isEncode ? "加密操作," : "解密操作,") + "Aspect程序耗时:" + (endTime - startTime) + "ms");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -387,7 +387,7 @@ public class JeecgElasticsearchTemplate {
|
|||||||
data.remove("id");
|
data.remove("id");
|
||||||
bodySb.append(data.toJSONString()).append("\n");
|
bodySb.append(data.toJSONString()).append("\n");
|
||||||
}
|
}
|
||||||
System.out.println("+-+-+-: bodySb.toString(): " + bodySb.toString());
|
//System.out.println("+-+-+-: bodySb.toString(): " + bodySb.toString());
|
||||||
HttpHeaders headers = RestUtil.getHeaderApplicationJson();
|
HttpHeaders headers = RestUtil.getHeaderApplicationJson();
|
||||||
RestUtil.request(url, HttpMethod.PUT, headers, null, bodySb, JSONObject.class);
|
RestUtil.request(url, HttpMethod.PUT, headers, null, bodySb, JSONObject.class);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -121,13 +121,12 @@ public class JeecgBootExceptionHandler {
|
|||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public Result<?> handleException(Exception e){
|
public Result<?> handleException(Exception e){
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
//update-begin---author:zyf ---date:20220411 for:处理Sentinel限流自定义异常
|
// 代码逻辑说明: 处理Sentinel限流自定义异常
|
||||||
Throwable throwable = e.getCause();
|
Throwable throwable = e.getCause();
|
||||||
SentinelErrorInfoEnum errorInfoEnum = SentinelErrorInfoEnum.getErrorByException(throwable);
|
SentinelErrorInfoEnum errorInfoEnum = SentinelErrorInfoEnum.getErrorByException(throwable);
|
||||||
if (ObjectUtil.isNotEmpty(errorInfoEnum)) {
|
if (ObjectUtil.isNotEmpty(errorInfoEnum)) {
|
||||||
return Result.error(errorInfoEnum.getError());
|
return Result.error(errorInfoEnum.getError());
|
||||||
}
|
}
|
||||||
//update-end---author:zyf ---date:20220411 for:处理Sentinel限流自定义异常
|
|
||||||
addSysLog(e);
|
addSysLog(e);
|
||||||
return Result.error("操作失败,"+e.getMessage());
|
return Result.error("操作失败,"+e.getMessage());
|
||||||
}
|
}
|
||||||
@ -224,7 +223,6 @@ public class JeecgBootExceptionHandler {
|
|||||||
return Result.error("校验失败,存在SQL注入风险!" + msg);
|
return Result.error("校验失败,存在SQL注入风险!" + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin---author:chenrui ---date:20240423 for:[QQYUN-8732]把错误的日志都抓取了 方便后续处理,单独弄个日志类型------------
|
|
||||||
/**
|
/**
|
||||||
* 添加异常新系统日志
|
* 添加异常新系统日志
|
||||||
* @param e 异常
|
* @param e 异常
|
||||||
@ -243,7 +241,6 @@ public class JeecgBootExceptionHandler {
|
|||||||
} catch (NullPointerException | BeansException ignored) {
|
} catch (NullPointerException | BeansException ignored) {
|
||||||
}
|
}
|
||||||
if (null != request) {
|
if (null != request) {
|
||||||
//update-begin---author:chenrui ---date:20250408 for:[QQYUN-11716]上传大图片失败没有精确提示------------
|
|
||||||
//请求的参数
|
//请求的参数
|
||||||
if (!isTooBigException(e)) {
|
if (!isTooBigException(e)) {
|
||||||
// 文件上传过大异常时不能获取参数,否则会报错
|
// 文件上传过大异常时不能获取参数,否则会报错
|
||||||
@ -252,7 +249,6 @@ public class JeecgBootExceptionHandler {
|
|||||||
log.setMethod(oConvertUtils.mapToString(request.getParameterMap()));
|
log.setMethod(oConvertUtils.mapToString(request.getParameterMap()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-end---author:chenrui ---date:20250408 for:[QQYUN-11716]上传大图片失败没有精确提示------------
|
|
||||||
// 请求地址
|
// 请求地址
|
||||||
log.setRequestUrl(request.getRequestURI());
|
log.setRequestUrl(request.getRequestURI());
|
||||||
//设置IP地址
|
//设置IP地址
|
||||||
@ -276,7 +272,6 @@ public class JeecgBootExceptionHandler {
|
|||||||
|
|
||||||
baseCommonService.addLog(log);
|
baseCommonService.addLog(log);
|
||||||
}
|
}
|
||||||
//update-end---author:chenrui ---date:20240423 for:[QQYUN-8732]把错误的日志都抓取了 方便后续处理,单独弄个日志类型------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否文件过大异常
|
* 是否文件过大异常
|
||||||
|
|||||||
@ -68,12 +68,16 @@ public class JeecgController<T, S extends IService<T>> {
|
|||||||
//此处设置的filename无效 ,前端会重更新设置一下
|
//此处设置的filename无效 ,前端会重更新设置一下
|
||||||
mv.addObject(NormalExcelConstants.FILE_NAME, title);
|
mv.addObject(NormalExcelConstants.FILE_NAME, title);
|
||||||
mv.addObject(NormalExcelConstants.CLASS, clazz);
|
mv.addObject(NormalExcelConstants.CLASS, clazz);
|
||||||
//update-begin--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置--------------------
|
// 代码逻辑说明: 【QQYUN-13930】统一改成导出xlsx格式---
|
||||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
|
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title, ExcelType.XSSF);
|
||||||
exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload());
|
exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload());
|
||||||
//update-end--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置----------------------
|
|
||||||
mv.addObject(NormalExcelConstants.PARAMS,exportParams);
|
mv.addObject(NormalExcelConstants.PARAMS,exportParams);
|
||||||
mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
|
mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
|
||||||
|
// 代码逻辑说明: 【issues/9052】BasicTable列表页导出excel可以指定列---
|
||||||
|
String exportFields = request.getParameter(NormalExcelConstants.EXPORT_FIELDS);
|
||||||
|
if(oConvertUtils.isNotEmpty(exportFields)){
|
||||||
|
mv.addObject(NormalExcelConstants.EXPORT_FIELDS, exportFields);
|
||||||
|
}
|
||||||
return mv;
|
return mv;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -94,14 +98,12 @@ public class JeecgController<T, S extends IService<T>> {
|
|||||||
// Step.2 计算分页sheet数据
|
// Step.2 计算分页sheet数据
|
||||||
double total = service.count();
|
double total = service.count();
|
||||||
int count = (int)Math.ceil(total/pageNum);
|
int count = (int)Math.ceil(total/pageNum);
|
||||||
//update-begin-author:liusq---date:20220629--for: 多sheet导出根据选择导出写法调整 ---
|
|
||||||
// Step.3 过滤选中数据
|
// Step.3 过滤选中数据
|
||||||
String selections = request.getParameter("selections");
|
String selections = request.getParameter("selections");
|
||||||
if (oConvertUtils.isNotEmpty(selections)) {
|
if (oConvertUtils.isNotEmpty(selections)) {
|
||||||
List<String> selectionList = Arrays.asList(selections.split(","));
|
List<String> selectionList = Arrays.asList(selections.split(","));
|
||||||
queryWrapper.in("id",selectionList);
|
queryWrapper.in("id",selectionList);
|
||||||
}
|
}
|
||||||
//update-end-author:liusq---date:20220629--for: 多sheet导出根据选择导出写法调整 ---
|
|
||||||
// Step.4 多sheet处理
|
// Step.4 多sheet处理
|
||||||
List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>();
|
List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>();
|
||||||
for (int i = 1; i <=count ; i++) {
|
for (int i = 1; i <=count ; i++) {
|
||||||
@ -220,16 +222,15 @@ public class JeecgController<T, S extends IService<T>> {
|
|||||||
params.setNeedSave(true);
|
params.setNeedSave(true);
|
||||||
try {
|
try {
|
||||||
List<T> list = ExcelImportUtil.importExcel(file.getInputStream(), clazz, params);
|
List<T> list = ExcelImportUtil.importExcel(file.getInputStream(), clazz, params);
|
||||||
//update-begin-author:taoyan date:20190528 for:批量插入数据
|
// 代码逻辑说明: 批量插入数据
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
service.saveBatch(list);
|
service.saveBatch(list);
|
||||||
//400条 saveBatch消耗时间1592毫秒 循环插入消耗时间1947毫秒
|
//400条 saveBatch消耗时间1592毫秒 循环插入消耗时间1947毫秒
|
||||||
//1200条 saveBatch消耗时间3687毫秒 循环插入消耗时间5212毫秒
|
//1200条 saveBatch消耗时间3687毫秒 循环插入消耗时间5212毫秒
|
||||||
log.info("消耗时间" + (System.currentTimeMillis() - start) + "毫秒");
|
log.info("消耗时间" + (System.currentTimeMillis() - start) + "毫秒");
|
||||||
//update-end-author:taoyan date:20190528 for:批量插入数据
|
|
||||||
return Result.ok("文件导入成功!数据行数:" + list.size());
|
return Result.ok("文件导入成功!数据行数:" + list.size());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
//update-begin-author:taoyan date:20211124 for: 导入数据重复增加提示
|
// 代码逻辑说明: 导入数据重复增加提示
|
||||||
String msg = e.getMessage();
|
String msg = e.getMessage();
|
||||||
log.error(msg, e);
|
log.error(msg, e);
|
||||||
if(msg!=null && msg.indexOf("Duplicate entry")>=0){
|
if(msg!=null && msg.indexOf("Duplicate entry")>=0){
|
||||||
@ -237,7 +238,6 @@ public class JeecgController<T, S extends IService<T>> {
|
|||||||
}else{
|
}else{
|
||||||
return Result.error("文件导入失败:" + e.getMessage());
|
return Result.error("文件导入失败:" + e.getMessage());
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:20211124 for: 导入数据重复增加提示
|
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
file.getInputStream().close();
|
file.getInputStream().close();
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: Entity基类
|
* @Description: Entity基类
|
||||||
|
|||||||
@ -97,7 +97,6 @@ public class QueryGenerator {
|
|||||||
return queryWrapper;
|
return queryWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin---author:chenrui ---date:20240527 for:[TV360X-378]增加自定义字段查询规则功能------------
|
|
||||||
/**
|
/**
|
||||||
* 获取查询条件构造器QueryWrapper实例 通用查询条件已被封装完成
|
* 获取查询条件构造器QueryWrapper实例 通用查询条件已被封装完成
|
||||||
* @param searchObj 查询实体
|
* @param searchObj 查询实体
|
||||||
@ -112,7 +111,6 @@ public class QueryGenerator {
|
|||||||
log.debug("---查询条件构造器初始化完成,耗时:"+(System.currentTimeMillis()-start)+"毫秒----");
|
log.debug("---查询条件构造器初始化完成,耗时:"+(System.currentTimeMillis()-start)+"毫秒----");
|
||||||
return queryWrapper;
|
return queryWrapper;
|
||||||
}
|
}
|
||||||
//update-end---author:chenrui ---date:20240527 for:[TV360X-378]增加自定义字段查询规则功能------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 组装Mybatis Plus 查询条件
|
* 组装Mybatis Plus 查询条件
|
||||||
@ -142,7 +140,6 @@ public class QueryGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String name, type, column;
|
String name, type, column;
|
||||||
// update-begin--Author:taoyan Date:20200923 for:issues/1671 如果字段加注解了@TableField(exist = false),不走DB查询-------
|
|
||||||
//定义实体字段和数据库字段名称的映射 高级查询中 只能获取实体字段 如果设置TableField注解 那么查询条件会出问题
|
//定义实体字段和数据库字段名称的映射 高级查询中 只能获取实体字段 如果设置TableField注解 那么查询条件会出问题
|
||||||
Map<String,String> fieldColumnMap = new HashMap<>(5);
|
Map<String,String> fieldColumnMap = new HashMap<>(5);
|
||||||
for (int i = 0; i < origDescriptors.length; i++) {
|
for (int i = 0; i < origDescriptors.length; i++) {
|
||||||
@ -188,7 +185,7 @@ public class QueryGenerator {
|
|||||||
queryWrapper.and(j -> j.like(field,vals[0]));
|
queryWrapper.and(j -> j.like(field,vals[0]));
|
||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
//update-begin---author:chenrui ---date:20240527 for:[TV360X-378]增加自定义字段查询规则功能------------
|
// 代码逻辑说明: [TV360X-378]增加自定义字段查询规则功能------------
|
||||||
QueryRuleEnum rule;
|
QueryRuleEnum rule;
|
||||||
if(null != customRuleMap && customRuleMap.containsKey(name)) {
|
if(null != customRuleMap && customRuleMap.containsKey(name)) {
|
||||||
// 有自定义规则,使用自定义规则.
|
// 有自定义规则,使用自定义规则.
|
||||||
@ -197,7 +194,6 @@ public class QueryGenerator {
|
|||||||
//根据参数值带什么关键字符串判断走什么类型的查询
|
//根据参数值带什么关键字符串判断走什么类型的查询
|
||||||
rule = convert2Rule(value);
|
rule = convert2Rule(value);
|
||||||
}
|
}
|
||||||
//update-end---author:chenrui ---date:20240527 for:[TV360X-378]增加自定义字段查询规则功能------------
|
|
||||||
value = replaceValue(rule,value);
|
value = replaceValue(rule,value);
|
||||||
// add -begin 添加判断为字符串时设为全模糊查询
|
// add -begin 添加判断为字符串时设为全模糊查询
|
||||||
//if( (rule==null || QueryRuleEnum.EQ.equals(rule)) && "class java.lang.String".equals(type)) {
|
//if( (rule==null || QueryRuleEnum.EQ.equals(rule)) && "class java.lang.String".equals(type)) {
|
||||||
@ -217,7 +213,6 @@ public class QueryGenerator {
|
|||||||
|
|
||||||
//高级查询
|
//高级查询
|
||||||
doSuperQuery(queryWrapper, parameterMap, fieldColumnMap);
|
doSuperQuery(queryWrapper, parameterMap, fieldColumnMap);
|
||||||
// update-end--Author:taoyan Date:20200923 for:issues/1671 如果字段加注解了@TableField(exist = false),不走DB查询-------
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,16 +255,16 @@ public class QueryGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(oConvertUtils.isNotEmpty(column)){
|
if(oConvertUtils.isNotEmpty(column)){
|
||||||
log.info("单字段排序规则>> column:" + column + ",排序方式:" + order);
|
log.debug("单字段排序规则>> column:" + column + ",排序方式:" + order);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 列表多字段排序优先
|
// 1. 列表多字段排序优先
|
||||||
if(parameterMap!=null&& parameterMap.containsKey("sortInfoString")) {
|
if(parameterMap!=null&& parameterMap.containsKey("sortInfoString")) {
|
||||||
// 多字段排序
|
// 多字段排序
|
||||||
String sortInfoString = parameterMap.get("sortInfoString")[0];
|
String sortInfoString = parameterMap.get("sortInfoString")[0];
|
||||||
log.info("多字段排序规则>> sortInfoString:" + sortInfoString);
|
log.debug("多字段排序规则>> sortInfoString:" + sortInfoString);
|
||||||
List<OrderItem> orderItemList = SqlConcatUtil.getQueryConditionOrders(column, order, sortInfoString);
|
List<OrderItem> orderItemList = SqlConcatUtil.getQueryConditionOrders(column, order, sortInfoString);
|
||||||
log.info(orderItemList.toString());
|
log.debug(orderItemList.toString());
|
||||||
if (orderItemList != null && !orderItemList.isEmpty()) {
|
if (orderItemList != null && !orderItemList.isEmpty()) {
|
||||||
for (OrderItem item : orderItemList) {
|
for (OrderItem item : orderItemList) {
|
||||||
// 一、获取排序数据库字段
|
// 一、获取排序数据库字段
|
||||||
@ -321,13 +316,11 @@ public class QueryGenerator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin-author:scott date:2022-11-07 for:避免用户自定义表无默认字段{创建时间},导致排序报错
|
|
||||||
//TODO 避免用户自定义表无默认字段创建时间,导致排序报错
|
//TODO 避免用户自定义表无默认字段创建时间,导致排序报错
|
||||||
if(DataBaseConstant.CREATE_TIME.equals(column) && !fieldColumnMap.containsKey(DataBaseConstant.CREATE_TIME)){
|
if(DataBaseConstant.CREATE_TIME.equals(column) && !fieldColumnMap.containsKey(DataBaseConstant.CREATE_TIME)){
|
||||||
column = "id";
|
column = "id";
|
||||||
log.warn("检测到实体里没有字段createTime,改成采用ID排序!");
|
log.warn("检测到实体里没有字段createTime,改成采用ID排序!");
|
||||||
}
|
}
|
||||||
//update-end-author:scott date:2022-11-07 for:避免用户自定义表无默认字段{创建时间},导致排序报错
|
|
||||||
|
|
||||||
if (oConvertUtils.isNotEmpty(column) && oConvertUtils.isNotEmpty(order)) {
|
if (oConvertUtils.isNotEmpty(column) && oConvertUtils.isNotEmpty(order)) {
|
||||||
//字典字段,去掉字典翻译文本后缀
|
//字典字段,去掉字典翻译文本后缀
|
||||||
@ -335,15 +328,12 @@ public class QueryGenerator {
|
|||||||
column = column.substring(0, column.lastIndexOf(CommonConstant.DICT_TEXT_SUFFIX));
|
column = column.substring(0, column.lastIndexOf(CommonConstant.DICT_TEXT_SUFFIX));
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin-author:taoyan date:2022-5-16 for: issues/3676 获取系统用户列表时,使用SQL注入生效
|
|
||||||
//判断column是不是当前实体的
|
//判断column是不是当前实体的
|
||||||
log.debug("当前字段有:"+ allFields);
|
log.debug("当前字段有:"+ allFields);
|
||||||
if (!allColumnExist(column, allFields)) {
|
if (!allColumnExist(column, allFields)) {
|
||||||
throw new JeecgBootException("请注意,将要排序的列字段不存在:" + column);
|
throw new JeecgBootException("请注意,将要排序的列字段不存在:" + column);
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:2022-5-16 for: issues/3676 获取系统用户列表时,使用SQL注入生效
|
|
||||||
|
|
||||||
//update-begin-author:scott date:2022-10-10 for:【jeecg-boot/issues/I5FJU6】doMultiFieldsOrder() 多字段排序方法存在问题
|
|
||||||
//多字段排序方法没有读取 MybatisPlus 注解 @TableField 里 value 的值
|
//多字段排序方法没有读取 MybatisPlus 注解 @TableField 里 value 的值
|
||||||
if (column.contains(",")) {
|
if (column.contains(",")) {
|
||||||
List<String> columnList = Arrays.asList(column.split(","));
|
List<String> columnList = Arrays.asList(column.split(","));
|
||||||
@ -354,12 +344,10 @@ public class QueryGenerator {
|
|||||||
}else{
|
}else{
|
||||||
column = fieldColumnMap.get(column);
|
column = fieldColumnMap.get(column);
|
||||||
}
|
}
|
||||||
//update-end-author:scott date:2022-10-10 for:【jeecg-boot/issues/I5FJU6】doMultiFieldsOrder() 多字段排序方法存在问题
|
|
||||||
|
|
||||||
//SQL注入check
|
//SQL注入check
|
||||||
SqlInjectionUtil.filterContentMulti(column);
|
SqlInjectionUtil.filterContentMulti(column);
|
||||||
|
|
||||||
//update-begin--Author:scott Date:20210531 for:36 多条件排序无效问题修正-------
|
|
||||||
// 排序规则修改
|
// 排序规则修改
|
||||||
// 将现有排序 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1,column2 desc"
|
// 将现有排序 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1,column2 desc"
|
||||||
// 修改为 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1 desc,column2 desc"
|
// 修改为 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1 desc,column2 desc"
|
||||||
@ -368,11 +356,9 @@ public class QueryGenerator {
|
|||||||
} else {
|
} else {
|
||||||
queryWrapper.orderByDesc(SqlInjectionUtil.getSqlInjectSortFields(column.split(",")));
|
queryWrapper.orderByDesc(SqlInjectionUtil.getSqlInjectSortFields(column.split(",")));
|
||||||
}
|
}
|
||||||
//update-end--Author:scott Date:20210531 for:36 多条件排序无效问题修正-------
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin-author:taoyan date:2022-5-23 for: issues/3676 获取系统用户列表时,使用SQL注入生效
|
|
||||||
/**
|
/**
|
||||||
* 多字段排序 判断所传字段是否存在
|
* 多字段排序 判断所传字段是否存在
|
||||||
* @return
|
* @return
|
||||||
@ -392,7 +378,6 @@ public class QueryGenerator {
|
|||||||
}
|
}
|
||||||
return exist;
|
return exist;
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:2022-5-23 for: issues/3676 获取系统用户列表时,使用SQL注入生效
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 高级查询
|
* 高级查询
|
||||||
@ -405,14 +390,14 @@ public class QueryGenerator {
|
|||||||
String superQueryParams = parameterMap.get(SUPER_QUERY_PARAMS)[0];
|
String superQueryParams = parameterMap.get(SUPER_QUERY_PARAMS)[0];
|
||||||
String superQueryMatchType = parameterMap.get(SUPER_QUERY_MATCH_TYPE) != null ? parameterMap.get(SUPER_QUERY_MATCH_TYPE)[0] : MatchTypeEnum.AND.getValue();
|
String superQueryMatchType = parameterMap.get(SUPER_QUERY_MATCH_TYPE) != null ? parameterMap.get(SUPER_QUERY_MATCH_TYPE)[0] : MatchTypeEnum.AND.getValue();
|
||||||
MatchTypeEnum matchType = MatchTypeEnum.getByValue(superQueryMatchType);
|
MatchTypeEnum matchType = MatchTypeEnum.getByValue(superQueryMatchType);
|
||||||
// update-begin--Author:sunjianlei Date:20200325 for:高级查询的条件要用括号括起来,防止和用户的其他条件冲突 -------
|
// 代码逻辑说明: 高级查询的条件要用括号括起来,防止和用户的其他条件冲突 -------
|
||||||
try {
|
try {
|
||||||
superQueryParams = URLDecoder.decode(superQueryParams, "UTF-8");
|
superQueryParams = URLDecoder.decode(superQueryParams, "UTF-8");
|
||||||
List<QueryCondition> conditions = JSON.parseArray(superQueryParams, QueryCondition.class);
|
List<QueryCondition> conditions = JSON.parseArray(superQueryParams, QueryCondition.class);
|
||||||
if (conditions == null || conditions.size() == 0) {
|
if (conditions == null || conditions.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// update-begin-author:sunjianlei date:20220119 for: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and
|
// 代码逻辑说明: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and
|
||||||
List<QueryCondition> filterConditions = conditions.stream().filter(
|
List<QueryCondition> filterConditions = conditions.stream().filter(
|
||||||
rule -> (oConvertUtils.isNotEmpty(rule.getField())
|
rule -> (oConvertUtils.isNotEmpty(rule.getField())
|
||||||
&& oConvertUtils.isNotEmpty(rule.getRule())
|
&& oConvertUtils.isNotEmpty(rule.getRule())
|
||||||
@ -423,7 +408,6 @@ public class QueryGenerator {
|
|||||||
if (filterConditions.size() == 0) {
|
if (filterConditions.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// update-end-author:sunjianlei date:20220119 for: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and
|
|
||||||
log.debug("---高级查询参数-->" + filterConditions);
|
log.debug("---高级查询参数-->" + filterConditions);
|
||||||
|
|
||||||
queryWrapper.and(andWrapper -> {
|
queryWrapper.and(andWrapper -> {
|
||||||
@ -438,14 +422,14 @@ public class QueryGenerator {
|
|||||||
|
|
||||||
log.debug("SuperQuery ==> " + rule.toString());
|
log.debug("SuperQuery ==> " + rule.toString());
|
||||||
|
|
||||||
//update-begin-author:taoyan date:20201228 for: 【高级查询】 oracle 日期等于查询报错
|
// 代码逻辑说明: 【高级查询】 oracle 日期等于查询报错
|
||||||
Object queryValue = rule.getVal();
|
Object queryValue = rule.getVal();
|
||||||
if("date".equals(rule.getType())){
|
if("date".equals(rule.getType())){
|
||||||
queryValue = DateUtils.str2Date(rule.getVal(),DateUtils.date_sdf.get());
|
queryValue = DateUtils.str2Date(rule.getVal(),DateUtils.date_sdf.get());
|
||||||
}else if("datetime".equals(rule.getType())){
|
}else if("datetime".equals(rule.getType())){
|
||||||
queryValue = DateUtils.str2Date(rule.getVal(), DateUtils.datetimeFormat.get());
|
queryValue = DateUtils.str2Date(rule.getVal(), DateUtils.datetimeFormat.get());
|
||||||
}
|
}
|
||||||
// update-begin--author:sunjianlei date:20210702 for:【/issues/I3VR8E】高级查询没有类型转换,查询参数都是字符串类型 ----
|
// 代码逻辑说明: 【/issues/I3VR8E】高级查询没有类型转换,查询参数都是字符串类型 ----
|
||||||
String dbType = rule.getDbType();
|
String dbType = rule.getDbType();
|
||||||
if (oConvertUtils.isNotEmpty(dbType)) {
|
if (oConvertUtils.isNotEmpty(dbType)) {
|
||||||
try {
|
try {
|
||||||
@ -478,9 +462,8 @@ public class QueryGenerator {
|
|||||||
log.error("高级查询值转换失败:", e);
|
log.error("高级查询值转换失败:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// update-begin--author:sunjianlei date:20210702 for:【/issues/I3VR8E】高级查询没有类型转换,查询参数都是字符串类型 ----
|
// 代码逻辑说明: 【/issues/I3VR8E】高级查询没有类型转换,查询参数都是字符串类型 ----
|
||||||
addEasyQuery(andWrapper, fieldColumnMap.get(rule.getField()), QueryRuleEnum.getByValue(rule.getRule()), queryValue);
|
addEasyQuery(andWrapper, fieldColumnMap.get(rule.getField()), QueryRuleEnum.getByValue(rule.getRule()), queryValue);
|
||||||
//update-end-author:taoyan date:20201228 for: 【高级查询】 oracle 日期等于查询报错
|
|
||||||
|
|
||||||
// 如果拼接方式是OR,就拼接OR
|
// 如果拼接方式是OR,就拼接OR
|
||||||
if (MatchTypeEnum.OR == matchType && i < (filterConditions.size() - 1)) {
|
if (MatchTypeEnum.OR == matchType && i < (filterConditions.size() - 1)) {
|
||||||
@ -496,7 +479,6 @@ public class QueryGenerator {
|
|||||||
log.error("--高级查询拼接失败:" + e.getMessage());
|
log.error("--高级查询拼接失败:" + e.getMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
// update-end--Author:sunjianlei Date:20200325 for:高级查询的条件要用括号括起来,防止和用户的其他条件冲突 -------
|
|
||||||
}
|
}
|
||||||
//log.info(" superQuery getCustomSqlSegment: "+ queryWrapper.getCustomSqlSegment());
|
//log.info(" superQuery getCustomSqlSegment: "+ queryWrapper.getCustomSqlSegment());
|
||||||
}
|
}
|
||||||
@ -508,7 +490,7 @@ public class QueryGenerator {
|
|||||||
*/
|
*/
|
||||||
public static QueryRuleEnum convert2Rule(Object value) {
|
public static QueryRuleEnum convert2Rule(Object value) {
|
||||||
// 避免空数据
|
// 避免空数据
|
||||||
// update-begin-author:taoyan date:20210629 for: 查询条件输入空格导致return null后续判断导致抛出null异常
|
// 代码逻辑说明: 查询条件输入空格导致return null后续判断导致抛出null异常
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return QueryRuleEnum.EQ;
|
return QueryRuleEnum.EQ;
|
||||||
}
|
}
|
||||||
@ -516,10 +498,8 @@ public class QueryGenerator {
|
|||||||
if (val.length() == 0) {
|
if (val.length() == 0) {
|
||||||
return QueryRuleEnum.EQ;
|
return QueryRuleEnum.EQ;
|
||||||
}
|
}
|
||||||
// update-end-author:taoyan date:20210629 for: 查询条件输入空格导致return null后续判断导致抛出null异常
|
|
||||||
QueryRuleEnum rule =null;
|
QueryRuleEnum rule =null;
|
||||||
|
|
||||||
//update-begin--Author:scott Date:20190724 for:initQueryWrapper组装sql查询条件错误 #284-------------------
|
|
||||||
//TODO 此处规则,只适用于 le lt ge gt
|
//TODO 此处规则,只适用于 le lt ge gt
|
||||||
// step 2 .>= =<
|
// step 2 .>= =<
|
||||||
int length2 = 2;
|
int length2 = 2;
|
||||||
@ -535,14 +515,12 @@ public class QueryGenerator {
|
|||||||
rule = QueryRuleEnum.getByValue(val.substring(0, 1));
|
rule = QueryRuleEnum.getByValue(val.substring(0, 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-end--Author:scott Date:20190724 for:initQueryWrapper组装sql查询条件错误 #284---------------------
|
|
||||||
|
|
||||||
// step 3 like
|
// step 3 like
|
||||||
//update-begin-author:taoyan for: /issues/3382 默认带*就走模糊,但是如果只有一个*,那么走等于查询
|
// 代码逻辑说明: /issues/3382 默认带*就走模糊,但是如果只有一个*,那么走等于查询
|
||||||
if(rule == null && val.equals(STAR)){
|
if(rule == null && val.equals(STAR)){
|
||||||
rule = QueryRuleEnum.EQ;
|
rule = QueryRuleEnum.EQ;
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan for: /issues/3382 默认带*就走模糊,但是如果只有一个*,那么走等于查询
|
|
||||||
if (rule == null && val.contains(STAR)) {
|
if (rule == null && val.contains(STAR)) {
|
||||||
if (val.startsWith(STAR) && val.endsWith(STAR)) {
|
if (val.startsWith(STAR) && val.endsWith(STAR)) {
|
||||||
rule = QueryRuleEnum.LIKE;
|
rule = QueryRuleEnum.LIKE;
|
||||||
@ -567,12 +545,10 @@ public class QueryGenerator {
|
|||||||
rule = QueryRuleEnum.EQ_WITH_ADD;
|
rule = QueryRuleEnum.EQ_WITH_ADD;
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin--Author:taoyan Date:20201229 for:initQueryWrapper组装sql查询条件错误 #284---------------------
|
|
||||||
//特殊处理:Oracle的表达式to_date('xxx','yyyy-MM-dd')含有逗号,会被识别为in查询,转为等于查询
|
//特殊处理:Oracle的表达式to_date('xxx','yyyy-MM-dd')含有逗号,会被识别为in查询,转为等于查询
|
||||||
if(rule == QueryRuleEnum.IN && val.indexOf(YYYY_MM_DD)>=0 && val.indexOf(TO_DATE)>=0){
|
if(rule == QueryRuleEnum.IN && val.indexOf(YYYY_MM_DD)>=0 && val.indexOf(TO_DATE)>=0){
|
||||||
rule = QueryRuleEnum.EQ;
|
rule = QueryRuleEnum.EQ;
|
||||||
}
|
}
|
||||||
//update-end--Author:taoyan Date:20201229 for:initQueryWrapper组装sql查询条件错误 #284---------------------
|
|
||||||
|
|
||||||
return rule != null ? rule : QueryRuleEnum.EQ;
|
return rule != null ? rule : QueryRuleEnum.EQ;
|
||||||
}
|
}
|
||||||
@ -592,11 +568,10 @@ public class QueryGenerator {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
String val = (value + "").toString().trim();
|
String val = (value + "").toString().trim();
|
||||||
//update-begin-author:taoyan date:20220302 for: 查询条件的值为等号(=)bug #3443
|
// 代码逻辑说明: 查询条件的值为等号(=)bug #3443
|
||||||
if(QueryRuleEnum.EQ.getValue().equals(val)){
|
if(QueryRuleEnum.EQ.getValue().equals(val)){
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:20220302 for: 查询条件的值为等号(=)bug #3443
|
|
||||||
if (rule == QueryRuleEnum.LIKE) {
|
if (rule == QueryRuleEnum.LIKE) {
|
||||||
value = val.substring(1, val.length() - 1);
|
value = val.substring(1, val.length() - 1);
|
||||||
//mysql 模糊查询之特殊字符下划线 (_、\)
|
//mysql 模糊查询之特殊字符下划线 (_、\)
|
||||||
@ -614,21 +589,19 @@ public class QueryGenerator {
|
|||||||
} else if (rule == QueryRuleEnum.EQ_WITH_ADD) {
|
} else if (rule == QueryRuleEnum.EQ_WITH_ADD) {
|
||||||
value = val.replaceAll("\\+\\+", COMMA);
|
value = val.replaceAll("\\+\\+", COMMA);
|
||||||
}else {
|
}else {
|
||||||
//update-begin--Author:scott Date:20190724 for:initQueryWrapper组装sql查询条件错误 #284-------------------
|
// 代码逻辑说明: initQueryWrapper组装sql查询条件错误 #284-------------------
|
||||||
if(val.startsWith(rule.getValue())){
|
if(val.startsWith(rule.getValue())){
|
||||||
//TODO 此处逻辑应该注释掉-> 如果查询内容中带有查询匹配规则符号,就会被截取的(比如:>=您好)
|
//TODO 此处逻辑应该注释掉-> 如果查询内容中带有查询匹配规则符号,就会被截取的(比如:>=您好)
|
||||||
value = val.replaceFirst(rule.getValue(),"");
|
value = val.replaceFirst(rule.getValue(),"");
|
||||||
}else if(val.startsWith(rule.getCondition()+QUERY_SEPARATE_KEYWORD)){
|
}else if(val.startsWith(rule.getCondition()+QUERY_SEPARATE_KEYWORD)){
|
||||||
value = val.replaceFirst(rule.getCondition()+QUERY_SEPARATE_KEYWORD,"").trim();
|
value = val.replaceFirst(rule.getCondition()+QUERY_SEPARATE_KEYWORD,"").trim();
|
||||||
}
|
}
|
||||||
//update-end--Author:scott Date:20190724 for:initQueryWrapper组装sql查询条件错误 #284-------------------
|
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addQueryByRule(QueryWrapper<?> queryWrapper,String name,String type,String value,QueryRuleEnum rule) throws ParseException {
|
private static void addQueryByRule(QueryWrapper<?> queryWrapper,String name,String type,String value,QueryRuleEnum rule) throws ParseException {
|
||||||
if(oConvertUtils.isNotEmpty(value)) {
|
if(oConvertUtils.isNotEmpty(value)) {
|
||||||
//update-begin--Author:sunjianlei Date:20220104 for:【JTC-409】修复逗号分割情况下没有转换类型,导致类型严格的数据库查询报错 -------------------
|
|
||||||
// 针对数字类型字段,多值查询
|
// 针对数字类型字段,多值查询
|
||||||
if(value.contains(COMMA)){
|
if(value.contains(COMMA)){
|
||||||
Object[] temp = Arrays.stream(value.split(COMMA)).map(v -> {
|
Object[] temp = Arrays.stream(value.split(COMMA)).map(v -> {
|
||||||
@ -644,7 +617,6 @@ public class QueryGenerator {
|
|||||||
}
|
}
|
||||||
Object temp = QueryGenerator.parseByType(value, type, rule);
|
Object temp = QueryGenerator.parseByType(value, type, rule);
|
||||||
addEasyQuery(queryWrapper, name, rule, temp);
|
addEasyQuery(queryWrapper, name, rule, temp);
|
||||||
//update-end--Author:sunjianlei Date:20220104 for:【JTC-409】修复逗号分割情况下没有转换类型,导致类型严格的数据库查询报错 -------------------
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -759,13 +731,12 @@ public class QueryGenerator {
|
|||||||
}else if(value instanceof String[]) {
|
}else if(value instanceof String[]) {
|
||||||
queryWrapper.in(name, (Object[]) value);
|
queryWrapper.in(name, (Object[]) value);
|
||||||
}
|
}
|
||||||
//update-begin-author:taoyan date:20200909 for:【bug】in 类型多值查询 不适配postgresql #1671
|
// 代码逻辑说明: 【bug】in 类型多值查询 不适配postgresql #1671
|
||||||
else if(value.getClass().isArray()) {
|
else if(value.getClass().isArray()) {
|
||||||
queryWrapper.in(name, (Object[])value);
|
queryWrapper.in(name, (Object[])value);
|
||||||
}else {
|
}else {
|
||||||
queryWrapper.in(name, value);
|
queryWrapper.in(name, value);
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:20200909 for:【bug】in 类型多值查询 不适配postgresql #1671
|
|
||||||
break;
|
break;
|
||||||
case LIKE:
|
case LIKE:
|
||||||
queryWrapper.like(name, value);
|
queryWrapper.like(name, value);
|
||||||
@ -782,7 +753,7 @@ public class QueryGenerator {
|
|||||||
case NOT_RIGHT_LIKE:
|
case NOT_RIGHT_LIKE:
|
||||||
queryWrapper.notLikeRight(name, value);
|
queryWrapper.notLikeRight(name, value);
|
||||||
break;
|
break;
|
||||||
//update-begin---author:chenrui ---date:20240527 for:[TV360X-378]下拉多框根据条件查询不出来:增加自定义字段查询规则功能------------
|
// 代码逻辑说明: [TV360X-378]下拉多框根据条件查询不出来:增加自定义字段查询规则功能------------
|
||||||
case LIKE_WITH_OR:
|
case LIKE_WITH_OR:
|
||||||
final String nameFinal = name;
|
final String nameFinal = name;
|
||||||
Object[] vals;
|
Object[] vals;
|
||||||
@ -791,7 +762,7 @@ public class QueryGenerator {
|
|||||||
} else if (value instanceof String[]) {
|
} else if (value instanceof String[]) {
|
||||||
vals = (Object[]) value;
|
vals = (Object[]) value;
|
||||||
}
|
}
|
||||||
//update-begin-author:taoyan date:20200909 for:【bug】in 类型多值查询 不适配postgresql #1671
|
// 代码逻辑说明: 【bug】in 类型多值查询 不适配postgresql #1671
|
||||||
else if (value.getClass().isArray()) {
|
else if (value.getClass().isArray()) {
|
||||||
vals = (Object[]) value;
|
vals = (Object[]) value;
|
||||||
} else {
|
} else {
|
||||||
@ -806,7 +777,6 @@ public class QueryGenerator {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
//update-end---author:chenrui ---date:20240527 for:[TV360X-378]下拉多框根据条件查询不出来:增加自定义字段查询规则功能------------
|
|
||||||
default:
|
default:
|
||||||
log.info("--查询规则未匹配到---");
|
log.info("--查询规则未匹配到---");
|
||||||
break;
|
break;
|
||||||
@ -820,10 +790,8 @@ public class QueryGenerator {
|
|||||||
private static boolean judgedIsUselessField(String name) {
|
private static boolean judgedIsUselessField(String name) {
|
||||||
return "class".equals(name) || "ids".equals(name)
|
return "class".equals(name) || "ids".equals(name)
|
||||||
|| "page".equals(name) || "rows".equals(name)
|
|| "page".equals(name) || "rows".equals(name)
|
||||||
//// update-begin--author:sunjianlei date:20240808 for:【TV360X-2009】取消过滤 sort、order 字段,防止前端排序报错 ------
|
|
||||||
//// https://github.com/jeecgboot/JeecgBoot/issues/6937
|
//// https://github.com/jeecgboot/JeecgBoot/issues/6937
|
||||||
// || "sort".equals(name) || "order".equals(name)
|
// || "sort".equals(name) || "order".equals(name)
|
||||||
//// update-end----author:sunjianlei date:20240808 for:【TV360X-2009】取消过滤 sort、order 字段,防止前端排序报错 ------
|
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -836,13 +804,12 @@ public class QueryGenerator {
|
|||||||
public static Map<String, SysPermissionDataRuleModel> getRuleMap() {
|
public static Map<String, SysPermissionDataRuleModel> getRuleMap() {
|
||||||
Map<String, SysPermissionDataRuleModel> ruleMap = new HashMap<>(5);
|
Map<String, SysPermissionDataRuleModel> ruleMap = new HashMap<>(5);
|
||||||
List<SysPermissionDataRuleModel> list = null;
|
List<SysPermissionDataRuleModel> list = null;
|
||||||
//update-begin-author:taoyan date:2023-6-1 for:QQYUN-5441 【简流】获取多个用户/部门/角色 设置部门查询 报错
|
// 代码逻辑说明: QQYUN-5441 【简流】获取多个用户/部门/角色 设置部门查询 报错
|
||||||
try {
|
try {
|
||||||
list = JeecgDataAutorUtils.loadDataSearchConditon();
|
list = JeecgDataAutorUtils.loadDataSearchConditon();
|
||||||
}catch (Exception e){
|
}catch (Exception e){
|
||||||
log.error("根据request对象获取权限数据失败,可能是定时任务中执行的。", e);
|
log.error("根据request对象获取权限数据失败,可能是定时任务中执行的。", e);
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:2023-6-1 for:QQYUN-5441 【简流】获取多个用户/部门/角色 设置部门查询 报错
|
|
||||||
if(list != null&&list.size()>0){
|
if(list != null&&list.size()>0){
|
||||||
if(list.get(0)==null){
|
if(list.get(0)==null){
|
||||||
return ruleMap;
|
return ruleMap;
|
||||||
@ -879,9 +846,8 @@ public class QueryGenerator {
|
|||||||
addEasyQuery(queryWrapper, name, rule, DateUtils.str2Date(dateStr,DateUtils.datetimeFormat.get()));
|
addEasyQuery(queryWrapper, name, rule, DateUtils.str2Date(dateStr,DateUtils.datetimeFormat.get()));
|
||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
//update-begin---author:chenrui ---date:20241125 for:[issues/7481]多租户模式下 数据权限使用变量:#{tenant_id} 报错------------
|
// 代码逻辑说明: [issues/7481]多租户模式下 数据权限使用变量:#{tenant_id} 报错------------
|
||||||
addEasyQuery(queryWrapper, name, rule, NumberUtils.parseNumber(converRuleValue(dataRule.getRuleValue()), propertyType));
|
addEasyQuery(queryWrapper, name, rule, NumberUtils.parseNumber(converRuleValue(dataRule.getRuleValue()), propertyType));
|
||||||
//update-end---author:chenrui ---date:20241125 for:[issues/7481]多租户模式下 数据权限使用变量:#{tenant_id} 报错------------
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -935,9 +901,8 @@ public class QueryGenerator {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Set<String> varParams = new HashSet<String>();
|
Set<String> varParams = new HashSet<String>();
|
||||||
//update-begin---author:chenrui ---date:20250108 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
// 代码逻辑说明: [QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||||
String regex = "#\\{\\[*\\w+]*}";
|
String regex = "#\\{\\[*\\w+]*}";
|
||||||
//update-end---author:chenrui ---date:20250108 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
|
||||||
|
|
||||||
Pattern p = Pattern.compile(regex);
|
Pattern p = Pattern.compile(regex);
|
||||||
Matcher m = p.matcher(sql);
|
Matcher m = p.matcher(sql);
|
||||||
@ -993,9 +958,8 @@ public class QueryGenerator {
|
|||||||
Class propType = origDescriptors[i].getPropertyType();
|
Class propType = origDescriptors[i].getPropertyType();
|
||||||
boolean isString = propType.equals(String.class);
|
boolean isString = propType.equals(String.class);
|
||||||
Object value;
|
Object value;
|
||||||
//update-begin---author:chenrui ---date:20240527 for:[TV360X-539]数据权限,配置日期等于条件时后端报转换错误------------
|
// 代码逻辑说明: [TV360X-539]数据权限,配置日期等于条件时后端报转换错误------------
|
||||||
if(isString || Date.class.equals(propType)) {
|
if(isString || Date.class.equals(propType)) {
|
||||||
//update-end---author:chenrui ---date:20240527 for:[TV360X-539]数据权限,配置日期等于条件时后端报转换错误------------
|
|
||||||
value = converRuleValue(dataRule.getRuleValue());
|
value = converRuleValue(dataRule.getRuleValue());
|
||||||
}else {
|
}else {
|
||||||
value = NumberUtils.parseNumber(dataRule.getRuleValue(),propType);
|
value = NumberUtils.parseNumber(dataRule.getRuleValue(),propType);
|
||||||
|
|||||||
@ -41,8 +41,10 @@ import org.jeecg.common.util.oConvertUtils;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class JwtUtil {
|
public class JwtUtil {
|
||||||
|
|
||||||
/**Token有效期为7天(Token在reids中缓存时间为两倍)*/
|
/**PC端,Token有效期为7天(Token在reids中缓存时间为两倍)*/
|
||||||
public static final long EXPIRE_TIME = (7 * 12) * 60 * 60 * 1000;
|
public static final long EXPIRE_TIME = (7 * 12) * 60 * 60 * 1000L;
|
||||||
|
/**APP端,Token有效期为30天(Token在reids中缓存时间为两倍)*/
|
||||||
|
public static final long APP_EXPIRE_TIME = (30 * 12) * 60 * 60 * 1000L;
|
||||||
static final String WELL_NUMBER = SymbolConstant.WELL_NUMBER + SymbolConstant.LEFT_CURLY_BRACKET;
|
static final String WELL_NUMBER = SymbolConstant.WELL_NUMBER + SymbolConstant.LEFT_CURLY_BRACKET;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,7 +88,7 @@ public class JwtUtil {
|
|||||||
DecodedJWT jwt = verifier.verify(token);
|
DecodedJWT jwt = verifier.verify(token);
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.warn("Token验证失败:" + e.getMessage(),e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,7 +114,9 @@ public class JwtUtil {
|
|||||||
* @param username 用户名
|
* @param username 用户名
|
||||||
* @param secret 用户的密码
|
* @param secret 用户的密码
|
||||||
* @return 加密的token
|
* @return 加密的token
|
||||||
|
* @deprecated 请使用sign(String username, String secret, String clientType)方法代替
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static String sign(String username, String secret) {
|
public static String sign(String username, String secret) {
|
||||||
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
|
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
|
||||||
Algorithm algorithm = Algorithm.HMAC256(secret);
|
Algorithm algorithm = Algorithm.HMAC256(secret);
|
||||||
@ -121,6 +125,68 @@ public class JwtUtil {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成签名,5min后过期
|
||||||
|
*
|
||||||
|
* @param username 用户名
|
||||||
|
* @param secret 用户的密码
|
||||||
|
* @param expireTime 过期时间
|
||||||
|
* @return 加密的token
|
||||||
|
* @deprecated 请使用sign(String username, String secret, String clientType)方法代替
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static String sign(String username, String secret, Long expireTime) {
|
||||||
|
Date date = new Date(System.currentTimeMillis() + expireTime);
|
||||||
|
Algorithm algorithm = Algorithm.HMAC256(secret);
|
||||||
|
// 附带username信息
|
||||||
|
return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成签名,根据客户端类型自动选择过期时间
|
||||||
|
* for [JHHB-1030]【鉴权】移动端用户token到期后续期时间变成pc端时长
|
||||||
|
*
|
||||||
|
* @param username 用户名
|
||||||
|
* @param secret 用户的密码
|
||||||
|
* @param clientType 客户端类型(PC或APP)
|
||||||
|
* @return 加密的token
|
||||||
|
*/
|
||||||
|
public static String sign(String username, String secret, String clientType) {
|
||||||
|
// 根据客户端类型选择对应的过期时间
|
||||||
|
long expireTime = CommonConstant.CLIENT_TYPE_APP.equalsIgnoreCase(clientType)
|
||||||
|
? APP_EXPIRE_TIME
|
||||||
|
: EXPIRE_TIME;
|
||||||
|
Date date = new Date(System.currentTimeMillis() + expireTime);
|
||||||
|
Algorithm algorithm = Algorithm.HMAC256(secret);
|
||||||
|
// 附带username和clientType信息
|
||||||
|
return JWT.create()
|
||||||
|
.withClaim("username", username)
|
||||||
|
.withClaim("clientType", clientType)
|
||||||
|
.withExpiresAt(date)
|
||||||
|
.sign(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从token中获取客户端类型
|
||||||
|
* for [JHHB-1030]【鉴权】移动端用户token到期后续期时间变成pc端时长
|
||||||
|
*
|
||||||
|
* @param token JWT token
|
||||||
|
* @return 客户端类型,如果不存在则返回PC(兼容旧token)
|
||||||
|
*/
|
||||||
|
public static String getClientType(String token) {
|
||||||
|
try {
|
||||||
|
DecodedJWT jwt = JWT.decode(token);
|
||||||
|
String clientType = jwt.getClaim("clientType").asString();
|
||||||
|
// 如果clientType为空,返回默认值PC(兼容旧token)
|
||||||
|
return oConvertUtils.isNotEmpty(clientType) ? clientType : CommonConstant.CLIENT_TYPE_PC;
|
||||||
|
} catch (JWTDecodeException e) {
|
||||||
|
log.warn("解析token中的clientType失败,使用默认值PC:" + e.getMessage());
|
||||||
|
return CommonConstant.CLIENT_TYPE_PC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据request中的token获取用户账号
|
* 根据request中的token获取用户账号
|
||||||
*
|
*
|
||||||
@ -200,7 +266,6 @@ public class JwtUtil {
|
|||||||
} else {
|
} else {
|
||||||
key = key;
|
key = key;
|
||||||
}
|
}
|
||||||
//update-begin---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
|
||||||
// 是否存在字符串标志
|
// 是否存在字符串标志
|
||||||
boolean multiStr;
|
boolean multiStr;
|
||||||
if(oConvertUtils.isNotEmpty(key) && key.trim().matches("^\\[\\w+]$")){
|
if(oConvertUtils.isNotEmpty(key) && key.trim().matches("^\\[\\w+]$")){
|
||||||
@ -209,7 +274,6 @@ public class JwtUtil {
|
|||||||
} else {
|
} else {
|
||||||
multiStr = false;
|
multiStr = false;
|
||||||
}
|
}
|
||||||
//update-end---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
|
||||||
//替换为当前系统时间(年月日)
|
//替换为当前系统时间(年月日)
|
||||||
if (key.equals(DataBaseConstant.SYS_DATE)|| key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
|
if (key.equals(DataBaseConstant.SYS_DATE)|| key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
|
||||||
returnValue = DateUtils.formatDate();
|
returnValue = DateUtils.formatDate();
|
||||||
@ -278,20 +342,17 @@ public class JwtUtil {
|
|||||||
if(user==null){
|
if(user==null){
|
||||||
//TODO 暂时使用用户登录部门,存在逻辑缺陷,不是用户所拥有的部门
|
//TODO 暂时使用用户登录部门,存在逻辑缺陷,不是用户所拥有的部门
|
||||||
returnValue = sysUser.getOrgCode();
|
returnValue = sysUser.getOrgCode();
|
||||||
//update-begin---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
// 代码逻辑说明: [QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||||
returnValue = multiStr ? "'" + returnValue + "'" : returnValue;
|
returnValue = multiStr ? "'" + returnValue + "'" : returnValue;
|
||||||
//update-end---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
|
||||||
}else{
|
}else{
|
||||||
if(user.isOneDepart()) {
|
if(user.isOneDepart()) {
|
||||||
returnValue = user.getSysMultiOrgCode().get(0);
|
returnValue = user.getSysMultiOrgCode().get(0);
|
||||||
//update-begin---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
// 代码逻辑说明: [QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||||
returnValue = multiStr ? "'" + returnValue + "'" : returnValue;
|
returnValue = multiStr ? "'" + returnValue + "'" : returnValue;
|
||||||
//update-end---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
|
||||||
}else {
|
}else {
|
||||||
//update-begin---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
// 代码逻辑说明: [QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
||||||
returnValue = user.getSysMultiOrgCode().stream()
|
returnValue = user.getSysMultiOrgCode().stream()
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
//update-begin---author:chenrui ---date:20250224 for:[issues/7288]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
|
||||||
.map(orgCode -> {
|
.map(orgCode -> {
|
||||||
if (multiStr) {
|
if (multiStr) {
|
||||||
return "'" + orgCode + "'";
|
return "'" + orgCode + "'";
|
||||||
@ -299,9 +360,7 @@ public class JwtUtil {
|
|||||||
return orgCode;
|
return orgCode;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
//update-end---author:chenrui ---date:20250224 for:[issues/7288]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
|
||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
//update-end---author:chenrui ---date:20250107 for:[QQYUN-10785]数据权限,查看自己拥有部门的权限中存在问题 #7288------------
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,7 +374,7 @@ public class JwtUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin-author:taoyan date:20210330 for:多租户ID作为系统变量
|
// 代码逻辑说明: 多租户ID作为系统变量
|
||||||
else if (key.equals(TenantConstant.TENANT_ID) || key.toLowerCase().equals(TenantConstant.TENANT_ID_TABLE)){
|
else if (key.equals(TenantConstant.TENANT_ID) || key.toLowerCase().equals(TenantConstant.TENANT_ID_TABLE)){
|
||||||
try {
|
try {
|
||||||
returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID);
|
returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID);
|
||||||
@ -323,7 +382,6 @@ public class JwtUtil {
|
|||||||
log.warn("获取系统租户异常:" + e.getMessage());
|
log.warn("获取系统租户异常:" + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:20210330 for:多租户ID作为系统变量
|
|
||||||
if(returnValue!=null){returnValue = returnValue + moshi;}
|
if(returnValue!=null){returnValue = returnValue + moshi;}
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,7 @@ package org.jeecg.common.system.util;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jeecg.common.system.annotation.EnumDict;
|
import org.jeecg.common.system.annotation.EnumDict;
|
||||||
import org.jeecg.common.system.vo.DictModel;
|
import org.jeecg.common.system.vo.DictModel;
|
||||||
import org.jeecg.common.util.SpringContextUtils;
|
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||||
@ -13,6 +11,7 @@ import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
|||||||
import org.springframework.core.type.classreading.MetadataReader;
|
import org.springframework.core.type.classreading.MetadataReader;
|
||||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@ -67,13 +66,13 @@ public class ResourceUtil {
|
|||||||
synchronized (ResourceUtil.class) {
|
synchronized (ResourceUtil.class) {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
log.info("【枚举字典加载】开始初始化枚举字典数据...");
|
log.debug("【枚举字典加载】开始初始化枚举字典数据...");
|
||||||
|
|
||||||
initEnumDictData();
|
initEnumDictData();
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
long endTime = System.currentTimeMillis();
|
long endTime = System.currentTimeMillis();
|
||||||
log.info("【枚举字典加载】枚举字典数据初始化完成,共加载 {} 个字典,总耗时: {}ms", enumDictData.size(), endTime - startTime);
|
log.debug("【枚举字典加载】枚举字典数据初始化完成,共加载 {} 个字典,总耗时: {}ms", enumDictData.size(), endTime - startTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,7 +102,7 @@ public class ResourceUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long scanEndTime = System.currentTimeMillis();
|
long scanEndTime = System.currentTimeMillis();
|
||||||
log.info("【枚举字典加载】文件扫描完成,总共找到 {} 个枚举类文件,扫描耗时: {}ms", allResources.size(), scanEndTime - scanStartTime);
|
log.debug("【枚举字典加载】文件扫描完成,总共找到 {} 个枚举类文件,扫描耗时: {}ms", allResources.size(), scanEndTime - scanStartTime);
|
||||||
|
|
||||||
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
|
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
|
||||||
|
|
||||||
@ -126,7 +125,7 @@ public class ResourceUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long processEndTime = System.currentTimeMillis();
|
long processEndTime = System.currentTimeMillis();
|
||||||
log.info("【枚举字典加载】处理完成,实际处理 {} 个带注解的枚举类,处理耗时: {}ms", processedCount, processEndTime - processStartTime);
|
log.debug("【枚举字典加载】处理完成,实际处理 {} 个带注解的枚举类,处理耗时: {}ms", processedCount, processEndTime - processStartTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -183,10 +182,10 @@ public class ResourceUtil {
|
|||||||
for (DictModel dm : dictItemList) {
|
for (DictModel dm : dictItemList) {
|
||||||
String value = dm.getValue();
|
String value = dm.getValue();
|
||||||
if (keySet.contains(value)) {
|
if (keySet.contains(value)) {
|
||||||
List<DictModel> list = new ArrayList<>();
|
// 修复bug:获取或创建该dictCode对应的list,而不是每次都创建新的list
|
||||||
|
List<DictModel> list = map.computeIfAbsent(code, k -> new ArrayList<>());
|
||||||
list.add(new DictModel(value, dm.getText()));
|
list.add(new DictModel(value, dm.getText()));
|
||||||
map.put(code, list);
|
//break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -150,7 +150,7 @@ public class SqlConcatUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String getInConditionValue(Object value,boolean isString) {
|
private static String getInConditionValue(Object value,boolean isString) {
|
||||||
//update-begin-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
|
// 代码逻辑说明: 查询条件如果输入,导致sql报错
|
||||||
String[] temp = value.toString().split(",");
|
String[] temp = value.toString().split(",");
|
||||||
if(temp.length==0){
|
if(temp.length==0){
|
||||||
return "('')";
|
return "('')";
|
||||||
@ -168,7 +168,6 @@ public class SqlConcatUtil {
|
|||||||
}else {
|
}else {
|
||||||
return "("+value.toString()+")";
|
return "("+value.toString()+")";
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -215,7 +214,6 @@ public class SqlConcatUtil {
|
|||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
|
|
||||||
//update-begin-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
|
|
||||||
// 走到这里说明 value不带有任何模糊查询的标识(*或者%)
|
// 走到这里说明 value不带有任何模糊查询的标识(*或者%)
|
||||||
if (ruleEnum == QueryRuleEnum.LEFT_LIKE) {
|
if (ruleEnum == QueryRuleEnum.LEFT_LIKE) {
|
||||||
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
|
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
|
||||||
@ -236,7 +234,6 @@ public class SqlConcatUtil {
|
|||||||
return "'%" + str + "%'";
|
return "'%" + str + "%'";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,9 @@ public class CommonUtils {
|
|||||||
public static String uploadOnlineImage(byte[] data,String basePath,String bizPath,String uploadType){
|
public static String uploadOnlineImage(byte[] data,String basePath,String bizPath,String uploadType){
|
||||||
String dbPath = null;
|
String dbPath = null;
|
||||||
String fileName = "image" + Math.round(Math.random() * 100000000000L);
|
String fileName = "image" + Math.round(Math.random() * 100000000000L);
|
||||||
fileName += "." + PoiPublicUtil.getFileExtendName(data);
|
//update-begin---author:wangshuai---date:2026-01-08---for:【QQYUN-14535】ai生成图片的后缀不一致的,导致不展示---
|
||||||
|
fileName += "." + PoiPublicUtil.getFileExtendName(data).toLowerCase();
|
||||||
|
//update-end---author:wangshuai---date:2026-01-08---for:【QQYUN-14535】ai生成图片的后缀不一致的,导致不展示---
|
||||||
try {
|
try {
|
||||||
if(CommonConstant.UPLOAD_TYPE_LOCAL.equals(uploadType)){
|
if(CommonConstant.UPLOAD_TYPE_LOCAL.equals(uploadType)){
|
||||||
File file = new File(basePath + File.separator + bizPath + File.separator );
|
File file = new File(basePath + File.separator + bizPath + File.separator );
|
||||||
|
|||||||
@ -10,7 +10,9 @@ import com.aliyuncs.exceptions.ClientException;
|
|||||||
import com.aliyuncs.profile.DefaultProfile;
|
import com.aliyuncs.profile.DefaultProfile;
|
||||||
import com.aliyuncs.profile.IClientProfile;
|
import com.aliyuncs.profile.IClientProfile;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.jeecg.common.constant.enums.DySmsEnum;
|
import org.jeecg.common.constant.enums.DySmsEnum;
|
||||||
|
import org.jeecg.config.JeecgBaseConfig;
|
||||||
import org.jeecg.config.JeecgSmsTemplateConfig;
|
import org.jeecg.config.JeecgSmsTemplateConfig;
|
||||||
import org.jeecg.config.StaticConfig;
|
import org.jeecg.config.StaticConfig;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -61,17 +63,21 @@ public class DySmsHelper {
|
|||||||
|
|
||||||
|
|
||||||
public static boolean sendSms(String phone, JSONObject templateParamJson, DySmsEnum dySmsEnum) throws ClientException {
|
public static boolean sendSms(String phone, JSONObject templateParamJson, DySmsEnum dySmsEnum) throws ClientException {
|
||||||
//可自助调整超时时间
|
JeecgBaseConfig config = SpringContextUtils.getBean(JeecgBaseConfig.class);
|
||||||
|
String smsSendType = config.getSmsSendType();
|
||||||
|
if(oConvertUtils.isNotEmpty(smsSendType) && CommonConstant.SMS_SEND_TYPE_TENCENT.equals(smsSendType)){
|
||||||
|
return TencentSms.sendTencentSms(phone, templateParamJson, config.getTencent(), dySmsEnum);
|
||||||
|
}
|
||||||
|
//可自助调整超时时间
|
||||||
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
|
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
|
||||||
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
|
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
|
||||||
|
|
||||||
//update-begin-author:taoyan date:20200811 for:配置类数据获取
|
// 代码逻辑说明: 配置类数据获取
|
||||||
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
|
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
|
||||||
//logger.info("阿里大鱼短信秘钥 accessKeyId:" + staticConfig.getAccessKeyId());
|
//logger.info("阿里大鱼短信秘钥 accessKeyId:" + staticConfig.getAccessKeyId());
|
||||||
//logger.info("阿里大鱼短信秘钥 accessKeySecret:"+ staticConfig.getAccessKeySecret());
|
//logger.info("阿里大鱼短信秘钥 accessKeySecret:"+ staticConfig.getAccessKeySecret());
|
||||||
setAccessKeyId(staticConfig.getAccessKeyId());
|
setAccessKeyId(staticConfig.getAccessKeyId());
|
||||||
setAccessKeySecret(staticConfig.getAccessKeySecret());
|
setAccessKeySecret(staticConfig.getAccessKeySecret());
|
||||||
//update-end-author:taoyan date:20200811 for:配置类数据获取
|
|
||||||
|
|
||||||
//初始化acsClient,暂不支持region化
|
//初始化acsClient,暂不支持region化
|
||||||
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
|
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
|
||||||
@ -81,7 +87,7 @@ public class DySmsHelper {
|
|||||||
//验证json参数
|
//验证json参数
|
||||||
validateParam(templateParamJson,dySmsEnum);
|
validateParam(templateParamJson,dySmsEnum);
|
||||||
|
|
||||||
//update-begin---author:wangshuai---date:2024-11-05---for:【QQYUN-9422】短信模板管理,阿里云---
|
// 代码逻辑说明: 【QQYUN-9422】短信模板管理,阿里云---
|
||||||
String templateCode = dySmsEnum.getTemplateCode();
|
String templateCode = dySmsEnum.getTemplateCode();
|
||||||
JeecgSmsTemplateConfig baseConfig = SpringContextUtils.getBean(JeecgSmsTemplateConfig.class);
|
JeecgSmsTemplateConfig baseConfig = SpringContextUtils.getBean(JeecgSmsTemplateConfig.class);
|
||||||
if(baseConfig != null && CollectionUtil.isNotEmpty(baseConfig.getTemplateCode())){
|
if(baseConfig != null && CollectionUtil.isNotEmpty(baseConfig.getTemplateCode())){
|
||||||
@ -97,7 +103,6 @@ public class DySmsHelper {
|
|||||||
logger.info("yml中读取签名名称{}",baseConfig.getSignature());
|
logger.info("yml中读取签名名称{}",baseConfig.getSignature());
|
||||||
signName = baseConfig.getSignature();
|
signName = baseConfig.getSignature();
|
||||||
}
|
}
|
||||||
//update-end---author:wangshuai---date:2024-11-05---for:【QQYUN-9422】短信模板管理,阿里云---
|
|
||||||
|
|
||||||
//组装请求对象-具体描述见控制台-文档部分内容
|
//组装请求对象-具体描述见控制台-文档部分内容
|
||||||
SendSmsRequest request = new SendSmsRequest();
|
SendSmsRequest request = new SendSmsRequest();
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package org.jeecg.common.util;
|
package org.jeecg.common.util;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||||
|
|||||||
@ -38,14 +38,12 @@ public class HTMLUtils {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static String parseMarkdown(String markdownContent) {
|
public static String parseMarkdown(String markdownContent) {
|
||||||
//update-begin---author:wangshuai---date:2024-06-26---for:【TV360X-1344】JDK17 邮箱发送失败,需要换写法---
|
|
||||||
/*PegDownProcessor pdp = new PegDownProcessor();
|
/*PegDownProcessor pdp = new PegDownProcessor();
|
||||||
return pdp.markdownToHtml(markdownContent);*/
|
return pdp.markdownToHtml(markdownContent);*/
|
||||||
Parser parser = Parser.builder().build();
|
Parser parser = Parser.builder().build();
|
||||||
Node document = parser.parse(markdownContent);
|
Node document = parser.parse(markdownContent);
|
||||||
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||||
return renderer.render(document);
|
return renderer.render(document);
|
||||||
//update-end---author:wangshuai---date:2024-06-26---for:【TV360X-1344】JDK17 邮箱发送失败,需要换写法---
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -161,11 +161,10 @@ public class MinioUtil {
|
|||||||
public static String getObjectUrl(String bucketName, String objectName, Integer expires) {
|
public static String getObjectUrl(String bucketName, String objectName, Integer expires) {
|
||||||
initMinio(minioUrl, minioName,minioPass);
|
initMinio(minioUrl, minioName,minioPass);
|
||||||
try{
|
try{
|
||||||
//update-begin---author:liusq Date:20220121 for:获取文件外链报错提示method不能为空,导致文件下载和预览失败----
|
// 代码逻辑说明: 获取文件外链报错提示method不能为空,导致文件下载和预览失败----
|
||||||
GetPresignedObjectUrlArgs objectArgs = GetPresignedObjectUrlArgs.builder().object(objectName)
|
GetPresignedObjectUrlArgs objectArgs = GetPresignedObjectUrlArgs.builder().object(objectName)
|
||||||
.bucket(bucketName)
|
.bucket(bucketName)
|
||||||
.expiry(expires).method(Method.GET).build();
|
.expiry(expires).method(Method.GET).build();
|
||||||
//update-begin---author:liusq Date:20220121 for:获取文件外链报错提示method不能为空,导致文件下载和预览失败----
|
|
||||||
String url = minioClient.getPresignedObjectUrl(objectArgs);
|
String url = minioClient.getPresignedObjectUrl(objectArgs);
|
||||||
return URLDecoder.decode(url,"UTF-8");
|
return URLDecoder.decode(url,"UTF-8");
|
||||||
}catch (Exception e){
|
}catch (Exception e){
|
||||||
|
|||||||
@ -0,0 +1,78 @@
|
|||||||
|
package org.jeecg.common.util;
|
||||||
|
|
||||||
|
import org.apache.commons.fileupload.FileItem;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @date 2025-09-04
|
||||||
|
* @author scott
|
||||||
|
*
|
||||||
|
* 升级springboot3 无法使用 CommonsMultipartFile
|
||||||
|
* 自定义 MultipartFile 实现类,支持从 FileItem 构造
|
||||||
|
*/
|
||||||
|
public class MyCommonsMultipartFile implements MultipartFile {
|
||||||
|
|
||||||
|
private final byte[] fileContent;
|
||||||
|
private final String fileName;
|
||||||
|
private final String contentType;
|
||||||
|
|
||||||
|
// 新增构造方法,支持 FileItem 参数
|
||||||
|
public MyCommonsMultipartFile(FileItem fileItem) throws IOException {
|
||||||
|
this.fileName = fileItem.getName();
|
||||||
|
this.contentType = fileItem.getContentType();
|
||||||
|
try (InputStream inputStream = fileItem.getInputStream()) {
|
||||||
|
this.fileContent = inputStream.readAllBytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 现有构造方法
|
||||||
|
public MyCommonsMultipartFile(InputStream inputStream, String fileName, String contentType) throws IOException {
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.contentType = contentType;
|
||||||
|
this.fileContent = inputStream.readAllBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOriginalFilename() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return fileContent.length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSize() {
|
||||||
|
return fileContent.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBytes() {
|
||||||
|
return fileContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() {
|
||||||
|
return new ByteArrayInputStream(fileContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transferTo(File dest) throws IOException {
|
||||||
|
try (OutputStream os = new FileOutputStream(dest)) {
|
||||||
|
os.write(fileContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -99,9 +99,8 @@ public class PasswordUtil {
|
|||||||
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||||
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
|
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
|
||||||
//update-begin-author:sccott date:20180815 for:中文作为用户名时,加密的密码windows和linux会得到不同的结果 gitee/issues/IZUD7
|
// 代码逻辑说明: 中文作为用户名时,加密的密码windows和linux会得到不同的结果 gitee/issues/IZUD7
|
||||||
encipheredData = cipher.doFinal(plaintext.getBytes("utf-8"));
|
encipheredData = cipher.doFinal(plaintext.getBytes("utf-8"));
|
||||||
//update-end-author:sccott date:20180815 for:中文作为用户名时,加密的密码windows和linux会得到不同的结果 gitee/issues/IZUD7
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
}
|
}
|
||||||
return bytesToHexString(encipheredData);
|
return bytesToHexString(encipheredData);
|
||||||
|
|||||||
@ -46,7 +46,7 @@ public class RestUtil {
|
|||||||
|
|
||||||
public static String getBaseUrl() {
|
public static String getBaseUrl() {
|
||||||
String basepath = getDomain() + getPath();
|
String basepath = getDomain() + getPath();
|
||||||
log.info(" RestUtil.getBaseUrl: " + basepath);
|
log.debug(" RestUtil.getBaseUrl: " + basepath);
|
||||||
return basepath;
|
return basepath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,14 +56,12 @@ public class RestUtil {
|
|||||||
private final static RestTemplate RT;
|
private final static RestTemplate RT;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
//update-begin---author:chenrui ---date:20251011 for:[issues/8859]online表单java增强失效------------
|
// 解决[issues/8859]online表单java增强失效------------
|
||||||
// 使用 Apache HttpClient 避免 JDK HttpURLConnection 的 too many bytes written 问题
|
// 使用 Apache HttpClient 避免 JDK HttpURLConnection 的 too many bytes written 问题
|
||||||
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
|
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
|
||||||
//update-end---author:chenrui ---date:20251011 for:[issues/8859]online表单java增强失效------------
|
|
||||||
requestFactory.setConnectTimeout(30000);
|
requestFactory.setConnectTimeout(30000);
|
||||||
requestFactory.setReadTimeout(30000);
|
requestFactory.setReadTimeout(30000);
|
||||||
RT = new RestTemplate(requestFactory);
|
RT = new RestTemplate(requestFactory);
|
||||||
//update-begin---author:chenrui ---date:20251011 for:[issues/8859]online表单java增强失效------------
|
|
||||||
// 解决乱码问题(替换 StringHttpMessageConverter 为 UTF-8)
|
// 解决乱码问题(替换 StringHttpMessageConverter 为 UTF-8)
|
||||||
for (int i = 0; i < RT.getMessageConverters().size(); i++) {
|
for (int i = 0; i < RT.getMessageConverters().size(); i++) {
|
||||||
if (RT.getMessageConverters().get(i) instanceof StringHttpMessageConverter) {
|
if (RT.getMessageConverters().get(i) instanceof StringHttpMessageConverter) {
|
||||||
@ -71,7 +69,6 @@ public class RestUtil {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-end---author:chenrui ---date:20251011 for:[issues/8859]online表单java增强失效------------
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RestTemplate getRestTemplate() {
|
public static RestTemplate getRestTemplate() {
|
||||||
@ -202,7 +199,7 @@ public class RestUtil {
|
|||||||
* @return ResponseEntity<responseType>
|
* @return ResponseEntity<responseType>
|
||||||
*/
|
*/
|
||||||
public static <T> ResponseEntity<T> request(String url, HttpMethod method, HttpHeaders headers, JSONObject variables, Object params, Class<T> responseType) {
|
public static <T> ResponseEntity<T> request(String url, HttpMethod method, HttpHeaders headers, JSONObject variables, Object params, Class<T> responseType) {
|
||||||
log.info(" RestUtil --- request --- url = "+ url);
|
log.debug(" RestUtil --- request --- url = "+ url);
|
||||||
if (StringUtils.isEmpty(url)) {
|
if (StringUtils.isEmpty(url)) {
|
||||||
throw new RuntimeException("url 不能为空");
|
throw new RuntimeException("url 不能为空");
|
||||||
}
|
}
|
||||||
@ -226,6 +223,16 @@ public class RestUtil {
|
|||||||
if (variables != null && !variables.isEmpty()) {
|
if (variables != null && !variables.isEmpty()) {
|
||||||
url += ("?" + asUrlVariables(variables));
|
url += ("?" + asUrlVariables(variables));
|
||||||
}
|
}
|
||||||
|
// 解决[issues/8951]从jeecgboot 3.8.2 升级到 3.8.3 在线表单java增强功能报错------------
|
||||||
|
// Content-Length 强制设置(解决可能出现的截断问题)
|
||||||
|
if (StringUtils.isNotEmpty(body)) {
|
||||||
|
int contentLength = body.getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
String current = headers.getFirst(HttpHeaders.CONTENT_LENGTH);
|
||||||
|
if (current == null || !current.equals(String.valueOf(contentLength))) {
|
||||||
|
headers.setContentLength(contentLength);
|
||||||
|
log.debug(" RestUtil --- request --- 修正/设置 Content-Length = " + contentLength + (current!=null?" (原值="+current+")":""));
|
||||||
|
}
|
||||||
|
}
|
||||||
// 发送请求
|
// 发送请求
|
||||||
HttpEntity<String> request = new HttpEntity<>(body, headers);
|
HttpEntity<String> request = new HttpEntity<>(body, headers);
|
||||||
return RT.exchange(url, method, request, responseType);
|
return RT.exchange(url, method, request, responseType);
|
||||||
@ -245,7 +252,7 @@ public class RestUtil {
|
|||||||
*/
|
*/
|
||||||
public static <T> ResponseEntity<T> request(String url, HttpMethod method, HttpHeaders headers,
|
public static <T> ResponseEntity<T> request(String url, HttpMethod method, HttpHeaders headers,
|
||||||
JSONObject variables, Object params, Class<T> responseType, int timeout) {
|
JSONObject variables, Object params, Class<T> responseType, int timeout) {
|
||||||
log.info(" RestUtil --- request --- url = "+ url + ", timeout = " + timeout);
|
log.debug(" RestUtil --- request --- url = "+ url + ", timeout = " + timeout);
|
||||||
|
|
||||||
if (StringUtils.isEmpty(url)) {
|
if (StringUtils.isEmpty(url)) {
|
||||||
throw new RuntimeException("url 不能为空");
|
throw new RuntimeException("url 不能为空");
|
||||||
@ -260,13 +267,11 @@ public class RestUtil {
|
|||||||
// 创建自定义RestTemplate(如果需要设置超时)
|
// 创建自定义RestTemplate(如果需要设置超时)
|
||||||
RestTemplate restTemplate = RT;
|
RestTemplate restTemplate = RT;
|
||||||
if (timeout > 0) {
|
if (timeout > 0) {
|
||||||
//update-begin---author:chenrui ---date:20251011 for:[issues/8859]online表单java增强失效------------
|
// 代码逻辑说明: [issues/8859]online表单java增强失效------------
|
||||||
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
|
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
|
||||||
//update-end---author:chenrui ---date:20251011 for:[issues/8859]online表单java增强失效------------
|
|
||||||
requestFactory.setConnectTimeout(timeout);
|
requestFactory.setConnectTimeout(timeout);
|
||||||
requestFactory.setReadTimeout(timeout);
|
requestFactory.setReadTimeout(timeout);
|
||||||
restTemplate = new RestTemplate(requestFactory);
|
restTemplate = new RestTemplate(requestFactory);
|
||||||
//update-begin---author:chenrui ---date:20251011 for:[issues/8859]online表单java增强失效------------
|
|
||||||
// 解决乱码问题(替换 StringHttpMessageConverter 为 UTF-8)
|
// 解决乱码问题(替换 StringHttpMessageConverter 为 UTF-8)
|
||||||
for (int i = 0; i < restTemplate.getMessageConverters().size(); i++) {
|
for (int i = 0; i < restTemplate.getMessageConverters().size(); i++) {
|
||||||
if (restTemplate.getMessageConverters().get(i) instanceof StringHttpMessageConverter) {
|
if (restTemplate.getMessageConverters().get(i) instanceof StringHttpMessageConverter) {
|
||||||
@ -274,7 +279,6 @@ public class RestUtil {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-end---author:chenrui ---date:20251011 for:[issues/8859]online表单java增强失效------------
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求体
|
// 请求体
|
||||||
@ -292,11 +296,21 @@ public class RestUtil {
|
|||||||
url += ("?" + asUrlVariables(variables));
|
url += ("?" + asUrlVariables(variables));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content-Length 强制设置(解决可能出现的截断问题)
|
||||||
|
if (StringUtils.isNotEmpty(body) && !headers.containsKey(HttpHeaders.CONTENT_LENGTH)) {
|
||||||
|
int contentLength = body.getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
String current = headers.getFirst(HttpHeaders.CONTENT_LENGTH);
|
||||||
|
if (current == null || !current.equals(String.valueOf(contentLength))) {
|
||||||
|
headers.setContentLength(contentLength);
|
||||||
|
log.debug(" RestUtil --- request(timeout) --- 修正/设置 Content-Length = " + contentLength + (current!=null?" (原值="+current+")":""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 发送请求
|
// 发送请求
|
||||||
HttpEntity<String> request = new HttpEntity<>(body, headers);
|
HttpEntity<String> request = new HttpEntity<>(body, headers);
|
||||||
return restTemplate.exchange(url, method, request, responseType);
|
return restTemplate.exchange(url, method, request, responseType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取JSON请求头
|
* 获取JSON请求头
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -335,13 +335,12 @@ public class SqlInjectionUtil {
|
|||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin---author:scott ---date:2024-05-28 for:表单设计器列表翻译存在表名带条件,导致翻译出问题----
|
// 代码逻辑说明: 表单设计器列表翻译存在表名带条件,导致翻译出问题----
|
||||||
int index = table.toLowerCase().indexOf(" where ");
|
int index = table.toLowerCase().indexOf(" where ");
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
table = table.substring(0, index);
|
table = table.substring(0, index);
|
||||||
log.info("截掉where之后的新表名:" + table);
|
log.info("截掉where之后的新表名:" + table);
|
||||||
}
|
}
|
||||||
//update-end---author:scott ---date::2024-05-28 for:表单设计器列表翻译存在表名带条件,导致翻译出问题----
|
|
||||||
|
|
||||||
table = table.trim();
|
table = table.trim();
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -0,0 +1,184 @@
|
|||||||
|
package org.jeecg.common.util;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.tencentcloudapi.common.Credential;
|
||||||
|
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
|
||||||
|
import com.tencentcloudapi.common.profile.ClientProfile;
|
||||||
|
import com.tencentcloudapi.common.profile.HttpProfile;
|
||||||
|
import com.tencentcloudapi.sms.v20210111.SmsClient;
|
||||||
|
import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
|
||||||
|
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
|
||||||
|
import com.tencentcloudapi.sms.v20210111.models.SendStatus;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.jeecg.common.constant.enums.DySmsEnum;
|
||||||
|
import org.jeecg.config.JeecgSmsTemplateConfig;
|
||||||
|
import org.jeecg.config.tencent.JeecgTencent;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 腾讯发送短信
|
||||||
|
* @author: wangshuai
|
||||||
|
* @date: 2025/11/4 19:27
|
||||||
|
*/
|
||||||
|
public class TencentSms {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(TencentSms.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送腾讯短信
|
||||||
|
*
|
||||||
|
* @param phone
|
||||||
|
* @param templateParamJson
|
||||||
|
* @param tencent
|
||||||
|
* @param dySmsEnum
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean sendTencentSms(String phone, JSONObject templateParamJson, JeecgTencent tencent, DySmsEnum dySmsEnum) {
|
||||||
|
//获取客户端链接
|
||||||
|
SmsClient client = getSmsClient(tencent);
|
||||||
|
//构建腾讯云短信发送请求
|
||||||
|
SendSmsRequest req = buildSendSmsRequest(phone, templateParamJson, dySmsEnum, tencent);
|
||||||
|
try {
|
||||||
|
//发送短信
|
||||||
|
SendSmsResponse resp = client.SendSms(req);
|
||||||
|
// 处理响应
|
||||||
|
SendStatus[] statusSet = resp.getSendStatusSet();
|
||||||
|
if (statusSet != null && statusSet.length > 0) {
|
||||||
|
SendStatus status = statusSet[0];
|
||||||
|
if ("Ok".equals(status.getCode())) {
|
||||||
|
logger.info("短信发送成功,手机号:{}", phone);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
logger.error("短信发送失败,手机号:{},错误码:{},错误信息:{}",
|
||||||
|
phone, status.getCode(), status.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (TencentCloudSDKException e) {
|
||||||
|
logger.error("短信发送失败{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取sms客户端
|
||||||
|
*
|
||||||
|
* @param tencent 腾讯云配置
|
||||||
|
* @return SmsClient对象
|
||||||
|
*/
|
||||||
|
private static SmsClient getSmsClient(JeecgTencent tencent) {
|
||||||
|
Credential cred = new Credential(tencent.getSecretId(), tencent.getSecretKey());
|
||||||
|
// 实例化一个http选项,可选的,没有特殊需求可以跳过
|
||||||
|
HttpProfile httpProfile = new HttpProfile();
|
||||||
|
//指定接入地域域名*/
|
||||||
|
httpProfile.setEndpoint(tencent.getEndpoint());
|
||||||
|
//实例化一个客户端配置对象
|
||||||
|
ClientProfile clientProfile = new ClientProfile();
|
||||||
|
clientProfile.setHttpProfile(httpProfile);
|
||||||
|
//实例化要请求产品的client对象,第二个参数是地域信息
|
||||||
|
return new SmsClient(cred, tencent.getRegion(), clientProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建腾讯云短信发送请求
|
||||||
|
*
|
||||||
|
* @param phone 手机号码
|
||||||
|
* @param templateParamJson 模板参数JSON对象
|
||||||
|
* @param dySmsEnum 短信枚举配置
|
||||||
|
* @param tencent 腾讯云配置
|
||||||
|
* @return 构建好的SendSmsRequest对象
|
||||||
|
*/
|
||||||
|
private static SendSmsRequest buildSendSmsRequest(
|
||||||
|
String phone,
|
||||||
|
JSONObject templateParamJson,
|
||||||
|
DySmsEnum dySmsEnum,
|
||||||
|
JeecgTencent tencent) {
|
||||||
|
|
||||||
|
SendSmsRequest req = new SendSmsRequest();
|
||||||
|
|
||||||
|
// 1. 设置短信应用ID
|
||||||
|
String sdkAppId = tencent.getSdkAppId();
|
||||||
|
req.setSmsSdkAppId(sdkAppId);
|
||||||
|
// 2. 设置短信签名
|
||||||
|
String signName = getSmsSignName(dySmsEnum);
|
||||||
|
req.setSignName(signName);
|
||||||
|
// 3. 设置模板ID
|
||||||
|
String templateId = getSmsTemplateId(dySmsEnum);
|
||||||
|
req.setTemplateId(templateId);
|
||||||
|
// 4. 设置模板参数
|
||||||
|
String[] templateParams = extractTemplateParams(templateParamJson);
|
||||||
|
req.setTemplateParamSet(templateParams);
|
||||||
|
// 5. 设置手机号码
|
||||||
|
String[] phoneNumberSet = { phone };
|
||||||
|
req.setPhoneNumberSet(phoneNumberSet);
|
||||||
|
|
||||||
|
logger.debug("构建短信请求完成 - 应用ID: {}, 签名: {}, 模板ID: {}, 手机号: {}",
|
||||||
|
sdkAppId, signName, templateId, phone);
|
||||||
|
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取短信签名名称
|
||||||
|
*
|
||||||
|
* @param dySmsEnum 腾讯云对象
|
||||||
|
*/
|
||||||
|
private static String getSmsSignName(DySmsEnum dySmsEnum) {
|
||||||
|
JeecgSmsTemplateConfig baseConfig = SpringContextUtils.getBean(JeecgSmsTemplateConfig.class);
|
||||||
|
String signName = dySmsEnum.getSignName();
|
||||||
|
|
||||||
|
if (StringUtils.isNotEmpty(baseConfig.getSignature())) {
|
||||||
|
logger.debug("yml中读取签名名称: {}", baseConfig.getSignature());
|
||||||
|
signName = baseConfig.getSignature();
|
||||||
|
}
|
||||||
|
|
||||||
|
return signName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取短信模板ID
|
||||||
|
*
|
||||||
|
* @param dySmsEnum 腾讯云对象
|
||||||
|
*/
|
||||||
|
private static String getSmsTemplateId(DySmsEnum dySmsEnum) {
|
||||||
|
JeecgSmsTemplateConfig baseConfig = SpringContextUtils.getBean(JeecgSmsTemplateConfig.class);
|
||||||
|
String templateCode = dySmsEnum.getTemplateCode();
|
||||||
|
|
||||||
|
if (StringUtils.isNotEmpty(baseConfig.getSignature())) {
|
||||||
|
Map<String, String> smsTemplate = baseConfig.getTemplateCode();
|
||||||
|
if (smsTemplate.containsKey(templateCode) &&
|
||||||
|
StringUtils.isNotEmpty(smsTemplate.get(templateCode))) {
|
||||||
|
templateCode = smsTemplate.get(templateCode);
|
||||||
|
logger.debug("yml中读取短信模板ID: {}", templateCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return templateCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从JSONObject中提取模板参数(按原始顺序)
|
||||||
|
*
|
||||||
|
* @param templateParamJson 模板参数
|
||||||
|
*/
|
||||||
|
private static String[] extractTemplateParams(JSONObject templateParamJson) {
|
||||||
|
if (templateParamJson == null || templateParamJson.isEmpty()) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
List<String> params = new ArrayList<>();
|
||||||
|
for (String key : templateParamJson.keySet()) {
|
||||||
|
Object value = templateParamJson.get(key);
|
||||||
|
if (value != null) {
|
||||||
|
params.add(value.toString());
|
||||||
|
} else {
|
||||||
|
// 处理null值
|
||||||
|
params.add("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("提取模板参数: {}", params);
|
||||||
|
return params.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -121,7 +121,9 @@ public class TokenUtils {
|
|||||||
}
|
}
|
||||||
// 校验token是否超时失效 & 或者账号密码是否错误
|
// 校验token是否超时失效 & 或者账号密码是否错误
|
||||||
if (!jwtTokenRefresh(token, username, user.getPassword(), redisUtil)) {
|
if (!jwtTokenRefresh(token, username, user.getPassword(), redisUtil)) {
|
||||||
throw new JeecgBoot401Exception(CommonConstant.TOKEN_IS_INVALID_MSG);
|
// 用户登录Token过期提示信息
|
||||||
|
String userLoginTokenErrorMsg = oConvertUtils.getString(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN_ERROR_MSG + token));
|
||||||
|
throw new JeecgBoot401Exception(oConvertUtils.isEmpty(userLoginTokenErrorMsg)? CommonConstant.TOKEN_IS_INVALID_MSG: userLoginTokenErrorMsg);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -139,10 +141,15 @@ public class TokenUtils {
|
|||||||
if (oConvertUtils.isNotEmpty(cacheToken)) {
|
if (oConvertUtils.isNotEmpty(cacheToken)) {
|
||||||
// 校验token有效性
|
// 校验token有效性
|
||||||
if (!JwtUtil.verify(cacheToken, userName, passWord)) {
|
if (!JwtUtil.verify(cacheToken, userName, passWord)) {
|
||||||
String newAuthorization = JwtUtil.sign(userName, passWord);
|
// 从token中解析客户端类型,保持续期时使用相同的客户端类型
|
||||||
// 设置Toekn缓存有效时间
|
String clientType = JwtUtil.getClientType(token);
|
||||||
|
String newAuthorization = JwtUtil.sign(userName, passWord, clientType);
|
||||||
|
// 根据客户端类型设置对应的缓存有效时间
|
||||||
|
long expireTime = CommonConstant.CLIENT_TYPE_APP.equalsIgnoreCase(clientType)
|
||||||
|
? JwtUtil.APP_EXPIRE_TIME * 2 / 1000
|
||||||
|
: JwtUtil.EXPIRE_TIME * 2 / 1000;
|
||||||
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
|
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
|
||||||
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME * 2 / 1000);
|
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, expireTime);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,11 +54,10 @@ public class FreemarkerParseFactory {
|
|||||||
//classic_compatible设置,解决报空指针错误
|
//classic_compatible设置,解决报空指针错误
|
||||||
SQL_CONFIG.setClassicCompatible(true);
|
SQL_CONFIG.setClassicCompatible(true);
|
||||||
|
|
||||||
//update-begin-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime。
|
// 解决freemarker模板注入问题 禁止解析ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime。
|
||||||
//https://ackcent.com/in-depth-freemarker-template-injection/
|
//https://ackcent.com/in-depth-freemarker-template-injection/
|
||||||
TPL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
|
TPL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
|
||||||
SQL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
|
SQL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
|
||||||
//update-end-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime。
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,13 +72,12 @@ public class FreemarkerParseFactory {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
//update-begin--Author:scott Date:20180320 for:解决问题 - 错误提示sql文件不存在,实际问题是sql freemarker用法错误-----
|
// 代码逻辑说明: 解决问题 - 错误提示sql文件不存在,实际问题是sql freemarker用法错误-----
|
||||||
if (e instanceof ParseException) {
|
if (e instanceof ParseException) {
|
||||||
log.error(e.getMessage(), e.fillInStackTrace());
|
log.error(e.getMessage(), e.fillInStackTrace());
|
||||||
throw new Exception(e);
|
throw new Exception(e);
|
||||||
}
|
}
|
||||||
log.debug("----isExistTemplate----" + e.toString());
|
log.debug("----isExistTemplate----" + e.toString());
|
||||||
//update-end--Author:scott Date:20180320 for:解决问题 - 错误提示sql文件不存在,实际问题是sql freemarker用法错误------
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -1,123 +1,107 @@
|
|||||||
package org.jeecg.common.util.encryption;
|
package org.jeecg.common.util.encryption;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.shiro.lang.codec.Base64;
|
import org.apache.shiro.lang.codec.Base64;
|
||||||
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: AES 加密
|
* AES 工具 (兼容历史 NoPadding + 新 PKCS5Padding)
|
||||||
* @author: jeecg-boot
|
|
||||||
* @date: 2022/3/30 11:48
|
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class AesEncryptUtil {
|
public class AesEncryptUtil {
|
||||||
|
|
||||||
/**
|
private static final String KEY = EncryptedString.key;
|
||||||
* 使用AES-128-CBC加密模式 key和iv可以相同
|
private static final String IV = EncryptedString.iv;
|
||||||
*/
|
|
||||||
private static String KEY = EncryptedString.key;
|
|
||||||
private static String IV = EncryptedString.iv;
|
|
||||||
|
|
||||||
/**
|
/* -------- 新版:CBC + PKCS5Padding (与前端 CryptoJS Pkcs7 兼容) -------- */
|
||||||
* 加密方法
|
private static String decryptPkcs5(String cipherBase64) throws Exception {
|
||||||
* @param data 要加密的数据
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
* @param key 加密key
|
SecretKeySpec ks = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), "AES");
|
||||||
* @param iv 加密iv
|
IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));
|
||||||
* @return 加密的结果
|
cipher.init(Cipher.DECRYPT_MODE, ks, ivSpec);
|
||||||
* @throws Exception
|
byte[] plain = cipher.doFinal(Base64.decode(cipherBase64));
|
||||||
*/
|
return new String(plain, StandardCharsets.UTF_8);
|
||||||
public static String encrypt(String data, String key, String iv) throws Exception {
|
}
|
||||||
try {
|
|
||||||
|
|
||||||
//"算法/模式/补码方式"NoPadding PkcsPadding
|
/* -------- 旧版:CBC + NoPadding (手工补 0) -------- */
|
||||||
|
private static String decryptLegacyNoPadding(String cipherBase64) throws Exception {
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||||
|
SecretKeySpec ks = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), "AES");
|
||||||
|
IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, ks, ivSpec);
|
||||||
|
byte[] data = cipher.doFinal(Base64.decode(cipherBase64));
|
||||||
|
return new String(data, StandardCharsets.UTF_8)
|
||||||
|
.replace("\u0000",""); // 旧填充 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------- 兼容入口:登录使用 -------- */
|
||||||
|
public static String resolvePassword(String input){
|
||||||
|
if(oConvertUtils.isEmpty(input)){
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
// 1. 先尝试新版
|
||||||
|
try{
|
||||||
|
String p = decryptPkcs5(input);
|
||||||
|
return clean(p);
|
||||||
|
}catch(Exception ignore){
|
||||||
|
//log.debug("【AES解密】Password not AES PKCS5 cipher, try legacy.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 回退旧版
|
||||||
|
try{
|
||||||
|
String legacy = decryptLegacyNoPadding(input);
|
||||||
|
return clean(legacy);
|
||||||
|
}catch(Exception e){
|
||||||
|
log.debug("【AES解密】Password not AES cipher, raw used.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 视为明文
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------- 可选:统一清理尾部不可见控制字符 -------- */
|
||||||
|
private static String clean(String s){
|
||||||
|
if(s==null) return null;
|
||||||
|
// 去除结尾控制符/空白(不影响中间合法空格)
|
||||||
|
return s.replaceAll("[\\p{Cntrl}]+","").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------- 若仍需要旧接口,可保留 (不建议再用于新前端) -------- */
|
||||||
|
@Deprecated
|
||||||
|
public static String desEncrypt(String data) throws Exception {
|
||||||
|
return decryptLegacyNoPadding(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加密(若前端不再使用,可忽略;保留旧实现避免影响历史) */
|
||||||
|
@Deprecated
|
||||||
|
public static String encrypt(String data) throws Exception {
|
||||||
|
try{
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||||
int blockSize = cipher.getBlockSize();
|
int blockSize = cipher.getBlockSize();
|
||||||
|
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
|
||||||
byte[] dataBytes = data.getBytes();
|
|
||||||
int plaintextLength = dataBytes.length;
|
int plaintextLength = dataBytes.length;
|
||||||
if (plaintextLength % blockSize != 0) {
|
if (plaintextLength % blockSize != 0) {
|
||||||
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
|
plaintextLength += (blockSize - (plaintextLength % blockSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] plaintext = new byte[plaintextLength];
|
byte[] plaintext = new byte[plaintextLength];
|
||||||
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
|
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
|
||||||
|
SecretKeySpec keyspec = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), "AES");
|
||||||
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
|
IvParameterSpec ivspec = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));
|
||||||
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
|
|
||||||
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
|
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
|
||||||
byte[] encrypted = cipher.doFinal(plaintext);
|
byte[] encrypted = cipher.doFinal(plaintext);
|
||||||
|
|
||||||
return Base64.encodeToString(encrypted);
|
return Base64.encodeToString(encrypted);
|
||||||
|
}catch(Exception e){
|
||||||
} catch (Exception e) {
|
throw new IllegalStateException("legacy encrypt error", e);
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// public static void main(String[] args) throws Exception {
|
||||||
* 解密方法
|
// // 前端 CBC/Pkcs7 密文测试
|
||||||
* @param data 要解密的数据
|
// String frontCipher = encrypt("sa"); // 仅验证管道是否可用(旧方式)
|
||||||
* @param key 解密key
|
// System.out.println(resolvePassword(frontCipher));
|
||||||
* @param iv 解密iv
|
|
||||||
* @return 解密的结果
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static String desEncrypt(String data, String key, String iv) throws Exception {
|
|
||||||
//update-begin-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理
|
|
||||||
byte[] encrypted1 = Base64.decode(data);
|
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
|
||||||
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
|
|
||||||
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
|
|
||||||
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
|
|
||||||
|
|
||||||
byte[] original = cipher.doFinal(encrypted1);
|
|
||||||
String originalString = new String(original);
|
|
||||||
//加密解码后的字符串会出现\u0000
|
|
||||||
return originalString.replaceAll("\\u0000", "");
|
|
||||||
//update-end-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用默认的key和iv加密
|
|
||||||
* @param data
|
|
||||||
* @return
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static String encrypt(String data) throws Exception {
|
|
||||||
return encrypt(data, KEY, IV);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用默认的key和iv解密
|
|
||||||
* @param data
|
|
||||||
* @return
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static String desEncrypt(String data) throws Exception {
|
|
||||||
return desEncrypt(data, KEY, IV);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 测试
|
|
||||||
// */
|
|
||||||
// public static void main(String args[]) throws Exception {
|
|
||||||
// String test1 = "sa";
|
|
||||||
// String test =new String(test1.getBytes(),"UTF-8");
|
|
||||||
// String data = null;
|
|
||||||
// String key = KEY;
|
|
||||||
// String iv = IV;
|
|
||||||
// // /g2wzfqvMOeazgtsUVbq1kmJawROa6mcRAzwG1/GeJ4=
|
|
||||||
// data = encrypt(test, key, iv);
|
|
||||||
// System.out.println("数据:"+test);
|
|
||||||
// System.out.println("加密:"+data);
|
|
||||||
// String jiemi =desEncrypt(data, key, iv).trim();
|
|
||||||
// System.out.println("解密:"+jiemi);
|
|
||||||
// }
|
// }
|
||||||
|
}
|
||||||
}
|
|
||||||
@ -194,7 +194,7 @@ public class SsrfFileTypeFilter {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
private static String getFileType(MultipartFile file, String customPath) throws Exception {
|
private static String getFileType(MultipartFile file, String customPath) throws Exception {
|
||||||
//update-begin-author:liusq date:20230404 for: [issue/4672]方法造成的文件被占用,注释掉此方法tomcat就能自动清理掉临时文件
|
// 代码逻辑说明: [issue/4672]方法造成的文件被占用,注释掉此方法tomcat就能自动清理掉临时文件
|
||||||
String fileExtendName = null;
|
String fileExtendName = null;
|
||||||
InputStream is = null;
|
InputStream is = null;
|
||||||
try {
|
try {
|
||||||
@ -234,7 +234,6 @@ public class SsrfFileTypeFilter {
|
|||||||
is.close();
|
is.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-end-author:liusq date:20230404 for: [issue/4672]方法造成的文件被占用,注释掉此方法tomcat就能自动清理掉临时文件
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -11,6 +11,10 @@ import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
|
|||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.beans.BeanWrapper;
|
||||||
|
import org.springframework.beans.BeanWrapperImpl;
|
||||||
|
|
||||||
|
import java.beans.PropertyDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
@ -23,6 +27,7 @@ import java.sql.Date;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -563,10 +568,8 @@ public class oConvertUtils {
|
|||||||
return "";
|
return "";
|
||||||
} else if (!name.contains(SymbolConstant.UNDERLINE)) {
|
} else if (!name.contains(SymbolConstant.UNDERLINE)) {
|
||||||
// 不含下划线,仅将首字母小写
|
// 不含下划线,仅将首字母小写
|
||||||
//update-begin--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
|
// 代码逻辑说明: TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
|
||||||
//update-begin--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
|
|
||||||
return name.substring(0, 1).toLowerCase() + name.substring(1).toLowerCase();
|
return name.substring(0, 1).toLowerCase() + name.substring(1).toLowerCase();
|
||||||
//update-end--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
|
|
||||||
}
|
}
|
||||||
// 用下划线将原始字符串分割
|
// 用下划线将原始字符串分割
|
||||||
String[] camels = name.split("_");
|
String[] camels = name.split("_");
|
||||||
@ -611,7 +614,6 @@ public class oConvertUtils {
|
|||||||
return result.substring(0, result.length() - 1);
|
return result.substring(0, result.length() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
|
|
||||||
/**
|
/**
|
||||||
* 将下划线大写方式命名的字符串转换为驼峰式。(首字母写)
|
* 将下划线大写方式命名的字符串转换为驼峰式。(首字母写)
|
||||||
* 如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。</br>
|
* 如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。</br>
|
||||||
@ -644,7 +646,6 @@ public class oConvertUtils {
|
|||||||
}
|
}
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
//update-end--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将驼峰命名转化成下划线
|
* 将驼峰命名转化成下划线
|
||||||
@ -982,17 +983,18 @@ public class oConvertUtils {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断 list1中的元素是否在list2中出现
|
* 判断 sourceList中的元素是否在targetList中出现
|
||||||
|
*
|
||||||
* QQYUN-5326【简流】获取组织人员 单/多 筛选条件 没有部门筛选
|
* QQYUN-5326【简流】获取组织人员 单/多 筛选条件 没有部门筛选
|
||||||
* @param list1
|
* @param sourceList 源列表,要检查的元素列表
|
||||||
* @param list2
|
* @param targetList 目标列表,用于匹配的列表
|
||||||
* @return
|
* @return 如果sourceList中有任何元素在targetList中存在则返回true,否则返回false
|
||||||
*/
|
*/
|
||||||
public static boolean isInList(List<String> list1, List<String> list2){
|
public static boolean isInList(List<String> sourceList, List<String> targetList){
|
||||||
for(String str1: list1){
|
for(String sourceItem: sourceList){
|
||||||
boolean flag = false;
|
boolean flag = false;
|
||||||
for(String str2: list2){
|
for(String targetItem: targetList){
|
||||||
if(str1.equals(str2)){
|
if(sourceItem.equals(targetItem)){
|
||||||
flag = true;
|
flag = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1004,6 +1006,35 @@ public class oConvertUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断 sourceList中的所有元素是否都在targetList中存在
|
||||||
|
* @param sourceList 源列表,要检查的元素列表
|
||||||
|
* @param targetList 目标列表,用于匹配的列表
|
||||||
|
* @return 如果sourceList中的所有元素都在targetList中存在则返回true,否则返回false
|
||||||
|
*/
|
||||||
|
public static boolean isAllInList(List<String> sourceList, List<String> targetList){
|
||||||
|
if(sourceList == null || sourceList.isEmpty()){
|
||||||
|
return true; // 空列表视为所有元素都存在
|
||||||
|
}
|
||||||
|
if(targetList == null || targetList.isEmpty()){
|
||||||
|
return false; // 目标列表为空,源列表非空时返回false
|
||||||
|
}
|
||||||
|
|
||||||
|
for(String sourceItem: sourceList){
|
||||||
|
boolean found = false;
|
||||||
|
for(String targetItem: targetList){
|
||||||
|
if(sourceItem.equals(targetItem)){
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!found){
|
||||||
|
return false; // 有任何一个元素不在目标列表中,返回false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true; // 所有元素都找到了
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算文件大小转成MB
|
* 计算文件大小转成MB
|
||||||
* @param uploadCount
|
* @param uploadCount
|
||||||
@ -1168,5 +1199,58 @@ public class oConvertUtils {
|
|||||||
public static boolean isEffectiveTenant(String tenantId) {
|
public static boolean isEffectiveTenant(String tenantId) {
|
||||||
return MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL && isNotEmpty(tenantId) && !("0").equals(tenantId);
|
return MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL && isNotEmpty(tenantId) && !("0").equals(tenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制源对象的非空属性到目标对象(同名属性)
|
||||||
|
*
|
||||||
|
* @param source 源对象(页面)
|
||||||
|
* @param target 目标对象(数据库实体)
|
||||||
|
*/
|
||||||
|
public static void copyNonNullFields(Object source, Object target) {
|
||||||
|
if (source == null || target == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取源对象的非空属性名数组
|
||||||
|
String[] nullPropertyNames = getNullPropertyNames(source);
|
||||||
|
// 复制:忽略源对象的空属性,仅覆盖目标对象的对应非空属性
|
||||||
|
BeanUtils.copyProperties(source, target, nullPropertyNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取源对象中值为 null 的属性名数组
|
||||||
|
*
|
||||||
|
* @param source
|
||||||
|
*/
|
||||||
|
private static String[] getNullPropertyNames(Object source) {
|
||||||
|
BeanWrapper beanWrapper = new BeanWrapperImpl(source);
|
||||||
|
//获取类的属性
|
||||||
|
PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();
|
||||||
|
// 过滤出值为 null 的属性名
|
||||||
|
return Stream.of(propertyDescriptors)
|
||||||
|
.map(PropertyDescriptor::getName)
|
||||||
|
.filter(name -> beanWrapper.getPropertyValue(name) == null)
|
||||||
|
.toArray(String[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String转换long类型
|
||||||
|
*
|
||||||
|
* @param v
|
||||||
|
* @param def
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static long getLong(Object v, long def) {
|
||||||
|
if (v == null) {
|
||||||
|
return def;
|
||||||
|
};
|
||||||
|
if (v instanceof Number) {
|
||||||
|
return ((Number) v).longValue();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Long.parseLong(v.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -124,9 +124,8 @@ public class OssBootUtil {
|
|||||||
if (!fileDir.endsWith(SymbolConstant.SINGLE_SLASH)) {
|
if (!fileDir.endsWith(SymbolConstant.SINGLE_SLASH)) {
|
||||||
fileDir = fileDir.concat(SymbolConstant.SINGLE_SLASH);
|
fileDir = fileDir.concat(SymbolConstant.SINGLE_SLASH);
|
||||||
}
|
}
|
||||||
//update-begin-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
|
// 代码逻辑说明: 过滤上传文件夹名特殊字符,防止攻击
|
||||||
fileDir=StrAttackFilter.filter(fileDir);
|
fileDir=StrAttackFilter.filter(fileDir);
|
||||||
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
|
|
||||||
fileUrl = fileUrl.append(fileDir + fileName);
|
fileUrl = fileUrl.append(fileDir + fileName);
|
||||||
|
|
||||||
if (oConvertUtils.isNotEmpty(staticDomain) && staticDomain.toLowerCase().startsWith(CommonConstant.STR_HTTP)) {
|
if (oConvertUtils.isNotEmpty(staticDomain) && staticDomain.toLowerCase().startsWith(CommonConstant.STR_HTTP)) {
|
||||||
@ -263,9 +262,8 @@ public class OssBootUtil {
|
|||||||
newBucket = bucket;
|
newBucket = bucket;
|
||||||
}
|
}
|
||||||
initOss(endPoint, accessKeyId, accessKeySecret);
|
initOss(endPoint, accessKeyId, accessKeySecret);
|
||||||
//update-begin---author:liusq Date:20220120 for:替换objectName前缀,防止key不一致导致获取不到文件----
|
// 代码逻辑说明: 替换objectName前缀,防止key不一致导致获取不到文件----
|
||||||
objectName = OssBootUtil.replacePrefix(objectName,bucket);
|
objectName = OssBootUtil.replacePrefix(objectName,bucket);
|
||||||
//update-end---author:liusq Date:20220120 for:替换objectName前缀,防止key不一致导致获取不到文件----
|
|
||||||
OSSObject ossObject = ossClient.getObject(newBucket,objectName);
|
OSSObject ossObject = ossClient.getObject(newBucket,objectName);
|
||||||
inputStream = new BufferedInputStream(ossObject.getObjectContent());
|
inputStream = new BufferedInputStream(ossObject.getObjectContent());
|
||||||
}catch (Exception e){
|
}catch (Exception e){
|
||||||
@ -293,9 +291,8 @@ public class OssBootUtil {
|
|||||||
public static String getObjectUrl(String bucketName, String objectName, Date expires) {
|
public static String getObjectUrl(String bucketName, String objectName, Date expires) {
|
||||||
initOss(endPoint, accessKeyId, accessKeySecret);
|
initOss(endPoint, accessKeyId, accessKeySecret);
|
||||||
try{
|
try{
|
||||||
//update-begin---author:liusq Date:20220120 for:替换objectName前缀,防止key不一致导致获取不到文件----
|
// 代码逻辑说明: 替换objectName前缀,防止key不一致导致获取不到文件----
|
||||||
objectName = OssBootUtil.replacePrefix(objectName,bucketName);
|
objectName = OssBootUtil.replacePrefix(objectName,bucketName);
|
||||||
//update-end---author:liusq Date:20220120 for:替换objectName前缀,防止key不一致导致获取不到文件----
|
|
||||||
if(ossClient.doesObjectExist(bucketName,objectName)){
|
if(ossClient.doesObjectExist(bucketName,objectName)){
|
||||||
URL url = ossClient.generatePresignedUrl(bucketName,objectName,expires);
|
URL url = ossClient.generatePresignedUrl(bucketName,objectName,expires);
|
||||||
//log.info("原始url : {}", url.toString());
|
//log.info("原始url : {}", url.toString());
|
||||||
|
|||||||
@ -63,7 +63,7 @@ public abstract class AbstractQueryBlackListHandler {
|
|||||||
if(list==null){
|
if(list==null){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
log.info(" 获取sql信息 :{} ", list.toString());
|
log.debug(" 获取sql信息 :{} ", list.toString());
|
||||||
boolean flag = checkTableAndFieldsName(list);
|
boolean flag = checkTableAndFieldsName(list);
|
||||||
if(flag == false){
|
if(flag == false){
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
package org.jeecg.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ai配置类,通用的配置可以放到这里面
|
||||||
|
*
|
||||||
|
* @Author: wangshuai
|
||||||
|
* @Date: 2025/12/17 14:00
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = AiRagConfigBean.PREFIX)
|
||||||
|
public class AiRagConfigBean {
|
||||||
|
public static final String PREFIX = "jeecg.airag";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 敏感节点
|
||||||
|
* stdio mpc命令行功能开启,sql:AI流程SQL节点开启
|
||||||
|
*/
|
||||||
|
private String allowSensitiveNodes = "";
|
||||||
|
}
|
||||||
@ -60,17 +60,15 @@ public class AutoPoiDictConfig implements AutoPoiDictServiceI {
|
|||||||
|
|
||||||
|
|
||||||
for (DictModel t : dictList) {
|
for (DictModel t : dictList) {
|
||||||
//update-begin---author:liusq Date:20230517 for:[issues/4917]excel 导出异常---
|
// 代码逻辑说明: [issues/4917]excel 导出异常---
|
||||||
if(t!=null && t.getText()!=null && t.getValue()!=null){
|
if(t!=null && t.getText()!=null && t.getValue()!=null){
|
||||||
//update-end---author:liusq Date:20230517 for:[issues/4917]excel 导出异常---
|
// 代码逻辑说明: [issues/I4MBB3]@Excel dicText字段的值有下划线时,导入功能不能正确解析---
|
||||||
//update-begin---author:scott Date:20211220 for:[issues/I4MBB3]@Excel dicText字段的值有下划线时,导入功能不能正确解析---
|
|
||||||
if(t.getValue().contains(EXCEL_SPLIT_TAG)){
|
if(t.getValue().contains(EXCEL_SPLIT_TAG)){
|
||||||
String val = t.getValue().replace(EXCEL_SPLIT_TAG,TEMP_EXCEL_SPLIT_TAG);
|
String val = t.getValue().replace(EXCEL_SPLIT_TAG,TEMP_EXCEL_SPLIT_TAG);
|
||||||
dictReplaces.add(t.getText() + EXCEL_SPLIT_TAG + val);
|
dictReplaces.add(t.getText() + EXCEL_SPLIT_TAG + val);
|
||||||
}else{
|
}else{
|
||||||
dictReplaces.add(t.getText() + EXCEL_SPLIT_TAG + t.getValue());
|
dictReplaces.add(t.getText() + EXCEL_SPLIT_TAG + t.getValue());
|
||||||
}
|
}
|
||||||
//update-end---author:20211220 Date:20211220 for:[issues/I4MBB3]@Excel dicText字段的值有下划线时,导入功能不能正确解析---
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dictReplaces != null && dictReplaces.size() != 0) {
|
if (dictReplaces != null && dictReplaces.size() != 0) {
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author eightmonth@qq.com
|
|
||||||
* 启动程序修改DruidWallConfig配置
|
* 启动程序修改DruidWallConfig配置
|
||||||
* 允许SELECT语句的WHERE子句是一个永真条件
|
* 允许SELECT语句的WHERE子句是一个永真条件
|
||||||
* @author eightmonth
|
* @author eightmonth
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
package org.jeecg.config;
|
package org.jeecg.config;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.jeecg.config.tencent.JeecgTencent;
|
||||||
import org.jeecg.config.vo.*;
|
import org.jeecg.config.vo.*;
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Role;
|
import org.springframework.context.annotation.Role;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@ -74,7 +76,35 @@ public class JeecgBaseConfig {
|
|||||||
/**
|
/**
|
||||||
* 百度开放API配置
|
* 百度开放API配置
|
||||||
*/
|
*/
|
||||||
private BaiduApi baiduApi;
|
private BaiduApi baiduApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* minio配置
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private JeecgMinio minio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* oss配置
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private JeecgOSS oss;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信发送方式 aliyun阿里云短信 tencent腾讯云短信
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private String smsSendType = "aliyun";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 腾讯配置
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private JeecgTencent tencent;
|
||||||
|
|
||||||
public String getCustomResourcePrefixPath() {
|
public String getCustomResourcePrefixPath() {
|
||||||
return customResourcePrefixPath;
|
return customResourcePrefixPath;
|
||||||
|
|||||||
@ -95,13 +95,11 @@
|
|||||||
// List<Parameter> pars = new ArrayList<>();
|
// List<Parameter> pars = new ArrayList<>();
|
||||||
// tokenPar.name(CommonConstant.X_ACCESS_TOKEN).description("token").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
|
// tokenPar.name(CommonConstant.X_ACCESS_TOKEN).description("token").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
|
||||||
// pars.add(tokenPar.build());
|
// pars.add(tokenPar.build());
|
||||||
// //update-begin-author:liusq---date:2024-08-15--for: 开启多租户时,全局参数增加租户id
|
|
||||||
// if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
|
// if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
|
||||||
// ParameterBuilder tenantPar = new ParameterBuilder();
|
// ParameterBuilder tenantPar = new ParameterBuilder();
|
||||||
// tenantPar.name(CommonConstant.TENANT_ID).description("租户ID").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
|
// tenantPar.name(CommonConstant.TENANT_ID).description("租户ID").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
|
||||||
// pars.add(tenantPar.build());
|
// pars.add(tenantPar.build());
|
||||||
// }
|
// }
|
||||||
// //update-end-author:liusq---date:2024-08-15--for: 开启多租户时,全局参数增加租户id
|
|
||||||
//
|
//
|
||||||
// return pars;
|
// return pars;
|
||||||
// }
|
// }
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.springdoc.core.customizers.OperationCustomizer;
|
import org.springdoc.core.customizers.OperationCustomizer;
|
||||||
import org.springdoc.core.filters.GlobalOpenApiMethodFilter;
|
import org.springdoc.core.filters.GlobalOpenApiMethodFilter;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.PropertySource;
|
import org.springframework.context.annotation.PropertySource;
|
||||||
@ -22,18 +23,24 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author eightmonth
|
* @author eightmonth
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@ConditionalOnProperty(prefix = "knife4j", name = "production", havingValue = "false", matchIfMissing = true)
|
||||||
@PropertySource("classpath:config/default-spring-doc.properties")
|
@PropertySource("classpath:config/default-spring-doc.properties")
|
||||||
public class Swagger3Config implements WebMvcConfigurer {
|
public class Swagger3Config implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
// 路径匹配结果缓存,避免重复计算
|
||||||
|
private static final Map<String, Boolean> EXCLUDED_PATHS_CACHE = new ConcurrentHashMap<>();
|
||||||
// 定义不需要注入安全要求的路径集合
|
// 定义不需要注入安全要求的路径集合
|
||||||
Set<String> excludedPaths = new HashSet<>(Arrays.asList(
|
private static final Set<String> excludedPaths = new HashSet<>(Arrays.asList(
|
||||||
"/sys/randomImage/{key}",
|
"/sys/randomImage/**",
|
||||||
"/sys/login",
|
"/sys/login",
|
||||||
"/sys/phoneLogin",
|
"/sys/phoneLogin",
|
||||||
"/sys/mLogin",
|
"/sys/mLogin",
|
||||||
@ -43,7 +50,20 @@ public class Swagger3Config implements WebMvcConfigurer {
|
|||||||
"/sys/thirdLogin/**",
|
"/sys/thirdLogin/**",
|
||||||
"/sys/user/register"
|
"/sys/user/register"
|
||||||
));
|
));
|
||||||
|
// 预处理通配符模式,提高匹配效率
|
||||||
|
private static final Set<String> wildcardPatterns = new HashSet<>();
|
||||||
|
private static final Set<String> exactPatterns = new HashSet<>();
|
||||||
|
static {
|
||||||
|
// 初始化时分离精确匹配和通配符匹配
|
||||||
|
for (String pattern : excludedPaths) {
|
||||||
|
if (pattern.endsWith("/**")) {
|
||||||
|
wildcardPatterns.add(pattern.substring(0, pattern.length() - 3));
|
||||||
|
} else {
|
||||||
|
exactPatterns.add(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* 显示swagger-ui.html文档展示页,还必须注入swagger资源:
|
* 显示swagger-ui.html文档展示页,还必须注入swagger资源:
|
||||||
@ -97,19 +117,18 @@ public class Swagger3Config implements WebMvcConfigurer {
|
|||||||
|
|
||||||
return fullPath.toString();
|
return fullPath.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean isExcludedPath(String path) {
|
private boolean isExcludedPath(String path) {
|
||||||
return excludedPaths.stream()
|
// 使用缓存避免重复计算
|
||||||
.anyMatch(pattern -> {
|
return EXCLUDED_PATHS_CACHE.computeIfAbsent(path, p -> {
|
||||||
if (pattern.endsWith("/**")) {
|
// 精确匹配
|
||||||
// 处理通配符匹配
|
if (exactPatterns.contains(p)) {
|
||||||
String basePath = pattern.substring(0, pattern.length() - 3);
|
return true;
|
||||||
return path.startsWith(basePath);
|
}
|
||||||
}
|
// 通配符匹配
|
||||||
// 精确匹配
|
return wildcardPatterns.stream().anyMatch(p::startsWith);
|
||||||
return pattern.equals(path);
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ -117,7 +136,7 @@ public class Swagger3Config implements WebMvcConfigurer {
|
|||||||
return new OpenAPI()
|
return new OpenAPI()
|
||||||
.info(new Info()
|
.info(new Info()
|
||||||
.title("JeecgBoot 后台服务API接口文档")
|
.title("JeecgBoot 后台服务API接口文档")
|
||||||
.version("3.8.3")
|
.version("3.9.1")
|
||||||
.contact(new Contact().name("北京国炬信息技术有限公司").url("www.jeccg.com").email("jeecgos@163.com"))
|
.contact(new Contact().name("北京国炬信息技术有限公司").url("www.jeccg.com").email("jeecgos@163.com"))
|
||||||
.description("后台API接口")
|
.description("后台API接口")
|
||||||
.termsOfService("NO terms of service")
|
.termsOfService("NO terms of service")
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
package org.jeecg.config;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务调度器配置
|
||||||
|
* 提供 ThreadPoolTaskScheduler Bean 用于 AI RAG 流程调度等功能
|
||||||
|
* 仅当容器中不存在 ThreadPoolTaskScheduler 时才创建
|
||||||
|
*
|
||||||
|
* @author jeecg
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
public class TaskSchedulerConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean(ThreadPoolTaskScheduler.class)
|
||||||
|
public ThreadPoolTaskScheduler taskScheduler() {
|
||||||
|
log.info("初始化定时任务调度器 ThreadPoolTaskScheduler");
|
||||||
|
|
||||||
|
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||||
|
scheduler.setPoolSize(10);
|
||||||
|
scheduler.setThreadNamePrefix("airag-scheduler-");
|
||||||
|
scheduler.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
|
scheduler.setAwaitTerminationSeconds(60);
|
||||||
|
return scheduler;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,19 +0,0 @@
|
|||||||
//package org.jeecg.config;
|
|
||||||
//
|
|
||||||
//import io.undertow.server.DefaultByteBufferPool;
|
|
||||||
//import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
|
|
||||||
//import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
|
||||||
//import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
|
||||||
//import org.springframework.stereotype.Component;
|
|
||||||
//
|
|
||||||
//@Component
|
|
||||||
//public class UndertowCustomizer implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
|
|
||||||
// @Override
|
|
||||||
// public void customize(UndertowServletWebServerFactory factory) {
|
|
||||||
// factory.addDeploymentInfoCustomizers(deploymentInfo -> {
|
|
||||||
// WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
|
|
||||||
// webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 1024));
|
|
||||||
// deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@ -142,7 +142,6 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
|||||||
return objectMapper;
|
return objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin---author:chenrui ---date:20240514 for:[QQYUN-9247]系统监控功能优化------------
|
|
||||||
// /**
|
// /**
|
||||||
// * SpringBootAdmin的Httptrace不见了
|
// * SpringBootAdmin的Httptrace不见了
|
||||||
// * https://blog.csdn.net/u013810234/article/details/110097201
|
// * https://blog.csdn.net/u013810234/article/details/110097201
|
||||||
@ -151,7 +150,6 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
|||||||
// public InMemoryHttpTraceRepository getInMemoryHttpTrace(){
|
// public InMemoryHttpTraceRepository getInMemoryHttpTrace(){
|
||||||
// return new InMemoryHttpTraceRepository();
|
// return new InMemoryHttpTraceRepository();
|
||||||
// }
|
// }
|
||||||
//update-end---author:chenrui ---date:20240514 for:[QQYUN-9247]系统监控功能优化------------
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -165,7 +163,7 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
|||||||
// 确保在应用启动早期就配置MeterFilter,避免警告
|
// 确保在应用启动早期就配置MeterFilter,避免警告
|
||||||
if (null != meterRegistryPostProcessor && null != prometheusMeterRegistry) {
|
if (null != meterRegistryPostProcessor && null != prometheusMeterRegistry) {
|
||||||
meterRegistryPostProcessor.postProcessAfterInitialization(prometheusMeterRegistry, "prometheusMeterRegistry");
|
meterRegistryPostProcessor.postProcessAfterInitialization(prometheusMeterRegistry, "prometheusMeterRegistry");
|
||||||
log.info("PrometheusMeterRegistry配置完成");
|
log.info("PrometheusMeterRegistry 配置完成");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -129,20 +129,18 @@ public class MybatisInterceptor implements Interceptor {
|
|||||||
Field[] fields = null;
|
Field[] fields = null;
|
||||||
if (parameter instanceof ParamMap) {
|
if (parameter instanceof ParamMap) {
|
||||||
ParamMap<?> p = (ParamMap<?>) parameter;
|
ParamMap<?> p = (ParamMap<?>) parameter;
|
||||||
//update-begin-author:scott date:20190729 for:批量更新报错issues/IZA3Q--
|
// 代码逻辑说明: 批量更新报错issues/IZA3Q--
|
||||||
String et = "et";
|
String et = "et";
|
||||||
if (p.containsKey(et)) {
|
if (p.containsKey(et)) {
|
||||||
parameter = p.get(et);
|
parameter = p.get(et);
|
||||||
} else {
|
} else {
|
||||||
parameter = p.get("param1");
|
parameter = p.get("param1");
|
||||||
}
|
}
|
||||||
//update-end-author:scott date:20190729 for:批量更新报错issues/IZA3Q-
|
|
||||||
|
|
||||||
//update-begin-author:scott date:20190729 for:更新指定字段时报错 issues/#516-
|
// 代码逻辑说明: 更新指定字段时报错 issues/#516-
|
||||||
if (parameter == null) {
|
if (parameter == null) {
|
||||||
return invocation.proceed();
|
return invocation.proceed();
|
||||||
}
|
}
|
||||||
//update-end-author:scott date:20190729 for:更新指定字段时报错 issues/#516-
|
|
||||||
|
|
||||||
fields = oConvertUtils.getAllFields(parameter);
|
fields = oConvertUtils.getAllFields(parameter);
|
||||||
} else {
|
} else {
|
||||||
@ -184,7 +182,6 @@ public class MybatisInterceptor implements Interceptor {
|
|||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin--Author:scott Date:20191213 for:关于使用Quzrtz 开启线程任务, #465
|
|
||||||
/**
|
/**
|
||||||
* 获取登录用户
|
* 获取登录用户
|
||||||
* @return
|
* @return
|
||||||
@ -199,6 +196,5 @@ public class MybatisInterceptor implements Interceptor {
|
|||||||
}
|
}
|
||||||
return sysUser;
|
return sysUser;
|
||||||
}
|
}
|
||||||
//update-end--Author:scott Date:20191213 for:关于使用Quzrtz 开启线程任务, #465
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,20 +21,23 @@ import org.jeecg.config.shiro.filters.CustomShiroFilterFactoryBean;
|
|||||||
import org.jeecg.config.shiro.filters.JwtFilter;
|
import org.jeecg.config.shiro.filters.JwtFilter;
|
||||||
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
import org.springframework.context.annotation.*;
|
import org.springframework.context.annotation.*;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||||
|
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||||
import redis.clients.jedis.HostAndPort;
|
import redis.clients.jedis.HostAndPort;
|
||||||
import redis.clients.jedis.JedisCluster;
|
import redis.clients.jedis.JedisCluster;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,7 +48,6 @@ import java.util.*;
|
|||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
||||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
|
||||||
public class ShiroConfig {
|
public class ShiroConfig {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
@ -111,7 +113,7 @@ public class ShiroConfig {
|
|||||||
filterChainDefinitionMap.put("/sys/checkAuth", "anon"); //授权接口排除
|
filterChainDefinitionMap.put("/sys/checkAuth", "anon"); //授权接口排除
|
||||||
filterChainDefinitionMap.put("/openapi/call/**", "anon"); // 开放平台接口排除
|
filterChainDefinitionMap.put("/openapi/call/**", "anon"); // 开放平台接口排除
|
||||||
|
|
||||||
//update-begin--Author:scott Date:20221116 for:排除静态资源后缀
|
// 代码逻辑说明: 排除静态资源后缀
|
||||||
filterChainDefinitionMap.put("/", "anon");
|
filterChainDefinitionMap.put("/", "anon");
|
||||||
filterChainDefinitionMap.put("/doc.html", "anon");
|
filterChainDefinitionMap.put("/doc.html", "anon");
|
||||||
filterChainDefinitionMap.put("/**/*.js", "anon");
|
filterChainDefinitionMap.put("/**/*.js", "anon");
|
||||||
@ -129,7 +131,6 @@ public class ShiroConfig {
|
|||||||
|
|
||||||
filterChainDefinitionMap.put("/**/*.glb", "anon");
|
filterChainDefinitionMap.put("/**/*.glb", "anon");
|
||||||
filterChainDefinitionMap.put("/**/*.wasm", "anon");
|
filterChainDefinitionMap.put("/**/*.wasm", "anon");
|
||||||
//update-end--Author:scott Date:20221116 for:排除静态资源后缀
|
|
||||||
|
|
||||||
filterChainDefinitionMap.put("/druid/**", "anon");
|
filterChainDefinitionMap.put("/druid/**", "anon");
|
||||||
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
|
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
|
||||||
@ -137,9 +138,7 @@ public class ShiroConfig {
|
|||||||
filterChainDefinitionMap.put("/webjars/**", "anon");
|
filterChainDefinitionMap.put("/webjars/**", "anon");
|
||||||
filterChainDefinitionMap.put("/v3/**", "anon");
|
filterChainDefinitionMap.put("/v3/**", "anon");
|
||||||
|
|
||||||
// update-begin--Author:sunjianlei Date:20210510 for:排除消息通告查看详情页面(用于第三方APP)
|
|
||||||
filterChainDefinitionMap.put("/sys/annountCement/show/**", "anon");
|
filterChainDefinitionMap.put("/sys/annountCement/show/**", "anon");
|
||||||
// update-end--Author:sunjianlei Date:20210510 for:排除消息通告查看详情页面(用于第三方APP)
|
|
||||||
|
|
||||||
//积木报表排除
|
//积木报表排除
|
||||||
filterChainDefinitionMap.put("/jmreport/**", "anon");
|
filterChainDefinitionMap.put("/jmreport/**", "anon");
|
||||||
@ -191,6 +190,8 @@ public class ShiroConfig {
|
|||||||
// 企业微信证书排除
|
// 企业微信证书排除
|
||||||
filterChainDefinitionMap.put("/WW_verify*", "anon");
|
filterChainDefinitionMap.put("/WW_verify*", "anon");
|
||||||
|
|
||||||
|
filterChainDefinitionMap.put("/openapi/call/**", "anon");
|
||||||
|
|
||||||
// 添加自己的过滤器并且取名为jwt
|
// 添加自己的过滤器并且取名为jwt
|
||||||
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
|
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
|
||||||
//如果cloudServer为空 则说明是单体 需要加载跨域配置【微服务跨域切换】
|
//如果cloudServer为空 则说明是单体 需要加载跨域配置【微服务跨域切换】
|
||||||
@ -207,7 +208,6 @@ public class ShiroConfig {
|
|||||||
return shiroFilterFactoryBean;
|
return shiroFilterFactoryBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* spring过滤装饰器 <br/>
|
* spring过滤装饰器 <br/>
|
||||||
@ -223,21 +223,24 @@ public class ShiroConfig {
|
|||||||
FilterRegistrationBean registration = new FilterRegistrationBean();
|
FilterRegistrationBean registration = new FilterRegistrationBean();
|
||||||
registration.setFilter(new DelegatingFilterProxy("shiroFilterFactoryBean"));
|
registration.setFilter(new DelegatingFilterProxy("shiroFilterFactoryBean"));
|
||||||
registration.setEnabled(true);
|
registration.setEnabled(true);
|
||||||
//update-begin---author:chenrui ---date:20241202 for:[issues/7491]运行时间好长,效率慢 ------------
|
// 代码逻辑说明: [issues/7491]运行耗时长,效率慢
|
||||||
registration.addUrlPatterns("/test/ai/chat/send");
|
registration.addUrlPatterns("/test/ai/chat/send");
|
||||||
//update-end---author:chenrui ---date:20241202 for:[issues/7491]运行时间好长,效率慢 ------------
|
|
||||||
registration.addUrlPatterns("/airag/flow/run");
|
registration.addUrlPatterns("/airag/flow/run");
|
||||||
registration.addUrlPatterns("/airag/flow/debug");
|
registration.addUrlPatterns("/airag/flow/debug");
|
||||||
registration.addUrlPatterns("/airag/chat/send");
|
registration.addUrlPatterns("/airag/chat/send");
|
||||||
registration.addUrlPatterns("/airag/app/debug");
|
registration.addUrlPatterns("/airag/app/debug");
|
||||||
registration.addUrlPatterns("/airag/app/prompt/generate");
|
registration.addUrlPatterns("/airag/app/prompt/generate");
|
||||||
registration.addUrlPatterns("/airag/chat/receive/**");
|
registration.addUrlPatterns("/airag/chat/receive/**");
|
||||||
|
// 添加SSE接口的异步支持
|
||||||
|
registration.addUrlPatterns("/airag/extData/evaluator/debug");
|
||||||
|
registration.addUrlPatterns("/drag/onlDragDatasetHead/generateChartSse");
|
||||||
|
registration.addUrlPatterns("/drag/onlDragDatasetHead/updateChartOptSse");
|
||||||
|
registration.addUrlPatterns("/drag/onlDragDatasetHead/generateSqlSse");
|
||||||
//支持异步
|
//支持异步
|
||||||
registration.setAsyncSupported(true);
|
registration.setAsyncSupported(true);
|
||||||
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
|
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
|
||||||
return registration;
|
return registration;
|
||||||
}
|
}
|
||||||
//update-end---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
|
||||||
|
|
||||||
@Bean("securityManager")
|
@Bean("securityManager")
|
||||||
public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
|
public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
|
||||||
@ -358,7 +361,6 @@ public class ShiroConfig {
|
|||||||
JedisCluster jedisCluster = new JedisCluster(portSet);
|
JedisCluster jedisCluster = new JedisCluster(portSet);
|
||||||
redisManager.setJedisCluster(jedisCluster);
|
redisManager.setJedisCluster(jedisCluster);
|
||||||
}
|
}
|
||||||
//update-end--Author:scott Date:20210531 for:修改集群模式下未设置redis密码的bug issues/I3QNIC
|
|
||||||
manager = redisManager;
|
manager = redisManager;
|
||||||
}
|
}
|
||||||
return manager;
|
return manager;
|
||||||
@ -375,7 +377,7 @@ public class ShiroConfig {
|
|||||||
mapping.setUrlPathHelper(new ShiroUrlPathHelper());
|
mapping.setUrlPathHelper(new ShiroUrlPathHelper());
|
||||||
return mapping;
|
return mapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> rebuildUrl(String[] bases, String[] uris) {
|
private List<String> rebuildUrl(String[] bases, String[] uris) {
|
||||||
List<String> urls = new ArrayList<>();
|
List<String> urls = new ArrayList<>();
|
||||||
for (String base : bases) {
|
for (String base : bases) {
|
||||||
|
|||||||
@ -83,7 +83,7 @@ public class ShiroRealm extends AuthorizingRealm {
|
|||||||
Set<String> permissionSet = commonApi.queryUserAuths(userId);
|
Set<String> permissionSet = commonApi.queryUserAuths(userId);
|
||||||
info.addStringPermissions(permissionSet);
|
info.addStringPermissions(permissionSet);
|
||||||
//System.out.println(permissionSet);
|
//System.out.println(permissionSet);
|
||||||
log.info("===============Shiro权限认证成功==============");
|
log.debug("===============Shiro权限认证成功==============");
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,8 +110,8 @@ public class ShiroRealm extends AuthorizingRealm {
|
|||||||
loginUser = this.checkUserTokenIsEffect(token);
|
loginUser = this.checkUserTokenIsEffect(token);
|
||||||
} catch (AuthenticationException e) {
|
} catch (AuthenticationException e) {
|
||||||
log.error("—————校验 check token 失败——————————"+ e.getMessage(), e);
|
log.error("—————校验 check token 失败——————————"+ e.getMessage(), e);
|
||||||
JwtUtil.responseError(SpringContextUtils.getHttpServletResponse(),401,e.getMessage());
|
// 重新抛出异常,让JwtFilter统一处理,避免返回两次错误响应
|
||||||
return null;
|
throw e;
|
||||||
}
|
}
|
||||||
return new SimpleAuthenticationInfo(loginUser, token, getName());
|
return new SimpleAuthenticationInfo(loginUser, token, getName());
|
||||||
}
|
}
|
||||||
@ -141,9 +141,11 @@ public class ShiroRealm extends AuthorizingRealm {
|
|||||||
}
|
}
|
||||||
// 校验token是否超时失效 & 或者账号密码是否错误
|
// 校验token是否超时失效 & 或者账号密码是否错误
|
||||||
if (!jwtTokenRefresh(token, username, loginUser.getPassword())) {
|
if (!jwtTokenRefresh(token, username, loginUser.getPassword())) {
|
||||||
throw new AuthenticationException(CommonConstant.TOKEN_IS_INVALID_MSG);
|
// 用户登录Token过期提示信息
|
||||||
|
String userLoginTokenErrorMsg = oConvertUtils.getString(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN_ERROR_MSG + token));
|
||||||
|
throw new AuthenticationException(oConvertUtils.isEmpty(userLoginTokenErrorMsg)? CommonConstant.TOKEN_IS_INVALID_MSG: userLoginTokenErrorMsg);
|
||||||
}
|
}
|
||||||
//update-begin-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
|
// 代码逻辑说明: 校验用户的tenant_id和前端传过来的是否一致
|
||||||
String userTenantIds = loginUser.getRelTenantIds();
|
String userTenantIds = loginUser.getRelTenantIds();
|
||||||
if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL && oConvertUtils.isNotEmpty(userTenantIds)){
|
if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL && oConvertUtils.isNotEmpty(userTenantIds)){
|
||||||
String contextTenantId = TenantContext.getTenant();
|
String contextTenantId = TenantContext.getTenant();
|
||||||
@ -152,7 +154,7 @@ public class ShiroRealm extends AuthorizingRealm {
|
|||||||
//登录用户无租户,前端header中租户ID值为 0
|
//登录用户无租户,前端header中租户ID值为 0
|
||||||
String str ="0";
|
String str ="0";
|
||||||
if(oConvertUtils.isNotEmpty(contextTenantId) && !str.equals(contextTenantId)){
|
if(oConvertUtils.isNotEmpty(contextTenantId) && !str.equals(contextTenantId)){
|
||||||
//update-begin-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
|
// 代码逻辑说明: /issues/I4O14W 用户租户信息变更判断漏洞
|
||||||
String[] arr = userTenantIds.split(",");
|
String[] arr = userTenantIds.split(",");
|
||||||
if(!oConvertUtils.isIn(contextTenantId, arr)){
|
if(!oConvertUtils.isIn(contextTenantId, arr)){
|
||||||
boolean isAuthorization = false;
|
boolean isAuthorization = false;
|
||||||
@ -177,10 +179,8 @@ public class ShiroRealm extends AuthorizingRealm {
|
|||||||
}
|
}
|
||||||
//*********************************************
|
//*********************************************
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
|
|
||||||
return loginUser;
|
return loginUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,19 +202,22 @@ public class ShiroRealm extends AuthorizingRealm {
|
|||||||
if (oConvertUtils.isNotEmpty(cacheToken)) {
|
if (oConvertUtils.isNotEmpty(cacheToken)) {
|
||||||
// 校验token有效性
|
// 校验token有效性
|
||||||
if (!JwtUtil.verify(cacheToken, userName, passWord)) {
|
if (!JwtUtil.verify(cacheToken, userName, passWord)) {
|
||||||
String newAuthorization = JwtUtil.sign(userName, passWord);
|
// 从token中解析客户端类型,保持续期时使用相同的客户端类型
|
||||||
// 设置超时时间
|
String clientType = JwtUtil.getClientType(token);
|
||||||
|
String newAuthorization = JwtUtil.sign(userName, passWord, clientType);
|
||||||
|
// 根据客户端类型设置对应的缓存有效时间
|
||||||
|
long expireTime = CommonConstant.CLIENT_TYPE_APP.equalsIgnoreCase(clientType)
|
||||||
|
? JwtUtil.APP_EXPIRE_TIME * 2 / 1000
|
||||||
|
: JwtUtil.EXPIRE_TIME * 2 / 1000;
|
||||||
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
|
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
|
||||||
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME *2 / 1000);
|
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, expireTime);
|
||||||
log.debug("——————————用户在线操作,更新token保证不掉线—————————jwtTokenRefresh——————— "+ token);
|
log.debug("——————————用户在线操作,更新token保证不掉线—————————jwtTokenRefresh——————— "+ token);
|
||||||
}
|
}
|
||||||
//update-begin--Author:scott Date:20191005 for:解决每次请求,都重写redis中 token缓存问题
|
|
||||||
// else {
|
// else {
|
||||||
// // 设置超时时间
|
// // 设置超时时间
|
||||||
// redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, cacheToken);
|
// redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, cacheToken);
|
||||||
// redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);
|
// redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);
|
||||||
// }
|
// }
|
||||||
//update-end--Author:scott Date:20191005 for:解决每次请求,都重写redis中 token缓存问题
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,8 +233,7 @@ public class ShiroRealm extends AuthorizingRealm {
|
|||||||
@Override
|
@Override
|
||||||
public void clearCache(PrincipalCollection principals) {
|
public void clearCache(PrincipalCollection principals) {
|
||||||
super.clearCache(principals);
|
super.clearCache(principals);
|
||||||
//update-begin---author:scott ---date::2024-06-18 for:【TV360X-1320】分配权限必须退出重新登录才生效,造成很多用户困扰---
|
// 代码逻辑说明: 【TV360X-1320】分配权限必须退出重新登录才生效,造成很多用户困扰---
|
||||||
super.clearCachedAuthorizationInfo(principals);
|
super.clearCachedAuthorizationInfo(principals);
|
||||||
//update-end---author:scott ---date::2024-06-18 for:【TV360X-1320】分配权限必须退出重新登录才生效,造成很多用户困扰---
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,9 +56,13 @@ public class JwtFilter extends BasicHttpAuthenticationFilter {
|
|||||||
executeLogin(request, response);
|
executeLogin(request, response);
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
JwtUtil.responseError((HttpServletResponse)response,401,CommonConstant.TOKEN_IS_INVALID_MSG);
|
// 使用异常中的具体错误信息,保留"不允许同一账号多地同时登录"等具体提示
|
||||||
|
String errorMsg = e.getMessage();
|
||||||
|
if (oConvertUtils.isEmpty(errorMsg)) {
|
||||||
|
errorMsg = CommonConstant.TOKEN_IS_INVALID_MSG;
|
||||||
|
}
|
||||||
|
JwtUtil.responseError((HttpServletResponse)response, 401, errorMsg);
|
||||||
return false;
|
return false;
|
||||||
//throw new AuthenticationException("Token失效,请重新登录", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,11 +73,10 @@ public class JwtFilter extends BasicHttpAuthenticationFilter {
|
|||||||
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
|
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
|
||||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||||
String token = httpServletRequest.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
String token = httpServletRequest.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||||
// update-begin--Author:lvdandan Date:20210105 for:JT-355 OA聊天添加token验证,获取token参数
|
// 代码逻辑说明: JT-355 OA聊天添加token验证,获取token参数
|
||||||
if (oConvertUtils.isEmpty(token)) {
|
if (oConvertUtils.isEmpty(token)) {
|
||||||
token = httpServletRequest.getParameter("token");
|
token = httpServletRequest.getParameter("token");
|
||||||
}
|
}
|
||||||
// update-end--Author:lvdandan Date:20210105 for:JT-355 OA聊天添加token验证,获取token参数
|
|
||||||
|
|
||||||
JwtToken jwtToken = new JwtToken(token);
|
JwtToken jwtToken = new JwtToken(token);
|
||||||
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
|
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
|
||||||
@ -106,10 +109,9 @@ public class JwtFilter extends BasicHttpAuthenticationFilter {
|
|||||||
httpServletResponse.setStatus(HttpStatus.OK.value());
|
httpServletResponse.setStatus(HttpStatus.OK.value());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//update-begin-author:taoyan date:20200708 for:多租户用到
|
// 代码逻辑说明: 多租户用到
|
||||||
String tenantId = httpServletRequest.getHeader(CommonConstant.TENANT_ID);
|
String tenantId = httpServletRequest.getHeader(CommonConstant.TENANT_ID);
|
||||||
TenantContext.setTenant(tenantId);
|
TenantContext.setTenant(tenantId);
|
||||||
//update-end-author:taoyan date:20200708 for:多租户用到
|
|
||||||
|
|
||||||
return super.preHandle(request, response);
|
return super.preHandle(request, response);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jeecg.config.shiro.IgnoreAuth;
|
import org.jeecg.config.shiro.IgnoreAuth;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@ -20,6 +21,7 @@ import java.util.stream.Collectors;
|
|||||||
* @date 2024/4/18 11:35
|
* @date 2024/4/18 11:35
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@Lazy(false)
|
||||||
@Component
|
@Component
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class IgnoreAuthPostProcessor implements InitializingBean {
|
public class IgnoreAuthPostProcessor implements InitializingBean {
|
||||||
@ -33,10 +35,15 @@ public class IgnoreAuthPostProcessor implements InitializingBean {
|
|||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
List<String> ignoreAuthUrls = new ArrayList<>();
|
List<String> ignoreAuthUrls = new ArrayList<>();
|
||||||
Set<Class<?>> restControllers = requestMappingHandlerMapping.getHandlerMethods().values().stream().map(HandlerMethod::getBeanType).collect(Collectors.toSet());
|
|
||||||
for (Class<?> restController : restControllers) {
|
// 优化:直接从HandlerMethod过滤,避免重复扫描
|
||||||
ignoreAuthUrls.addAll(postProcessRestController(restController));
|
requestMappingHandlerMapping.getHandlerMethods().values().stream()
|
||||||
}
|
.filter(handlerMethod -> handlerMethod.getMethod().isAnnotationPresent(IgnoreAuth.class))
|
||||||
|
.forEach(handlerMethod -> {
|
||||||
|
Class<?> clazz = handlerMethod.getBeanType();
|
||||||
|
Method method = handlerMethod.getMethod();
|
||||||
|
ignoreAuthUrls.addAll(processIgnoreAuthMethod(clazz, method));
|
||||||
|
});
|
||||||
|
|
||||||
log.info("Init Token ignoreAuthUrls Config [ 集合 ] :{}", ignoreAuthUrls);
|
log.info("Init Token ignoreAuthUrls Config [ 集合 ] :{}", ignoreAuthUrls);
|
||||||
if (!CollectionUtils.isEmpty(ignoreAuthUrls)) {
|
if (!CollectionUtils.isEmpty(ignoreAuthUrls)) {
|
||||||
@ -46,44 +53,30 @@ public class IgnoreAuthPostProcessor implements InitializingBean {
|
|||||||
// 计算方法的耗时
|
// 计算方法的耗时
|
||||||
long endTime = System.currentTimeMillis();
|
long endTime = System.currentTimeMillis();
|
||||||
long elapsedTime = endTime - startTime;
|
long elapsedTime = endTime - startTime;
|
||||||
log.info("Init Token ignoreAuthUrls Config [ 耗时 ] :" + elapsedTime + "毫秒");
|
log.info("Init Token ignoreAuthUrls Config [ 耗时 ] :" + elapsedTime + "ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> postProcessRestController(Class<?> clazz) {
|
// 优化:新方法处理单个@IgnoreAuth方法,减少重复注解检查
|
||||||
List<String> ignoreAuthUrls = new ArrayList<>();
|
private List<String> processIgnoreAuthMethod(Class<?> clazz, Method method) {
|
||||||
RequestMapping base = clazz.getAnnotation(RequestMapping.class);
|
RequestMapping base = clazz.getAnnotation(RequestMapping.class);
|
||||||
String[] baseUrl = Objects.nonNull(base) ? base.value() : new String[]{};
|
String[] baseUrl = Objects.nonNull(base) ? base.value() : new String[]{};
|
||||||
Method[] methods = clazz.getDeclaredMethods();
|
|
||||||
|
String[] uri = null;
|
||||||
for (Method method : methods) {
|
if (method.isAnnotationPresent(RequestMapping.class)) {
|
||||||
if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(RequestMapping.class)) {
|
uri = method.getAnnotation(RequestMapping.class).value();
|
||||||
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
|
} else if (method.isAnnotationPresent(GetMapping.class)) {
|
||||||
String[] uri = requestMapping.value();
|
uri = method.getAnnotation(GetMapping.class).value();
|
||||||
ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
|
} else if (method.isAnnotationPresent(PostMapping.class)) {
|
||||||
} else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(GetMapping.class)) {
|
uri = method.getAnnotation(PostMapping.class).value();
|
||||||
GetMapping requestMapping = method.getAnnotation(GetMapping.class);
|
} else if (method.isAnnotationPresent(PutMapping.class)) {
|
||||||
String[] uri = requestMapping.value();
|
uri = method.getAnnotation(PutMapping.class).value();
|
||||||
ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
|
} else if (method.isAnnotationPresent(DeleteMapping.class)) {
|
||||||
} else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(PostMapping.class)) {
|
uri = method.getAnnotation(DeleteMapping.class).value();
|
||||||
PostMapping requestMapping = method.getAnnotation(PostMapping.class);
|
} else if (method.isAnnotationPresent(PatchMapping.class)) {
|
||||||
String[] uri = requestMapping.value();
|
uri = method.getAnnotation(PatchMapping.class).value();
|
||||||
ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
|
|
||||||
} else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(PutMapping.class)) {
|
|
||||||
PutMapping requestMapping = method.getAnnotation(PutMapping.class);
|
|
||||||
String[] uri = requestMapping.value();
|
|
||||||
ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
|
|
||||||
} else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(DeleteMapping.class)) {
|
|
||||||
DeleteMapping requestMapping = method.getAnnotation(DeleteMapping.class);
|
|
||||||
String[] uri = requestMapping.value();
|
|
||||||
ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
|
|
||||||
} else if (method.isAnnotationPresent(IgnoreAuth.class) && method.isAnnotationPresent(PatchMapping.class)) {
|
|
||||||
PatchMapping requestMapping = method.getAnnotation(PatchMapping.class);
|
|
||||||
String[] uri = requestMapping.value();
|
|
||||||
ignoreAuthUrls.addAll(rebuildUrl(baseUrl, uri));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ignoreAuthUrls;
|
return uri != null ? rebuildUrl(baseUrl, uri) : Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> rebuildUrl(String[] bases, String[] uris) {
|
private List<String> rebuildUrl(String[] bases, String[] uris) {
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
package org.jeecg.config.sign.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名校验注解
|
||||||
|
* 用于方法级别的签名验证,功能等同于yml中的jeecg.signUrls配置
|
||||||
|
* 参考DragSignatureAspect的设计思路,使用AOP切面实现
|
||||||
|
*
|
||||||
|
* @author GitHub Copilot
|
||||||
|
* @since 2025-12-15
|
||||||
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface SignatureCheck {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用签名校验
|
||||||
|
* @return true-启用(默认), false-禁用
|
||||||
|
*/
|
||||||
|
boolean enabled() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名校验失败时的错误消息
|
||||||
|
* @return 错误消息
|
||||||
|
*/
|
||||||
|
String errorMessage() default "Sign签名校验失败!";
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
package org.jeecg.config.sign.aspect;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.aspectj.lang.JoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Before;
|
||||||
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import org.jeecg.config.sign.annotation.SignatureCheck;
|
||||||
|
import org.jeecg.config.sign.interceptor.SignAuthInterceptor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于AOP的签名验证切面
|
||||||
|
* 复用SignAuthInterceptor的成熟签名验证逻辑
|
||||||
|
*
|
||||||
|
* @author GitHub Copilot
|
||||||
|
* @since 2025-12-15
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Slf4j
|
||||||
|
@Component("signatureCheckAspect")
|
||||||
|
public class SignatureCheckAspect {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复用SignAuthInterceptor的签名验证逻辑
|
||||||
|
*/
|
||||||
|
private final SignAuthInterceptor signAuthInterceptor = new SignAuthInterceptor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验签切点:拦截所有标记了@SignatureCheck注解的方法
|
||||||
|
*/
|
||||||
|
@Pointcut("@annotation(org.jeecg.config.sign.annotation.SignatureCheck)")
|
||||||
|
private void signatureCheckPointCut() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始验签
|
||||||
|
*/
|
||||||
|
@Before("signatureCheckPointCut()")
|
||||||
|
public void doSignatureValidation(JoinPoint point) throws Exception {
|
||||||
|
// 获取方法上的注解
|
||||||
|
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||||
|
Method method = signature.getMethod();
|
||||||
|
SignatureCheck signatureCheck = method.getAnnotation(SignatureCheck.class);
|
||||||
|
|
||||||
|
log.info("AOP签名验证: {}.{}", method.getDeclaringClass().getSimpleName(), method.getName());
|
||||||
|
|
||||||
|
// 如果注解被禁用,直接返回
|
||||||
|
if (!signatureCheck.enabled()) {
|
||||||
|
log.info("签名验证已禁用,跳过");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update-begin---author:sjlei---date:20260115 for: 查找带有@RequestBody注解的参数,解决签名校验时读取请求体为空的问题
|
||||||
|
Object bodyParam = null;
|
||||||
|
Object[] args = point.getArgs();
|
||||||
|
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
Object arg = args[i];
|
||||||
|
Annotation[] annotations = parameterAnnotations[i];
|
||||||
|
boolean hasRequestBodyAnnotation = Arrays.stream(annotations).anyMatch(annotation -> annotation.annotationType().equals(RequestBody.class));
|
||||||
|
if (hasRequestBodyAnnotation) {
|
||||||
|
// 捕获携带@RequestBody注解的参数,供签名校验使用
|
||||||
|
bodyParam = arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update-end-----author:sjlei---date:20260115 for: 查找带有@RequestBody注解的参数,解决签名校验时读取请求体为空的问题
|
||||||
|
|
||||||
|
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||||
|
if (attributes == null) {
|
||||||
|
log.error("无法获取请求上下文");
|
||||||
|
throw new IllegalArgumentException("无法获取请求上下文");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpServletRequest request = attributes.getRequest();
|
||||||
|
log.info("X-SIGN: {}, X-TIMESTAMP: {}", request.getHeader("X-SIGN"), request.getHeader("X-TIMESTAMP"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 直接调用SignAuthInterceptor的验证逻辑
|
||||||
|
signAuthInterceptor.validateSignature(request, bodyParam);
|
||||||
|
log.info("AOP签名验证通过");
|
||||||
|
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// 使用注解中配置的错误消息,或者保留原始错误消息
|
||||||
|
String errorMessage = signatureCheck.errorMessage();
|
||||||
|
log.error("AOP签名验证失败: {}", e.getMessage());
|
||||||
|
|
||||||
|
if ("Sign签名校验失败!".equals(errorMessage)) {
|
||||||
|
// 如果是默认错误消息,使用原始的详细错误信息
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
// 如果是自定义错误消息,使用自定义消息
|
||||||
|
throw new IllegalArgumentException(errorMessage, e);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 包装其他异常
|
||||||
|
String errorMessage = signatureCheck.errorMessage();
|
||||||
|
log.error("AOP签名验证异常: {}", e.getMessage());
|
||||||
|
throw new IllegalArgumentException(errorMessage, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package org.jeecg.config.sign.interceptor;
|
package org.jeecg.config.sign.interceptor;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.jeecg.common.constant.TenantConstant;
|
||||||
import org.jeecg.common.util.PathMatcherUtil;
|
import org.jeecg.common.util.PathMatcherUtil;
|
||||||
import org.jeecg.config.JeecgBaseConfig;
|
import org.jeecg.config.JeecgBaseConfig;
|
||||||
import org.jeecg.config.filter.RequestBodyReserveFilter;
|
import org.jeecg.config.filter.RequestBodyReserveFilter;
|
||||||
@ -41,7 +42,7 @@ public class SignAuthConfiguration implements WebMvcConfigurer {
|
|||||||
registry.addInterceptor(signAuthInterceptor()).addPathPatterns(signUrlsArray);
|
registry.addInterceptor(signAuthInterceptor()).addPathPatterns(signUrlsArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
//update-begin-author:taoyan date:20220427 for: issues/I53J5E post请求X_SIGN签名拦截校验后报错, request body 为空
|
// 代码逻辑说明: issues/I53J5E post请求X_SIGN签名拦截校验后报错, request body 为空
|
||||||
@Bean
|
@Bean
|
||||||
public RequestBodyReserveFilter requestBodyReserveFilter(){
|
public RequestBodyReserveFilter requestBodyReserveFilter(){
|
||||||
return new RequestBodyReserveFilter();
|
return new RequestBodyReserveFilter();
|
||||||
@ -64,8 +65,9 @@ public class SignAuthConfiguration implements WebMvcConfigurer {
|
|||||||
//------------------------------------------------------------
|
//------------------------------------------------------------
|
||||||
// 建议此处只添加post请求地址而不是所有的都需要走过滤器
|
// 建议此处只添加post请求地址而不是所有的都需要走过滤器
|
||||||
registration.addUrlPatterns(signUrlsArray);
|
registration.addUrlPatterns(signUrlsArray);
|
||||||
|
// 增加注解签名请求
|
||||||
|
registration.addUrlPatterns(TenantConstant.SIGNATURE_CHECK_POST_URL);
|
||||||
return registration;
|
return registration;
|
||||||
}
|
}
|
||||||
//update-end-author:taoyan date:20220427 for: issues/I53J5E post请求X_SIGN签名拦截校验后报错, request body 为空
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,63 +33,104 @@ public class SignAuthInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
log.info("Sign Interceptor request URI = " + request.getRequestURI());
|
log.info("签名拦截器 Interceptor request URI = " + request.getRequestURI());
|
||||||
HttpServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
|
|
||||||
//获取全部参数(包括URL和body上的)
|
|
||||||
SortedMap<String, String> allParams = HttpUtils.getAllParams(requestWrapper);
|
|
||||||
//对参数进行签名验证
|
|
||||||
String headerSign = request.getHeader(CommonConstant.X_SIGN);
|
|
||||||
String xTimestamp = request.getHeader(CommonConstant.X_TIMESTAMP);
|
|
||||||
|
|
||||||
if(oConvertUtils.isEmpty(xTimestamp)){
|
try {
|
||||||
Result<?> result = Result.error("Sign签名校验失败,时间戳为空!");
|
// 调用验证逻辑
|
||||||
log.error("Sign 签名校验失败!Header xTimestamp 为空");
|
validateSignature(request);
|
||||||
//校验失败返回前端
|
|
||||||
response.setCharacterEncoding("UTF-8");
|
|
||||||
response.setContentType("application/json; charset=utf-8");
|
|
||||||
PrintWriter out = response.getWriter();
|
|
||||||
out.print(JSON.toJSON(result));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//客户端时间
|
|
||||||
Long clientTimestamp = Long.parseLong(xTimestamp);
|
|
||||||
|
|
||||||
int length = 14;
|
|
||||||
int length1000 = 1000;
|
|
||||||
//1.校验签名时间(兼容X_TIMESTAMP的新老格式)
|
|
||||||
if (xTimestamp.length() == length) {
|
|
||||||
//a. X_TIMESTAMP格式是 yyyyMMddHHmmss (例子:20220308152143)
|
|
||||||
if ((DateUtils.getCurrentTimestamp() - clientTimestamp) > MAX_EXPIRE) {
|
|
||||||
log.error("签名验证失败:X-TIMESTAMP已过期,注意系统时间和服务器时间是否有误差!");
|
|
||||||
throw new IllegalArgumentException("签名验证失败:X-TIMESTAMP已过期");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//b. X_TIMESTAMP格式是 时间戳 (例子:1646552406000)
|
|
||||||
if ((System.currentTimeMillis() - clientTimestamp) > (MAX_EXPIRE * length1000)) {
|
|
||||||
log.error("签名验证失败:X-TIMESTAMP已过期,注意系统时间和服务器时间是否有误差!");
|
|
||||||
throw new IllegalArgumentException("签名验证失败:X-TIMESTAMP已过期");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//2.校验签名
|
|
||||||
boolean isSigned = SignUtil.verifySign(allParams,headerSign);
|
|
||||||
|
|
||||||
if (isSigned) {
|
|
||||||
log.debug("Sign 签名通过!Header Sign : {}",headerSign);
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} catch (IllegalArgumentException e) {
|
||||||
log.info("sign allParams: {}", allParams);
|
// 验证失败,返回错误响应
|
||||||
log.error("request URI = " + request.getRequestURI());
|
log.error("Sign 签名校验失败!{}", e.getMessage());
|
||||||
log.error("Sign 签名校验失败!Header Sign : {}",headerSign);
|
|
||||||
//校验失败返回前端
|
|
||||||
response.setCharacterEncoding("UTF-8");
|
response.setCharacterEncoding("UTF-8");
|
||||||
response.setContentType("application/json; charset=utf-8");
|
response.setContentType("application/json; charset=utf-8");
|
||||||
PrintWriter out = response.getWriter();
|
PrintWriter out = response.getWriter();
|
||||||
Result<?> result = Result.error("Sign签名校验失败!");
|
Result<?> result = Result.error(e.getMessage());
|
||||||
out.print(JSON.toJSON(result));
|
out.print(JSON.toJSON(result));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名验证核心逻辑
|
||||||
|
* 提取出来供AOP切面复用
|
||||||
|
* @param request HTTP请求
|
||||||
|
* @throws IllegalArgumentException 验证失败时抛出异常
|
||||||
|
*/
|
||||||
|
public void validateSignature(HttpServletRequest request) throws IllegalArgumentException {
|
||||||
|
validateSignature(request, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名验证核心逻辑
|
||||||
|
* 提取出来供AOP切面复用
|
||||||
|
* @param request HTTP请求
|
||||||
|
* @throws IllegalArgumentException 验证失败时抛出异常
|
||||||
|
*/
|
||||||
|
public void validateSignature(HttpServletRequest request, Object bodyParam) throws IllegalArgumentException {
|
||||||
|
try {
|
||||||
|
log.debug("开始签名验证: {} {}", request.getMethod(), request.getRequestURI());
|
||||||
|
|
||||||
|
HttpServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
|
||||||
|
//获取全部参数(包括URL和body上的)
|
||||||
|
SortedMap<String, String> allParams = HttpUtils.getAllParams(requestWrapper, bodyParam);
|
||||||
|
log.debug("提取参数: {}", allParams);
|
||||||
|
|
||||||
|
//对参数进行签名验证
|
||||||
|
String headerSign = request.getHeader(CommonConstant.X_SIGN);
|
||||||
|
String xTimestamp = request.getHeader(CommonConstant.X_TIMESTAMP);
|
||||||
|
|
||||||
|
if(oConvertUtils.isEmpty(xTimestamp)){
|
||||||
|
log.error("Sign签名校验失败,时间戳为空!");
|
||||||
|
throw new IllegalArgumentException("Sign签名校验失败,请求参数不完整!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//客户端时间
|
||||||
|
Long clientTimestamp = Long.parseLong(xTimestamp);
|
||||||
|
|
||||||
|
int length = 14;
|
||||||
|
int length1000 = 1000;
|
||||||
|
//1.校验签名时间(兼容X_TIMESTAMP的新老格式)
|
||||||
|
if (xTimestamp.length() == length) {
|
||||||
|
//a. X_TIMESTAMP格式是 yyyyMMddHHmmss (例子:20220308152143)
|
||||||
|
long currentTimestamp = DateUtils.getCurrentTimestamp();
|
||||||
|
long timeDiff = currentTimestamp - clientTimestamp;
|
||||||
|
log.debug("时间戳验证(yyyyMMddHHmmss): 时间差{}秒", timeDiff);
|
||||||
|
|
||||||
|
if (timeDiff > MAX_EXPIRE) {
|
||||||
|
log.error("时间戳已过期: {}秒 > {}秒", timeDiff, MAX_EXPIRE);
|
||||||
|
throw new IllegalArgumentException("签名验证失败,请求时效性验证失败!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//b. X_TIMESTAMP格式是 时间戳 (例子:1646552406000)
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
long timeDiff = currentTime - clientTimestamp;
|
||||||
|
long maxExpireMs = MAX_EXPIRE * length1000;
|
||||||
|
log.debug("时间戳验证(Unix): 时间差{}ms", timeDiff);
|
||||||
|
|
||||||
|
if (timeDiff > maxExpireMs) {
|
||||||
|
log.error("时间戳已过期: {}ms > {}ms", timeDiff, maxExpireMs);
|
||||||
|
throw new IllegalArgumentException("签名验证失败,请求时效性验证失败!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//2.校验签名
|
||||||
|
boolean isSigned = SignUtil.verifySign(allParams,headerSign);
|
||||||
|
|
||||||
|
if (isSigned) {
|
||||||
|
log.debug("签名验证通过");
|
||||||
|
} else {
|
||||||
|
log.error("签名验证失败, 参数: {}", allParams);
|
||||||
|
throw new IllegalArgumentException("Sign签名校验失败!");
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// 重新抛出签名验证异常
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 包装其他异常(如IOException)
|
||||||
|
log.error("签名验证异常: {}", e.getMessage());
|
||||||
|
throw new IllegalArgumentException("Sign签名校验失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,25 +35,25 @@ public class HttpUtils {
|
|||||||
* @date 20210621
|
* @date 20210621
|
||||||
* @param request
|
* @param request
|
||||||
*/
|
*/
|
||||||
public static SortedMap<String, String> getAllParams(HttpServletRequest request) throws IOException {
|
public static SortedMap<String, String> getAllParams(HttpServletRequest request, Object bodyParam) throws IOException {
|
||||||
|
|
||||||
SortedMap<String, String> result = new TreeMap<>();
|
SortedMap<String, String> result = new TreeMap<>();
|
||||||
// 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username
|
// 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username
|
||||||
String pathVariable = request.getRequestURI().substring(request.getRequestURI().lastIndexOf("/") + 1);
|
String pathVariable = request.getRequestURI().substring(request.getRequestURI().lastIndexOf("/") + 1);
|
||||||
if (pathVariable.contains(SymbolConstant.COMMA)) {
|
if (pathVariable.contains(SymbolConstant.COMMA)) {
|
||||||
log.info(" pathVariable: {}",pathVariable);
|
log.debug(" pathVariable: {}",pathVariable);
|
||||||
String deString = URLDecoder.decode(pathVariable, "UTF-8");
|
String deString = URLDecoder.decode(pathVariable, "UTF-8");
|
||||||
|
|
||||||
//https://www.52dianzi.com/category/article/37/565371.html
|
//https://www.52dianzi.com/category/article/37/565371.html
|
||||||
if(deString.contains("%")){
|
if(deString.contains("%")){
|
||||||
try {
|
try {
|
||||||
deString = URLDecoder.decode(deString, "UTF-8");
|
deString = URLDecoder.decode(deString, "UTF-8");
|
||||||
log.info("存在%情况下,执行两次解码 — pathVariable decode: {}",deString);
|
log.debug("存在%情况下,执行两次解码 — pathVariable decode: {}",deString);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
//e.printStackTrace();
|
//e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info(" pathVariable decode: {}",deString);
|
log.debug(" pathVariable decode: {}",deString);
|
||||||
result.put(SignUtil.X_PATH_VARIABLE, deString);
|
result.put(SignUtil.X_PATH_VARIABLE, deString);
|
||||||
}
|
}
|
||||||
// 获取URL上的参数
|
// 获取URL上的参数
|
||||||
@ -65,7 +65,13 @@ public class HttpUtils {
|
|||||||
Map<String, String> allRequestParam = new HashMap<>(16);
|
Map<String, String> allRequestParam = new HashMap<>(16);
|
||||||
// get请求不需要拿body参数
|
// get请求不需要拿body参数
|
||||||
if (!HttpMethod.GET.name().equals(request.getMethod())) {
|
if (!HttpMethod.GET.name().equals(request.getMethod())) {
|
||||||
allRequestParam = getAllRequestParam(request);
|
if (bodyParam != null) {
|
||||||
|
// update-begin---author:sjlei---date:20260115 for: 解决签名校验时读取请求体为空的问题
|
||||||
|
allRequestParam = JSONObject.parseObject(JSONObject.toJSONString(bodyParam), Map.class);
|
||||||
|
// update-end-----author:sjlei---date:20260115 for: 解决签名校验时读取请求体为空的问题
|
||||||
|
} else {
|
||||||
|
allRequestParam = getAllRequestParam(request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 将URL的参数和body参数进行合并
|
// 将URL的参数和body参数进行合并
|
||||||
if (allRequestParam != null) {
|
if (allRequestParam != null) {
|
||||||
@ -91,15 +97,15 @@ public class HttpUtils {
|
|||||||
// 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username
|
// 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username
|
||||||
String pathVariable = url.substring(url.lastIndexOf("/") + 1);
|
String pathVariable = url.substring(url.lastIndexOf("/") + 1);
|
||||||
if (pathVariable.contains(SymbolConstant.COMMA)) {
|
if (pathVariable.contains(SymbolConstant.COMMA)) {
|
||||||
log.info(" pathVariable: {}",pathVariable);
|
log.debug(" pathVariable: {}",pathVariable);
|
||||||
String deString = URLDecoder.decode(pathVariable, "UTF-8");
|
String deString = URLDecoder.decode(pathVariable, "UTF-8");
|
||||||
|
|
||||||
//https://www.52dianzi.com/category/article/37/565371.html
|
//https://www.52dianzi.com/category/article/37/565371.html
|
||||||
if(deString.contains("%")){
|
if(deString.contains("%")){
|
||||||
deString = URLDecoder.decode(deString, "UTF-8");
|
deString = URLDecoder.decode(deString, "UTF-8");
|
||||||
log.info("存在%情况下,执行两次解码 — pathVariable decode: {}",deString);
|
log.debug("存在%情况下,执行两次解码 — pathVariable decode: {}",deString);
|
||||||
}
|
}
|
||||||
log.info(" pathVariable decode: {}",deString);
|
log.debug(" pathVariable decode: {}",deString);
|
||||||
result.put(SignUtil.X_PATH_VARIABLE, deString);
|
result.put(SignUtil.X_PATH_VARIABLE, deString);
|
||||||
}
|
}
|
||||||
// 获取URL上的参数
|
// 获取URL上的参数
|
||||||
@ -174,11 +180,10 @@ public class HttpUtils {
|
|||||||
String[] params = param.split("&");
|
String[] params = param.split("&");
|
||||||
for (String s : params) {
|
for (String s : params) {
|
||||||
int index = s.indexOf("=");
|
int index = s.indexOf("=");
|
||||||
//update-begin---author:chenrui ---date:20240222 for:[issues/5879]数据查询传ds=“”造成的异常------------
|
// 代码逻辑说明: [issues/5879]数据查询传ds=“”造成的异常------------
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
result.put(s.substring(0, index), s.substring(index + 1));
|
result.put(s.substring(0, index), s.substring(index + 1));
|
||||||
}
|
}
|
||||||
//update-end---author:chenrui ---date:20240222 for:[issues/5879]数据查询传ds=“”造成的异常------------
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -202,11 +207,10 @@ public class HttpUtils {
|
|||||||
String[] params = param.split("&");
|
String[] params = param.split("&");
|
||||||
for (String s : params) {
|
for (String s : params) {
|
||||||
int index = s.indexOf("=");
|
int index = s.indexOf("=");
|
||||||
//update-begin---author:chenrui ---date:20240222 for:[issues/5879]数据查询传ds=“”造成的异常------------
|
// 代码逻辑说明: [issues/5879]数据查询传ds=“”造成的异常------------
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
result.put(s.substring(0, index), s.substring(index + 1));
|
result.put(s.substring(0, index), s.substring(index + 1));
|
||||||
}
|
}
|
||||||
//update-end---author:chenrui ---date:20240222 for:[issues/5879]数据查询传ds=“”造成的异常------------
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,12 @@ import org.springframework.util.DigestUtils;
|
|||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 签名工具类
|
* 签名工具类
|
||||||
@ -34,7 +39,7 @@ public class SignUtil {
|
|||||||
}
|
}
|
||||||
// 把参数加密
|
// 把参数加密
|
||||||
String paramsSign = getParamsSign(params);
|
String paramsSign = getParamsSign(params);
|
||||||
log.info("Param Sign : {}", paramsSign);
|
log.debug("Param Sign : {}", paramsSign);
|
||||||
return !StringUtils.isEmpty(paramsSign) && headerSign.equals(paramsSign);
|
return !StringUtils.isEmpty(paramsSign) && headerSign.equals(paramsSign);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,14 +52,9 @@ public class SignUtil {
|
|||||||
//去掉 Url 里的时间戳
|
//去掉 Url 里的时间戳
|
||||||
params.remove("_t");
|
params.remove("_t");
|
||||||
String paramsJsonStr = JSONObject.toJSONString(params);
|
String paramsJsonStr = JSONObject.toJSONString(params);
|
||||||
log.info("Param paramsJsonStr : {}", paramsJsonStr);
|
log.debug("Param paramsJsonStr : {}", paramsJsonStr);
|
||||||
//设置签名秘钥
|
//设置签名秘钥
|
||||||
JeecgBaseConfig jeecgBaseConfig = SpringContextUtils.getBean(JeecgBaseConfig.class);
|
String signatureSecret = SignUtil.getSignatureSecret();
|
||||||
String signatureSecret = jeecgBaseConfig.getSignatureSecret();
|
|
||||||
String curlyBracket = SymbolConstant.DOLLAR + SymbolConstant.LEFT_CURLY_BRACKET;
|
|
||||||
if(oConvertUtils.isEmpty(signatureSecret) || signatureSecret.contains(curlyBracket)){
|
|
||||||
throw new JeecgBootException("签名密钥 ${jeecg.signatureSecret} 缺少配置 !!");
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
//【issues/I484RW】2.4.6部署后,下拉搜索框提示“sign签名检验失败”
|
//【issues/I484RW】2.4.6部署后,下拉搜索框提示“sign签名检验失败”
|
||||||
return DigestUtils.md5DigestAsHex((paramsJsonStr + signatureSecret).getBytes("UTF-8")).toUpperCase();
|
return DigestUtils.md5DigestAsHex((paramsJsonStr + signatureSecret).getBytes("UTF-8")).toUpperCase();
|
||||||
@ -63,4 +63,129 @@ public class SignUtil {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* 通过前端签名算法生成签名
|
||||||
|
*
|
||||||
|
* @param url 请求的完整URL(包含查询参数)
|
||||||
|
* @param requestParams 使用 @RequestParam 获取的参数集合
|
||||||
|
* @param requestBodyParams 使用 @RequestBody 获取的参数集合
|
||||||
|
* @return 计算得到的签名(大写MD5),若参数不足返回 null
|
||||||
|
*/
|
||||||
|
public static String generateRequestSign(String url, Map<String, Object> requestParams, Map<String, Object> requestBodyParams) {
|
||||||
|
if (oConvertUtils.isEmpty(url)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 解析URL上的查询参数与路径变量
|
||||||
|
Map<String, String> urlParams = parseQueryString(url);
|
||||||
|
// 合并URL参数与@RequestParam参数,确保数值和布尔类型转换为字符串
|
||||||
|
Map<String, String> mergedParams = mergeObject(urlParams, requestParams);
|
||||||
|
// 按需合并@RequestBody参数
|
||||||
|
if (requestBodyParams != null && !requestBodyParams.isEmpty()) {
|
||||||
|
mergedParams = mergeObject(mergedParams, requestBodyParams);
|
||||||
|
}
|
||||||
|
// 按键名升序排序,保持与前端一致的签名顺序
|
||||||
|
SortedMap<String, String> sortedParams = new TreeMap<>(mergedParams);
|
||||||
|
// 去除时间戳字段,避免参与签名
|
||||||
|
sortedParams.remove("_t");
|
||||||
|
// 序列化为JSON字符串
|
||||||
|
String paramsJsonStr = JSONObject.toJSONString(sortedParams);
|
||||||
|
// 读取签名秘钥
|
||||||
|
String signatureSecret = getSignatureSecret();
|
||||||
|
// 计算MD5摘要并转大写
|
||||||
|
return DigestUtils.md5DigestAsHex((paramsJsonStr + signatureSecret).getBytes(StandardCharsets.UTF_8)).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析URL中的查询参数,并处理末尾逗号分隔的路径变量片段。
|
||||||
|
*
|
||||||
|
* @param url 请求的完整URL
|
||||||
|
* @return 解析后的参数映射,数值与布尔类型均转换为字符串
|
||||||
|
*/
|
||||||
|
private static Map<String, String> parseQueryString(String url) {
|
||||||
|
Map<String, String> result = new HashMap<>(16);
|
||||||
|
int fragmentIndex = url.indexOf('#');
|
||||||
|
if (fragmentIndex >= 0) {
|
||||||
|
url = url.substring(0, fragmentIndex);
|
||||||
|
}
|
||||||
|
int questionIndex = url.indexOf('?');
|
||||||
|
String paramString = null;
|
||||||
|
if (questionIndex >= 0 && questionIndex < url.length() - 1) {
|
||||||
|
paramString = url.substring(questionIndex + 1);
|
||||||
|
}
|
||||||
|
// 处理路径变量末尾以逗号分隔的段,例如 /sys/dict/getDictItems/sys_user,realname,username
|
||||||
|
int lastSlashIndex = url.lastIndexOf(SymbolConstant.SINGLE_SLASH);
|
||||||
|
if (lastSlashIndex >= 0 && lastSlashIndex < url.length() - 1) {
|
||||||
|
String lastPathVariable = url.substring(lastSlashIndex + 1);
|
||||||
|
int qIndexInPath = lastPathVariable.indexOf('?');
|
||||||
|
if (qIndexInPath >= 0) {
|
||||||
|
lastPathVariable = lastPathVariable.substring(0, qIndexInPath);
|
||||||
|
}
|
||||||
|
if (lastPathVariable.contains(SymbolConstant.COMMA)) {
|
||||||
|
String decodedPathVariable = URLDecoder.decode(lastPathVariable, StandardCharsets.UTF_8);
|
||||||
|
result.put(X_PATH_VARIABLE, decodedPathVariable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oConvertUtils.isNotEmpty(paramString)) {
|
||||||
|
String[] pairs = paramString.split(SymbolConstant.AND);
|
||||||
|
for (String pair : pairs) {
|
||||||
|
int equalIndex = pair.indexOf('=');
|
||||||
|
if (equalIndex > 0 && equalIndex < pair.length() - 1) {
|
||||||
|
String key = pair.substring(0, equalIndex);
|
||||||
|
String value = pair.substring(equalIndex + 1);
|
||||||
|
// 解码并统一类型为字符串
|
||||||
|
String decodedKey = URLDecoder.decode(key, StandardCharsets.UTF_8);
|
||||||
|
String decodedValue = URLDecoder.decode(value, StandardCharsets.UTF_8);
|
||||||
|
result.put(decodedKey, decodedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并两个参数映射,并保证数值与布尔类型统一转为字符串。
|
||||||
|
*
|
||||||
|
* @param target 初始参数映射
|
||||||
|
* @param source 待合并的参数映射
|
||||||
|
* @return 合并后的新映射
|
||||||
|
*/
|
||||||
|
private static Map<String, String> mergeObject(Map<String, String> target, Map<String, Object> source) {
|
||||||
|
Map<String, String> merged = new HashMap<>(16);
|
||||||
|
if (target != null && !target.isEmpty()) {
|
||||||
|
merged.putAll(target);
|
||||||
|
}
|
||||||
|
if (source != null && !source.isEmpty()) {
|
||||||
|
for (Map.Entry<String, Object> entry : source.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
Object value = entry.getValue();
|
||||||
|
if (value instanceof Number) {
|
||||||
|
// 数值类型转字符串,保持前后端一致
|
||||||
|
merged.put(key, String.valueOf(value));
|
||||||
|
} else if (value instanceof Boolean) {
|
||||||
|
// 布尔类型转字符串,保持前后端一致
|
||||||
|
merged.put(key, String.valueOf(value));
|
||||||
|
} else if (value != null) {
|
||||||
|
merged.put(key, String.valueOf(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取并校验签名秘钥配置。
|
||||||
|
*
|
||||||
|
* @return 有效的签名秘钥
|
||||||
|
*/
|
||||||
|
private static String getSignatureSecret() {
|
||||||
|
JeecgBaseConfig jeecgBaseConfig = SpringContextUtils.getBean(JeecgBaseConfig.class);
|
||||||
|
String signatureSecret = jeecgBaseConfig.getSignatureSecret();
|
||||||
|
String curlyBracket = SymbolConstant.DOLLAR + SymbolConstant.LEFT_CURLY_BRACKET;
|
||||||
|
if (oConvertUtils.isEmpty(signatureSecret) || signatureSecret.contains(curlyBracket)) {
|
||||||
|
throw new JeecgBootException("签名密钥 ${jeecg.signatureSecret} 缺少配置 !!");
|
||||||
|
}
|
||||||
|
return signatureSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
package org.jeecg.config.tencent;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 腾讯短信配置
|
||||||
|
*
|
||||||
|
* @author: wangshuai
|
||||||
|
* @date: 2025/10/30 18:22
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class JeecgTencent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接入域名
|
||||||
|
*/
|
||||||
|
private String endpoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* api秘钥id
|
||||||
|
*/
|
||||||
|
private String secretId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* api秘钥key
|
||||||
|
*/
|
||||||
|
private String secretKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用id
|
||||||
|
*/
|
||||||
|
private String sdkAppId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地域信息
|
||||||
|
*/
|
||||||
|
private String region;
|
||||||
|
}
|
||||||
@ -19,11 +19,42 @@ public class Firewall {
|
|||||||
* 低代码模式(dev:开发模式,prod:发布模式——关闭所有在线开发配置能力)
|
* 低代码模式(dev:开发模式,prod:发布模式——关闭所有在线开发配置能力)
|
||||||
*/
|
*/
|
||||||
private String lowCodeMode;
|
private String lowCodeMode;
|
||||||
|
/**
|
||||||
|
* 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||||
|
*/
|
||||||
|
private Boolean isConcurrent = true;
|
||||||
|
/**
|
||||||
|
* 是否开启默认密码登录提醒(true 登录后提示必须修改默认密码)
|
||||||
|
*/
|
||||||
|
private Boolean enableDefaultPwdCheck = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否开启登录验证码校验(true 开启;false 关闭并跳过验证码逻辑)
|
||||||
|
*/
|
||||||
|
private Boolean enableLoginCaptcha = true;
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * 表字典安全模式(white:白名单——配置了白名单的表才能通过表字典方式访问,black:黑名单——配置了黑名单的表不允许表字典方式访问)
|
// * 表字典安全模式(white:白名单——配置了白名单的表才能通过表字典方式访问,black:黑名单——配置了黑名单的表不允许表字典方式访问)
|
||||||
// */
|
// */
|
||||||
// private String tableDictMode;
|
// private String tableDictMode;
|
||||||
|
|
||||||
|
|
||||||
|
public Boolean getEnableLoginCaptcha() {
|
||||||
|
return enableLoginCaptcha;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableLoginCaptcha(Boolean enableLoginCaptcha) {
|
||||||
|
this.enableLoginCaptcha = enableLoginCaptcha;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getEnableDefaultPwdCheck() {
|
||||||
|
return enableDefaultPwdCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableDefaultPwdCheck(Boolean enableDefaultPwdCheck) {
|
||||||
|
this.enableDefaultPwdCheck = enableDefaultPwdCheck;
|
||||||
|
}
|
||||||
|
|
||||||
public Boolean getDataSourceSafe() {
|
public Boolean getDataSourceSafe() {
|
||||||
return dataSourceSafe;
|
return dataSourceSafe;
|
||||||
}
|
}
|
||||||
@ -47,4 +78,12 @@ public class Firewall {
|
|||||||
public void setDisableSelectAll(Boolean disableSelectAll) {
|
public void setDisableSelectAll(Boolean disableSelectAll) {
|
||||||
this.disableSelectAll = disableSelectAll;
|
this.disableSelectAll = disableSelectAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getIsConcurrent() {
|
||||||
|
return isConcurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsConcurrent(Boolean isConcurrent) {
|
||||||
|
this.isConcurrent = isConcurrent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
package org.jeecg.config.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class JeecgMinio {
|
||||||
|
|
||||||
|
private String minio_url;
|
||||||
|
private String bucketName;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package org.jeecg.config.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class JeecgOSS {
|
||||||
|
|
||||||
|
private String endpoint;
|
||||||
|
private String bucketName;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
org.jeecg.config.DruidWallConfigRegister
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 0 B After Width: | Height: | Size: 487 B |
@ -0,0 +1,429 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
MCP Stdio 工具 - 修复编码问题
|
||||||
|
确保所有输出都使用UTF-8编码
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from typing import Dict, Any
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# 强制使用UTF-8编码
|
||||||
|
if sys.platform == "win32":
|
||||||
|
# Windows需要特殊处理
|
||||||
|
import io
|
||||||
|
|
||||||
|
sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||||
|
else:
|
||||||
|
# Unix-like系统
|
||||||
|
sys.stdin.reconfigure(encoding='utf-8')
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
sys.stderr.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
||||||
|
os.environ['PYTHONUTF8'] = '1'
|
||||||
|
|
||||||
|
# 配置日志
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
encoding='utf-8'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger("mcp-tool")
|
||||||
|
|
||||||
|
|
||||||
|
class FixedMCPServer:
|
||||||
|
"""修复编码问题的MCP服务器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.tools = {}
|
||||||
|
self.initialize_tools()
|
||||||
|
|
||||||
|
def initialize_tools(self):
|
||||||
|
"""初始化工具集"""
|
||||||
|
|
||||||
|
# 获取时间
|
||||||
|
self.tools["get_time"] = {
|
||||||
|
"name": "get_time",
|
||||||
|
"description": "获取当前时间",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"format": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "时间格式",
|
||||||
|
"enum": ["iso", "timestamp", "human", "chinese"],
|
||||||
|
"default": "iso"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 文本处理工具
|
||||||
|
self.tools["text_process"] = {
|
||||||
|
"name": "text_process",
|
||||||
|
"description": "文本处理工具",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "输入文本"
|
||||||
|
},
|
||||||
|
"operation": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "操作类型",
|
||||||
|
"enum": ["length", "upper", "lower", "reverse", "count_words"],
|
||||||
|
"default": "length"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["text"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 数据格式工具
|
||||||
|
self.tools["format_data"] = {
|
||||||
|
"name": "format_data",
|
||||||
|
"description": "格式化数据",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "原始数据"
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "格式类型",
|
||||||
|
"enum": ["json", "yaml", "xml"],
|
||||||
|
"default": "json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["data"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""处理请求"""
|
||||||
|
try:
|
||||||
|
method = request.get("method")
|
||||||
|
params = request.get("params", {})
|
||||||
|
|
||||||
|
if method == "tools/list":
|
||||||
|
return self.handle_tools_list()
|
||||||
|
elif method == "tools/call":
|
||||||
|
return self.handle_tool_call(params)
|
||||||
|
elif method == "ping":
|
||||||
|
return {"result": "pong"}
|
||||||
|
else:
|
||||||
|
return self.create_error_response(
|
||||||
|
code=-32601,
|
||||||
|
message="Method not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling request: {e}")
|
||||||
|
return self.create_error_response(
|
||||||
|
code=-32603,
|
||||||
|
message=f"Internal error: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_tools_list(self) -> Dict[str, Any]:
|
||||||
|
"""列出所有工具 - 确保返回标准JSON"""
|
||||||
|
return {
|
||||||
|
"result": {
|
||||||
|
"tools": list(self.tools.values())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_tool_call(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""调用工具 - 修复响应格式"""
|
||||||
|
name = params.get("name")
|
||||||
|
arguments = params.get("arguments", {})
|
||||||
|
|
||||||
|
if name not in self.tools:
|
||||||
|
return self.create_error_response(
|
||||||
|
code=-32602,
|
||||||
|
message=f"Tool '{name}' not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if name == "get_time":
|
||||||
|
result = self.execute_get_time(arguments)
|
||||||
|
elif name == "text_process":
|
||||||
|
result = self.execute_text_process(arguments)
|
||||||
|
elif name == "format_data":
|
||||||
|
result = self.execute_format_data(arguments)
|
||||||
|
else:
|
||||||
|
return self.create_error_response(
|
||||||
|
code=-32602,
|
||||||
|
message="Tool not implemented"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 确保返回正确的MCP响应格式
|
||||||
|
return self.create_success_response(result)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Tool execution error: {e}")
|
||||||
|
return self.create_error_response(
|
||||||
|
code=-32603,
|
||||||
|
message=f"Tool execution failed: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def execute_get_time(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""获取时间 - 支持中文"""
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
try:
|
||||||
|
format_type = args.get("format", "iso")
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
if format_type == "iso":
|
||||||
|
result = now.isoformat()
|
||||||
|
elif format_type == "timestamp":
|
||||||
|
result = now.timestamp()
|
||||||
|
elif format_type == "human":
|
||||||
|
result = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
elif format_type == "chinese":
|
||||||
|
result = now.strftime("%Y年%m月%d日 %H时%M分%S秒")
|
||||||
|
else:
|
||||||
|
result = now.isoformat()
|
||||||
|
logger.info(f"当前系统时间:{result}")
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"format": format_type,
|
||||||
|
"time": result,
|
||||||
|
"timestamp": now.timestamp(),
|
||||||
|
"date": now.strftime("%Y-%m-%d"),
|
||||||
|
"time_12h": now.strftime("%I:%M:%S %p")
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
def execute_text_process(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""文本处理"""
|
||||||
|
try:
|
||||||
|
text = args.get("text", "")
|
||||||
|
operation = args.get("operation", "length")
|
||||||
|
|
||||||
|
if operation == "length":
|
||||||
|
result = len(text)
|
||||||
|
result_str = f"文本长度: {result} 个字符"
|
||||||
|
elif operation == "upper":
|
||||||
|
result = text.upper()
|
||||||
|
result_str = f"大写: {result}"
|
||||||
|
elif operation == "lower":
|
||||||
|
result = text.lower()
|
||||||
|
result_str = f"小写: {result}"
|
||||||
|
elif operation == "reverse":
|
||||||
|
result = text[::-1]
|
||||||
|
result_str = f"反转: {result}"
|
||||||
|
elif operation == "count_words":
|
||||||
|
words = len(text.split())
|
||||||
|
result = words
|
||||||
|
result_str = f"单词数: {words}"
|
||||||
|
else:
|
||||||
|
raise ValueError(f"未知操作: {operation}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"operation": operation,
|
||||||
|
"original_text": text,
|
||||||
|
"result": result,
|
||||||
|
"result_str": result_str,
|
||||||
|
"text_length": len(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error": str(e),
|
||||||
|
"operation": args.get("operation", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
def execute_format_data(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""格式化数据"""
|
||||||
|
try:
|
||||||
|
data_str = args.get("data", "")
|
||||||
|
format_type = args.get("format", "json")
|
||||||
|
|
||||||
|
# 尝试解析为JSON
|
||||||
|
try:
|
||||||
|
data = json.loads(data_str)
|
||||||
|
is_json = True
|
||||||
|
except:
|
||||||
|
data = data_str
|
||||||
|
is_json = False
|
||||||
|
|
||||||
|
if format_type == "json":
|
||||||
|
if is_json:
|
||||||
|
result = json.dumps(data, ensure_ascii=False, indent=2)
|
||||||
|
else:
|
||||||
|
# 如果不是JSON,包装成JSON
|
||||||
|
result = json.dumps({"text": data}, ensure_ascii=False, indent=2)
|
||||||
|
elif format_type == "yaml":
|
||||||
|
import yaml
|
||||||
|
result = yaml.dump(data, allow_unicode=True, default_flow_style=False)
|
||||||
|
elif format_type == "xml":
|
||||||
|
# 简单的XML格式化
|
||||||
|
if isinstance(data, dict):
|
||||||
|
result = "<data>"
|
||||||
|
for k, v in data.items():
|
||||||
|
result += f"\n <{k}>{v}</{k}>"
|
||||||
|
result += "\n</data>"
|
||||||
|
else:
|
||||||
|
result = f"<text>{data}</text>"
|
||||||
|
else:
|
||||||
|
result = str(data)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"format": format_type,
|
||||||
|
"original": data_str,
|
||||||
|
"formatted": result,
|
||||||
|
"length": len(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error": str(e),
|
||||||
|
"format": args.get("format", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
def create_success_response(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""创建成功响应 - 确保符合MCP规范"""
|
||||||
|
# 将数据转换为JSON字符串作为文本内容
|
||||||
|
content_text = json.dumps(data, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"result": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": content_text
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isError": False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def create_error_response(self, code: int, message: str) -> Dict[str, Any]:
|
||||||
|
"""创建错误响应"""
|
||||||
|
return {
|
||||||
|
"error": {
|
||||||
|
"code": code,
|
||||||
|
"message": message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def safe_json_dump(data: Dict[str, Any]) -> str:
|
||||||
|
"""安全的JSON序列化,确保UTF-8编码"""
|
||||||
|
try:
|
||||||
|
return json.dumps(data, ensure_ascii=False, separators=(',', ':'))
|
||||||
|
except:
|
||||||
|
# 如果失败,使用ASCII转义
|
||||||
|
return json.dumps(data, ensure_ascii=True, separators=(',', ':'))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数 - 修复Stdio通信"""
|
||||||
|
logger.info("启动MCP Stdio服务器 (修复编码版)...")
|
||||||
|
|
||||||
|
server = FixedMCPServer()
|
||||||
|
|
||||||
|
# 初始握手消息
|
||||||
|
init_message = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"result": {
|
||||||
|
"protocolVersion": "2024-11-05",
|
||||||
|
"capabilities": {
|
||||||
|
"tools": {}
|
||||||
|
},
|
||||||
|
"serverInfo": {
|
||||||
|
"name": "fixed-mcp-server",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 发送初始化响应
|
||||||
|
try:
|
||||||
|
sys.stdout.write(safe_json_dump(init_message) + "\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"发送初始化消息失败: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("MCP服务器已初始化")
|
||||||
|
|
||||||
|
# 主循环
|
||||||
|
line_num = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
line = sys.stdin.readline()
|
||||||
|
if not line:
|
||||||
|
logger.info("输入流结束")
|
||||||
|
break
|
||||||
|
|
||||||
|
line = line.strip()
|
||||||
|
line_num += 1
|
||||||
|
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f"收到第 {line_num} 行: {line[:100]}...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
request = json.loads(line)
|
||||||
|
logger.info(f"解析请求: {request.get('method', 'unknown')}")
|
||||||
|
|
||||||
|
# 处理请求
|
||||||
|
response = server.handle_request(request)
|
||||||
|
response["jsonrpc"] = "2.0"
|
||||||
|
response["id"] = request.get("id")
|
||||||
|
|
||||||
|
# 发送响应
|
||||||
|
response_json = safe_json_dump(response)
|
||||||
|
sys.stdout.write(response_json + "\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
logger.info(f"发送响应: {response.get('result', response.get('error', {}))}")
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.error(f"JSON解析错误: {e}")
|
||||||
|
error_response = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"error": {
|
||||||
|
"code": -32700,
|
||||||
|
"message": f"Parse error at line {line_num}"
|
||||||
|
},
|
||||||
|
"id": None
|
||||||
|
}
|
||||||
|
sys.stdout.write(safe_json_dump(error_response) + "\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("接收到中断信号")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"未处理的错误: {e}")
|
||||||
|
break
|
||||||
|
|
||||||
|
logger.info("MCP服务器已停止")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.jeecgframework.boot3</groupId>
|
<groupId>org.jeecgframework.boot3</groupId>
|
||||||
<artifactId>jeecg-boot-module</artifactId>
|
<artifactId>jeecg-boot-module</artifactId>
|
||||||
<version>3.8.3</version>
|
<version>3.9.1</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>jeecg-boot-module-airag</artifactId>
|
<artifactId>jeecg-boot-module-airag</artifactId>
|
||||||
@ -31,7 +31,9 @@
|
|||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<apache-tika.version>2.9.1</apache-tika.version>
|
<kotlin.version>2.2.0</kotlin.version>
|
||||||
|
<liteflow.version>2.15.0</liteflow.version>
|
||||||
|
<apache-tika.version>3.2.3</apache-tika.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@ -39,14 +41,14 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.langchain4j</groupId>
|
<groupId>dev.langchain4j</groupId>
|
||||||
<artifactId>langchain4j-bom</artifactId>
|
<artifactId>langchain4j-bom</artifactId>
|
||||||
<version>1.3.0</version>
|
<version>1.9.1</version>
|
||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.langchain4j</groupId>
|
<groupId>dev.langchain4j</groupId>
|
||||||
<artifactId>langchain4j-community-bom</artifactId>
|
<artifactId>langchain4j-community-bom</artifactId>
|
||||||
<version>1.3.0-beta9</version>
|
<version>1.9.1-beta17</version>
|
||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
@ -73,10 +75,24 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.boot3</groupId>
|
<groupId>org.jeecgframework.boot3</groupId>
|
||||||
<artifactId>jeecg-aiflow</artifactId>
|
<artifactId>jeecg-aiflow</artifactId>
|
||||||
<version>3.8.3.1</version>
|
<version>3.9.1-beta</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-beanutils</groupId>
|
||||||
|
<artifactId>commons-beanutils</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.yomahub</groupId>
|
||||||
|
<artifactId>liteflow-script-python</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- beigin 这两个依赖太多每个包50M左右,如果你发布需要使用,请把<scope>provided</scope>删掉 -->
|
<!-- begin 注意:这几个依赖体积较大,每个约50MB。若发布时需要使用,请将 <scope>provided</scope> 删除 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
<artifactId>kotlin-scripting-jsr223</artifactId>
|
<artifactId>kotlin-scripting-jsr223</artifactId>
|
||||||
@ -89,14 +105,21 @@
|
|||||||
<version>${liteflow.version}</version>
|
<version>${liteflow.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- end 这两个依赖太多每个包50M左右,如果你发布需要使用,请把<scope>provided</scope>删掉 -->
|
|
||||||
<!-- aiflow 脚本依赖 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.yomahub</groupId>
|
<groupId>com.yomahub</groupId>
|
||||||
<artifactId>liteflow-script-groovy</artifactId>
|
<artifactId>liteflow-script-groovy</artifactId>
|
||||||
<version>${liteflow.version}</version>
|
<version>${liteflow.version}</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- end 注意:这几个依赖体积较大,每个约50MB。若发布时需要使用,请将 <scope>provided</scope> 删除 -->
|
||||||
|
|
||||||
|
<!-- aiflow 脚本依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.yomahub</groupId>
|
||||||
|
<artifactId>liteflow-script-python</artifactId>
|
||||||
|
<version>${liteflow.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.yomahub</groupId>
|
<groupId>com.yomahub</groupId>
|
||||||
<artifactId>liteflow-script-kotlin</artifactId>
|
<artifactId>liteflow-script-kotlin</artifactId>
|
||||||
@ -122,12 +145,17 @@
|
|||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- aiflow 脚本依赖 -->
|
<!-- aiflow 脚本依赖 -->
|
||||||
|
|
||||||
<!-- langChain4j model support -->
|
<!-- langChain4j model support -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.langchain4j</groupId>
|
<groupId>dev.langchain4j</groupId>
|
||||||
<artifactId>langchain4j-open-ai</artifactId>
|
<artifactId>langchain4j-open-ai</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- langChain4j mcp support -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.langchain4j</groupId>
|
||||||
|
<artifactId>langchain4j-mcp</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.langchain4j</groupId>
|
<groupId>dev.langchain4j</groupId>
|
||||||
<artifactId>langchain4j-ollama</artifactId>
|
<artifactId>langchain4j-ollama</artifactId>
|
||||||
@ -164,13 +192,21 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.langchain4j</groupId>
|
||||||
|
<artifactId>langchain4j-anthropic</artifactId>
|
||||||
|
</dependency>
|
||||||
<!-- langChain4j vextor support -->
|
<!-- langChain4j vextor support -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework</groupId>
|
<groupId>org.jeecgframework</groupId>
|
||||||
<artifactId>langchain4j-pgvector</artifactId>
|
<artifactId>langchain4j-pgvector</artifactId>
|
||||||
<version>1.3.0-beta9</version>
|
<version>1.3.0-beta9</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- langChain4j Document Parser -->
|
<!-- langChain4j Document Parser 适用于excel、ppt、word -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.langchain4j</groupId>
|
||||||
|
<artifactId>langchain4j-document-parser-apache-poi</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.tika</groupId>
|
<groupId>org.apache.tika</groupId>
|
||||||
<artifactId>tika-core</artifactId>
|
<artifactId>tika-core</artifactId>
|
||||||
@ -197,7 +233,12 @@
|
|||||||
<artifactId>tika-parser-text-module</artifactId>
|
<artifactId>tika-parser-text-module</artifactId>
|
||||||
<version>${apache-tika.version}</version>
|
<version>${apache-tika.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- word模版引擎 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.deepoove</groupId>
|
||||||
|
<artifactId>poi-tl</artifactId>
|
||||||
|
<version>1.12.2</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@ -38,4 +38,25 @@ public class AiAppConsts {
|
|||||||
*/
|
*/
|
||||||
public static final String APP_TYPE_CHAT_FLOW = "chatFLow";
|
public static final String APP_TYPE_CHAT_FLOW = "chatFLow";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用元数据:流程输入参数
|
||||||
|
* for [issues/8545]新建AI应用的时候只能选择没有自定义参数的AI流程
|
||||||
|
*/
|
||||||
|
public static final String APP_METADATA_FLOW_INPUTS = "flowInputs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否开启记忆
|
||||||
|
*/
|
||||||
|
public static final Integer IZ_OPEN_MEMORY = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话标题最大长度
|
||||||
|
*/
|
||||||
|
public static final int CONVERSATION_MAX_TITLE_LENGTH = 10;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI写作的应用id
|
||||||
|
*/
|
||||||
|
public static final String WRITER_APP_ID = "2010634128233779202";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,4 +104,68 @@ public class Prompts {
|
|||||||
" - 反幻觉校验:\"所有数据需标注来源,不确定信息用[需核实]标记\"\n" +
|
" - 反幻觉校验:\"所有数据需标注来源,不确定信息用[需核实]标记\"\n" +
|
||||||
" - 风格校准器:\"对比[目标风格]与生成内容的余弦相似度,低于0.7时启动重写\"\n" +
|
" - 风格校准器:\"对比[目标风格]与生成内容的余弦相似度,低于0.7时启动重写\"\n" +
|
||||||
" - 伦理审查模块:\"自动过滤涉及隐私/偏见/违法内容,替换为[合规表达]\"";
|
" - 伦理审查模块:\"自动过滤涉及隐私/偏见/违法内容,替换为[合规表达]\"";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提示词生成角色及通用要求
|
||||||
|
*/
|
||||||
|
public static final String GENERATE_GUIDE_HEADER = "# 角色\n" +
|
||||||
|
"你是一位AI提示词专家,请根据提供的配置信息,生成针对AI智能体的“使用指南”提示词。\n" +
|
||||||
|
"\n" +
|
||||||
|
"## 通用要求\n" +
|
||||||
|
"1. 生成的内容将作为系统提示词的一部分。\n" +
|
||||||
|
"2. **严禁**包含任何角色设定开场白(如“你是一个...AI助手”、“在对话过程中...”等)。\n" +
|
||||||
|
"3. **只输出提示词内容**,不要包含任何解释、寒暄或Markdown代码块标记。\n" +
|
||||||
|
"4. 语气专业、清晰、指令性强。\n" +
|
||||||
|
"5. 说明内容请使用中文。\n\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变量生成提示词
|
||||||
|
*/
|
||||||
|
public static final String GENERATE_VAR_PART = "## 任务:生成变量使用指南\n" +
|
||||||
|
"### 输入信息\n" +
|
||||||
|
"**变量列表**:\n" +
|
||||||
|
"%s\n" +
|
||||||
|
"### 要求\n" +
|
||||||
|
"1. 请生成一段**变量使用指南**。\n" +
|
||||||
|
"2. **遍历生成**:请遍历【输入信息】中的所有变量,为**每一个**变量生成一条具体的使用指南。\n" +
|
||||||
|
"3. **格式要求**:请仿照以下句式,根据变量的实际含义生成(确保包含{{变量名}}):\n" +
|
||||||
|
" 例如:针对name变量 -> “回复问题时,请称呼你的用户为{{name}}。”\n" +
|
||||||
|
" 例如:针对age变量 -> “用户的年龄是{{age}},请在对话中适时使用。”\n" +
|
||||||
|
" 例如:针对其他变量 -> “用户的[变量描述]是{{[变量名]}},请在对话中适时使用。”\n" +
|
||||||
|
"4. **通用更新指令**:请在变量指南的最后,单独生成一条指令,明确指示AI:“当从用户对话中获取到上述变量(<列出所有变量名,用顿号分隔>)的**新信息**时,**必须立即调用** `update_variable` 工具进行存储。**注意**:调用前请检查上下文,如果已调用过该工具或变量值未改变,**严禁**重复调用。”\n" +
|
||||||
|
"5. **保留原文**:如果输入信息中包含具体的行为指令(如“回复问题时,请称呼你的用户为{{name}}”),请在生成的指南中**直接引用原文**,不要进行改写或格式化,以免改变用户的原意。\n\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记忆库生成提示词
|
||||||
|
*/
|
||||||
|
public static final String GENERATE_MEMORY_PART = "## 任务:生成记忆库使用指南\n" +
|
||||||
|
"### 输入信息\n" +
|
||||||
|
"**记忆库描述**:\n" +
|
||||||
|
"%s\n" +
|
||||||
|
"### 要求\n" +
|
||||||
|
"1. 请生成一段**记忆库使用指南**,加入【工具使用强制协议】:\n" +
|
||||||
|
" - **全自动存储(无需用户指令)**:你必须时刻像一个观察者一样分析对话。一旦检测到符合记忆库描述的信息(尤其是:**姓名、职业、年龄**、联系方式、偏好、经历等),**立即**调用 `add_memory` 工具存储。**绝对不要**询问用户是否需要存储,也不要等待用户明确指令。这是你的后台职责。\n" +
|
||||||
|
" - **全自动检索(强制优先)**:\n" +
|
||||||
|
" * **禁止直接反问**:当用户提出依赖个人信息的问题(如“推荐适合我的...”或“我之前说过...”)时,**绝对禁止**直接反问用户“你的爱好是什么?”。\n" +
|
||||||
|
" * **必须先查后答**:你必须**先假设**记忆库中已经有了答案,并**立即调用** `query_memory` 进行验证。只有当工具返回“未找到相关信息”后,你才有资格询问用户。\n" +
|
||||||
|
" * **宁可查空,不可不查**:即使你觉得可能没有记录,也必须先走一遍查询流程。\n" +
|
||||||
|
" - **动态调整**:请根据【输入信息】中提供的**记忆库状态描述**,明确界定哪些信息属于“自动捕获”的范围。\n" +
|
||||||
|
" - **行为准则**:\n" +
|
||||||
|
" * 你的记忆动作应该是**主动且无感**的。用户只负责聊天,你负责记住一切重要细节。\n" +
|
||||||
|
" * **禁止口头空谈**:严禁只回复“我知道了”、“已记住”而实际不调用工具。这是严重错误。\n" +
|
||||||
|
" - **示例演示**:\n" +
|
||||||
|
" * 自动存储(职业):用户说“我是网络工程师” -> (捕捉到职业信息) -> **立即自动调用** `add_memory(content='用户职业是网络工程师')` -> (存储成功) -> 回复“原来是同行,网络工程很有趣...”。\n" +
|
||||||
|
" * 自动查询(场景):用户说“根据我的爱好推荐旅游地点” -> **严禁**直接问“你有什么爱好?” -> **必须立即调用** `query_memory(queryText='用户爱好')` -> (若查到:爬山) -> 回复“既然你喜欢爬山,推荐去黄山...”。\n" +
|
||||||
|
" * 自动查询(常规):用户问“今天吃什么好?” -> (需要了解口味) -> **立即自动调用** `query_memory(queryText='用户饮食偏好')` -> (获取到不吃香菜) -> 回复“推荐一家不放香菜的...”。\n\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ai写作提示词
|
||||||
|
*/
|
||||||
|
public static final String AI_WRITER_PROMPT ="请撰写一篇关于 [{}] 的文章。文章的内容格式:{},语气:{},语言:{},长度:{}。";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ai写作回复提示词
|
||||||
|
*/
|
||||||
|
public static final String AI_REPLY_PROMPT = "请针对如下内容:[{}] 做个回复。回复内容参考:[{}], 回复格式:{},语气:{},语言:{},长度:{}。";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: AI应用
|
* @Description: AI应用
|
||||||
@ -179,4 +178,16 @@ public class AiragAppController extends JeecgController<AiragApp, IAiragAppServi
|
|||||||
return (SseEmitter) airagAppService.generatePrompt(prompt,false);
|
return (SseEmitter) airagAppService.generatePrompt(prompt,false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据应用ID生成变量和记忆提示词 (SSE)
|
||||||
|
* for: 【QQYUN-14479】提示词单独拆分
|
||||||
|
* @param variables
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/prompt/generateMemoryByAppId")
|
||||||
|
public SseEmitter generatePromptByAppIdSse(@RequestParam(name = "variables") String variables,
|
||||||
|
@RequestParam(name = "memoryId") String memoryId) {
|
||||||
|
return (SseEmitter) airagAppService.generateMemoryByAppId(variables, memoryId,false);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import org.jeecg.common.constant.CommonConstant;
|
|||||||
import org.jeecg.common.util.CommonUtils;
|
import org.jeecg.common.util.CommonUtils;
|
||||||
import org.jeecg.config.shiro.IgnoreAuth;
|
import org.jeecg.config.shiro.IgnoreAuth;
|
||||||
import org.jeecg.modules.airag.app.service.IAiragChatService;
|
import org.jeecg.modules.airag.app.service.IAiragChatService;
|
||||||
|
import org.jeecg.modules.airag.app.vo.AiWriteGenerateVo;
|
||||||
import org.jeecg.modules.airag.app.vo.ChatConversation;
|
import org.jeecg.modules.airag.app.vo.ChatConversation;
|
||||||
import org.jeecg.modules.airag.app.vo.ChatSendParams;
|
import org.jeecg.modules.airag.app.vo.ChatSendParams;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -102,6 +103,19 @@ public class AiragChatController {
|
|||||||
return chatService.getConversations(appId);
|
return chatService.getConversations(appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型获取所有对话
|
||||||
|
*
|
||||||
|
* @return 返回一个Result对象,包含所有对话的信息
|
||||||
|
* @author wangshuai
|
||||||
|
* @date 2025/12/11 11:42
|
||||||
|
*/
|
||||||
|
@IgnoreAuth
|
||||||
|
@GetMapping(value = "/getConversationsByType")
|
||||||
|
public Result<?> getConversationsByType(@RequestParam(value = "sessionType") String sessionType) {
|
||||||
|
return chatService.getConversationsByType(sessionType);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除会话
|
* 删除会话
|
||||||
*
|
*
|
||||||
@ -113,7 +127,22 @@ public class AiragChatController {
|
|||||||
@IgnoreAuth
|
@IgnoreAuth
|
||||||
@DeleteMapping(value = "/conversation/{id}")
|
@DeleteMapping(value = "/conversation/{id}")
|
||||||
public Result<?> deleteConversation(@PathVariable("id") String id) {
|
public Result<?> deleteConversation(@PathVariable("id") String id) {
|
||||||
return chatService.deleteConversation(id);
|
return chatService.deleteConversation(id,"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除会话
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
* @author wangshuai
|
||||||
|
* @date 2025/12/11 20:00
|
||||||
|
*/
|
||||||
|
@IgnoreAuth
|
||||||
|
@DeleteMapping(value = "/conversation/{id}/{sessionType}")
|
||||||
|
public Result<?> deleteConversationByType(@PathVariable("id") String id,
|
||||||
|
@PathVariable("sessionType") String sessionType) {
|
||||||
|
return chatService.deleteConversation(id,sessionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -139,8 +168,9 @@ public class AiragChatController {
|
|||||||
*/
|
*/
|
||||||
@IgnoreAuth
|
@IgnoreAuth
|
||||||
@GetMapping(value = "/messages")
|
@GetMapping(value = "/messages")
|
||||||
public Result<?> getMessages(@RequestParam(value = "conversationId", required = true) String conversationId) {
|
public Result<?> getMessages(@RequestParam(value = "conversationId", required = true) String conversationId,
|
||||||
return chatService.getMessages(conversationId);
|
@RequestParam(value = "sessionType", required = false) String sessionType) {
|
||||||
|
return chatService.getMessages(conversationId, sessionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -153,7 +183,21 @@ public class AiragChatController {
|
|||||||
@IgnoreAuth
|
@IgnoreAuth
|
||||||
@GetMapping(value = "/messages/clear/{conversationId}")
|
@GetMapping(value = "/messages/clear/{conversationId}")
|
||||||
public Result<?> clearMessage(@PathVariable(value = "conversationId") String conversationId) {
|
public Result<?> clearMessage(@PathVariable(value = "conversationId") String conversationId) {
|
||||||
return chatService.clearMessage(conversationId);
|
return chatService.clearMessage(conversationId, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空消息
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @author wangshuai
|
||||||
|
* @date 2025/12/11 19:06
|
||||||
|
*/
|
||||||
|
@IgnoreAuth
|
||||||
|
@GetMapping(value = "/messages/clear/{conversationId}/{sessionType}")
|
||||||
|
public Result<?> clearMessageByType(@PathVariable(value = "conversationId") String conversationId,
|
||||||
|
@PathVariable(value = "sessionType") String sessionType) {
|
||||||
|
return chatService.clearMessage(conversationId, sessionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -217,4 +261,25 @@ public class AiragChatController {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ai海报生成
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/genAiPoster")
|
||||||
|
public Result<String> genAiPoster(@RequestBody ChatSendParams chatSendParams){
|
||||||
|
String imageUrl = chatService.genAiPoster(chatSendParams);
|
||||||
|
return Result.OK(imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成ai写作
|
||||||
|
*
|
||||||
|
* @param aiWriteGenerateVo
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/genAiWriter")
|
||||||
|
public SseEmitter genAiWriter(@RequestBody AiWriteGenerateVo aiWriteGenerateVo){
|
||||||
|
return chatService.genAiWriter(aiWriteGenerateVo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -167,6 +167,35 @@ public class AiragApp implements Serializable {
|
|||||||
@Schema(description = "元数据")
|
@Schema(description = "元数据")
|
||||||
private java.lang.String metadata;
|
private java.lang.String metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件 [{pluginId: '123213', pluginName: 'xxxx', category: 'mcp'}]
|
||||||
|
*/
|
||||||
|
@Schema(description = "插件")
|
||||||
|
private java.lang.String plugins;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否开启记忆(0 不开启,1开启)
|
||||||
|
*/
|
||||||
|
@Schema(description = "是否开启记忆(0 不开启,1开启)")
|
||||||
|
private java.lang.Integer izOpenMemory;
|
||||||
|
/**
|
||||||
|
* 记忆库,知识库的id
|
||||||
|
*/
|
||||||
|
@Schema(description = "记忆库")
|
||||||
|
private java.lang.String memoryId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变量
|
||||||
|
*/
|
||||||
|
@Schema(description = "变量")
|
||||||
|
private java.lang.String variables;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记忆和变量提示词
|
||||||
|
*/
|
||||||
|
@Schema(description = "记忆和变量提示词")
|
||||||
|
private java.lang.String memoryPrompt;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 知识库ids
|
* 知识库ids
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package org.jeecg.modules.airag.app.service;
|
package org.jeecg.modules.airag.app.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import org.jeecg.common.api.vo.Result;
|
|
||||||
import org.jeecg.modules.airag.app.entity.AiragApp;
|
import org.jeecg.modules.airag.app.entity.AiragApp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,4 +20,14 @@ public interface IAiragAppService extends IService<AiragApp> {
|
|||||||
* @date 2025/3/12 14:45
|
* @date 2025/3/12 14:45
|
||||||
*/
|
*/
|
||||||
Object generatePrompt(String prompt,boolean blocking);
|
Object generatePrompt(String prompt,boolean blocking);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据应用id生成提示词
|
||||||
|
*
|
||||||
|
* @param variables
|
||||||
|
* @param memoryId
|
||||||
|
* @param blocking
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Object generateMemoryByAppId(String variables, String memoryId, boolean blocking);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package org.jeecg.modules.airag.app.service;
|
package org.jeecg.modules.airag.app.service;
|
||||||
|
|
||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
import org.jeecg.modules.airag.app.vo.AiWriteGenerateVo;
|
||||||
import org.jeecg.modules.airag.app.vo.AppDebugParams;
|
import org.jeecg.modules.airag.app.vo.AppDebugParams;
|
||||||
import org.jeecg.modules.airag.app.vo.ChatConversation;
|
import org.jeecg.modules.airag.app.vo.ChatConversation;
|
||||||
import org.jeecg.modules.airag.app.vo.ChatSendParams;
|
import org.jeecg.modules.airag.app.vo.ChatSendParams;
|
||||||
@ -59,21 +60,23 @@ public interface IAiragChatService {
|
|||||||
* 获取对话聊天记录
|
* 获取对话聊天记录
|
||||||
*
|
*
|
||||||
* @param conversationId
|
* @param conversationId
|
||||||
|
* @param sessionType 类型
|
||||||
* @return
|
* @return
|
||||||
* @author chenrui
|
* @author chenrui
|
||||||
* @date 2025/2/26 15:16
|
* @date 2025/2/26 15:16
|
||||||
*/
|
*/
|
||||||
Result<?> getMessages(String conversationId);
|
Result<?> getMessages(String conversationId, String sessionType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除会话
|
* 删除会话
|
||||||
*
|
*
|
||||||
* @param conversationId
|
* @param conversationId
|
||||||
|
* @param sessionType
|
||||||
* @return
|
* @return
|
||||||
* @author chenrui
|
* @author chenrui
|
||||||
* @date 2025/3/3 16:55
|
* @date 2025/3/3 16:55
|
||||||
*/
|
*/
|
||||||
Result<?> deleteConversation(String conversationId);
|
Result<?> deleteConversation(String conversationId, String sessionType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新会话标题
|
* 更新会话标题
|
||||||
@ -87,11 +90,12 @@ public interface IAiragChatService {
|
|||||||
/**
|
/**
|
||||||
* 清空消息
|
* 清空消息
|
||||||
* @param conversationId
|
* @param conversationId
|
||||||
|
* @param sessionType
|
||||||
* @return
|
* @return
|
||||||
* @author chenrui
|
* @author chenrui
|
||||||
* @date 2025/3/3 19:49
|
* @date 2025/3/3 19:49
|
||||||
*/
|
*/
|
||||||
Result<?> clearMessage(String conversationId);
|
Result<?> clearMessage(String conversationId, String sessionType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化聊天(忽略租户)
|
* 初始化聊天(忽略租户)
|
||||||
@ -111,4 +115,27 @@ public interface IAiragChatService {
|
|||||||
* @date 2025/8/11 17:39
|
* @date 2025/8/11 17:39
|
||||||
*/
|
*/
|
||||||
SseEmitter receiveByRequestId(String requestId);
|
SseEmitter receiveByRequestId(String requestId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型获取会话列表
|
||||||
|
*
|
||||||
|
* @param sessionType
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Result<?> getConversationsByType(String sessionType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成海报图片
|
||||||
|
* @param chatSendParams
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String genAiPoster(ChatSendParams chatSendParams);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成ai创作
|
||||||
|
*
|
||||||
|
* @param chatSendParams
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
SseEmitter genAiWriter(AiWriteGenerateVo chatSendParams);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,44 @@
|
|||||||
|
package org.jeecg.modules.airag.app.service;
|
||||||
|
|
||||||
|
import org.jeecg.modules.airag.app.entity.AiragApp;
|
||||||
|
import org.jeecg.modules.airag.common.handler.AIChatParams;
|
||||||
|
|
||||||
|
public interface IAiragVariableService {
|
||||||
|
/**
|
||||||
|
* 更新变量值
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
* @param appId
|
||||||
|
* @param name
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
void updateVariable(String userId, String appId, String name, String value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追加提示词
|
||||||
|
*
|
||||||
|
* @param username
|
||||||
|
* @param app
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String additionalPrompt(String username, AiragApp app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化变量(仅不存在时设置)
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
* @param appId
|
||||||
|
* @param name
|
||||||
|
* @param defaultValue
|
||||||
|
*/
|
||||||
|
void initVariable(String userId, String appId, String name, String defaultValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加变量更新工具
|
||||||
|
*
|
||||||
|
* @param params
|
||||||
|
* @param aiApp
|
||||||
|
* @param username
|
||||||
|
*/
|
||||||
|
void addUpdateVariableTool(AiragApp aiApp, String username, AIChatParams params);
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package org.jeecg.modules.airag.app.service.impl;
|
package org.jeecg.modules.airag.app.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONArray;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import dev.langchain4j.data.message.AiMessage;
|
import dev.langchain4j.data.message.AiMessage;
|
||||||
@ -10,12 +11,15 @@ import dev.langchain4j.model.output.FinishReason;
|
|||||||
import dev.langchain4j.service.TokenStream;
|
import dev.langchain4j.service.TokenStream;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
import org.jeecg.common.exception.JeecgBootBizTipException;
|
||||||
import org.jeecg.common.util.AssertUtils;
|
import org.jeecg.common.util.AssertUtils;
|
||||||
import org.jeecg.common.util.UUIDGenerator;
|
import org.jeecg.common.util.UUIDGenerator;
|
||||||
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
import org.jeecg.modules.airag.app.consts.Prompts;
|
import org.jeecg.modules.airag.app.consts.Prompts;
|
||||||
import org.jeecg.modules.airag.app.entity.AiragApp;
|
import org.jeecg.modules.airag.app.entity.AiragApp;
|
||||||
import org.jeecg.modules.airag.app.mapper.AiragAppMapper;
|
import org.jeecg.modules.airag.app.mapper.AiragAppMapper;
|
||||||
import org.jeecg.modules.airag.app.service.IAiragAppService;
|
import org.jeecg.modules.airag.app.service.IAiragAppService;
|
||||||
|
import org.jeecg.modules.airag.app.vo.AppVariableVo;
|
||||||
import org.jeecg.modules.airag.common.consts.AiragConsts;
|
import org.jeecg.modules.airag.common.consts.AiragConsts;
|
||||||
import org.jeecg.modules.airag.common.handler.AIChatParams;
|
import org.jeecg.modules.airag.common.handler.AIChatParams;
|
||||||
import org.jeecg.modules.airag.common.handler.IAIChatHandler;
|
import org.jeecg.modules.airag.common.handler.IAIChatHandler;
|
||||||
@ -23,6 +27,8 @@ import org.jeecg.modules.airag.common.utils.AiragLocalCache;
|
|||||||
import org.jeecg.modules.airag.common.vo.event.EventData;
|
import org.jeecg.modules.airag.common.vo.event.EventData;
|
||||||
import org.jeecg.modules.airag.common.vo.event.EventFlowData;
|
import org.jeecg.modules.airag.common.vo.event.EventFlowData;
|
||||||
import org.jeecg.modules.airag.common.vo.event.EventMessageData;
|
import org.jeecg.modules.airag.common.vo.event.EventMessageData;
|
||||||
|
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
|
||||||
|
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
@ -31,6 +37,7 @@ import java.io.IOException;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: AI应用
|
* @Description: AI应用
|
||||||
@ -45,6 +52,9 @@ public class AiragAppServiceImpl extends ServiceImpl<AiragAppMapper, AiragApp> i
|
|||||||
@Autowired
|
@Autowired
|
||||||
IAIChatHandler aiChatHandler;
|
IAIChatHandler aiChatHandler;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IAiragKnowledgeService airagKnowledgeService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object generatePrompt(String prompt, boolean blocking) {
|
public Object generatePrompt(String prompt, boolean blocking) {
|
||||||
AssertUtils.assertNotEmpty("请输入提示词", prompt);
|
AssertUtils.assertNotEmpty("请输入提示词", prompt);
|
||||||
@ -62,81 +72,167 @@ public class AiragAppServiceImpl extends ServiceImpl<AiragAppMapper, AiragApp> i
|
|||||||
}
|
}
|
||||||
return Result.OK("success", promptValue);
|
return Result.OK("success", promptValue);
|
||||||
}else{
|
}else{
|
||||||
SseEmitter emitter = new SseEmitter(-0L);
|
//update-begin---author:wangshuai---date:2026-01-08---for: 将流式输出单独抽出去,变量和记忆也需要---
|
||||||
// 异步运行(流式)
|
return startSseChat(messages, params);
|
||||||
TokenStream tokenStream = aiChatHandler.chatByDefaultModel(messages, params);
|
//update-end---author:wangshuai---date:2026-01-08---for: 将流式输出单独抽出去,变量和记忆也需要---
|
||||||
/**
|
}
|
||||||
* 是否正在思考
|
}
|
||||||
*/
|
|
||||||
AtomicBoolean isThinking = new AtomicBoolean(false);
|
//update-begin---author:wangshuai---date:2026-01-05---for:【QQYUN-14479】增加一个开启记忆的按钮。下面为提示词和记忆,将记忆提示词单独拆分---
|
||||||
String requestId = UUIDGenerator.generate();
|
@Override
|
||||||
// ai聊天响应逻辑
|
public Object generateMemoryByAppId(String variables, String memoryId, boolean blocking) {
|
||||||
tokenStream.onPartialResponse((String resMessage) -> {
|
if(oConvertUtils.isEmpty(variables) && oConvertUtils.isEmpty(memoryId)){
|
||||||
// 兼容推理模型
|
throw new JeecgBootBizTipException("请先添加变量或者记忆后再次重试!");
|
||||||
if ("<think>".equals(resMessage)) {
|
}
|
||||||
isThinking.set(true);
|
// 构建变量描述
|
||||||
resMessage = "> ";
|
StringBuilder variablesDesc = new StringBuilder();
|
||||||
}
|
if (oConvertUtils.isNotEmpty(variables)) {
|
||||||
if ("</think>".equals(resMessage)) {
|
List<AppVariableVo> variableList = JSONArray.parseArray(variables, AppVariableVo.class);
|
||||||
isThinking.set(false);
|
if (variableList != null && !variableList.isEmpty()) {
|
||||||
resMessage = "\n\n";
|
for (AppVariableVo var : variableList) {
|
||||||
}
|
if (var.getEnable() != null && !var.getEnable()) {
|
||||||
if (isThinking.get()) {
|
continue;
|
||||||
if (null != resMessage && resMessage.contains("\n")) {
|
}
|
||||||
resMessage = "\n> ";
|
String name = var.getName();
|
||||||
|
if (oConvertUtils.isNotEmpty(var.getAction())) {
|
||||||
|
String action = var.getAction();
|
||||||
|
if (oConvertUtils.isNotEmpty(name)) {
|
||||||
|
try {
|
||||||
|
// 使用正则替换未被{{}}包裹的变量名
|
||||||
|
String regex = "(?<!\\{\\{)\\b" + Pattern.quote(name) + "\\b(?!\\}\\})";
|
||||||
|
action = action.replaceAll(regex, "{{" + name + "}}");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("变量名替换异常: name={}", name, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE);
|
variablesDesc.append(action).append("\n");
|
||||||
EventMessageData messageEventData = EventMessageData.builder()
|
} else {
|
||||||
.message(resMessage)
|
variablesDesc.append("- {{").append(name).append("}}");
|
||||||
.build();
|
if (oConvertUtils.isNotEmpty(var.getDescription())) {
|
||||||
eventData.setData(messageEventData);
|
variablesDesc.append(": ").append(var.getDescription());
|
||||||
|
}
|
||||||
|
variablesDesc.append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建Prompt
|
||||||
|
StringBuilder promptBuilder = new StringBuilder(Prompts.GENERATE_GUIDE_HEADER);
|
||||||
|
if (!variablesDesc.isEmpty()) {
|
||||||
|
promptBuilder.append(String.format(Prompts.GENERATE_VAR_PART, variablesDesc.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建记忆状态描述
|
||||||
|
if (oConvertUtils.isNotEmpty(memoryId)) {
|
||||||
|
String memoryDescr = "";
|
||||||
|
AiragKnowledge memory = airagKnowledgeService.getById(memoryId);
|
||||||
|
if (memory != null && oConvertUtils.isNotEmpty(memory.getDescr())) {
|
||||||
|
memoryDescr += "记忆库描述:" + memory.getDescr();
|
||||||
|
}
|
||||||
|
promptBuilder.append(String.format(Prompts.GENERATE_MEMORY_PART, memoryDescr));
|
||||||
|
}
|
||||||
|
|
||||||
|
String prompt = promptBuilder.toString();
|
||||||
|
|
||||||
|
List<ChatMessage> messages = List.of(new UserMessage(prompt));
|
||||||
|
|
||||||
|
AIChatParams params = new AIChatParams();
|
||||||
|
params.setTemperature(0.7);
|
||||||
|
|
||||||
|
if(blocking){
|
||||||
|
String promptValue = aiChatHandler.completionsByDefaultModel(messages, params);
|
||||||
|
if (promptValue == null || promptValue.isEmpty()) {
|
||||||
|
return Result.error("生成失败");
|
||||||
|
}
|
||||||
|
return Result.OK("success", promptValue);
|
||||||
|
}else{
|
||||||
|
return startSseChat(messages, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送聊天
|
||||||
|
* @param messages
|
||||||
|
* @param params
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private SseEmitter startSseChat(List<ChatMessage> messages, AIChatParams params) {
|
||||||
|
SseEmitter emitter = new SseEmitter(-0L);
|
||||||
|
// 异步运行(流式)
|
||||||
|
TokenStream tokenStream = aiChatHandler.chatByDefaultModel(messages, params);
|
||||||
|
/**
|
||||||
|
* 是否正在思考
|
||||||
|
*/
|
||||||
|
AtomicBoolean isThinking = new AtomicBoolean(false);
|
||||||
|
String requestId = UUIDGenerator.generate();
|
||||||
|
// ai聊天响应逻辑
|
||||||
|
tokenStream.onPartialResponse((String resMessage) -> {
|
||||||
|
// 兼容推理模型
|
||||||
|
if ("<think>".equals(resMessage)) {
|
||||||
|
isThinking.set(true);
|
||||||
|
resMessage = "> ";
|
||||||
|
}
|
||||||
|
if ("</think>".equals(resMessage)) {
|
||||||
|
isThinking.set(false);
|
||||||
|
resMessage = "\n\n";
|
||||||
|
}
|
||||||
|
if (isThinking.get()) {
|
||||||
|
if (null != resMessage && resMessage.contains("\n")) {
|
||||||
|
resMessage = "\n> ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE);
|
||||||
|
EventMessageData messageEventData = EventMessageData.builder()
|
||||||
|
.message(resMessage)
|
||||||
|
.build();
|
||||||
|
eventData.setData(messageEventData);
|
||||||
|
try {
|
||||||
|
String eventStr = JSONObject.toJSONString(eventData);
|
||||||
|
log.debug("[AI应用]接收LLM返回消息:{}", eventStr);
|
||||||
|
emitter.send(SseEmitter.event().data(eventStr));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onCompleteResponse((responseMessage) -> {
|
||||||
|
// 记录ai的回复
|
||||||
|
AiMessage aiMessage = responseMessage.aiMessage();
|
||||||
|
FinishReason finishReason = responseMessage.finishReason();
|
||||||
|
String respText = aiMessage.text();
|
||||||
|
if (FinishReason.STOP.equals(finishReason) || null == finishReason) {
|
||||||
|
// 正常结束
|
||||||
|
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END);
|
||||||
try {
|
try {
|
||||||
String eventStr = JSONObject.toJSONString(eventData);
|
log.debug("[AI应用]接收LLM返回消息完成:{}", respText);
|
||||||
log.debug("[AI应用]接收LLM返回消息:{}", eventStr);
|
emitter.send(SseEmitter.event().data(eventData));
|
||||||
emitter.send(SseEmitter.event().data(eventStr));
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.onCompleteResponse((responseMessage) -> {
|
|
||||||
// 记录ai的回复
|
|
||||||
AiMessage aiMessage = responseMessage.aiMessage();
|
|
||||||
FinishReason finishReason = responseMessage.finishReason();
|
|
||||||
String respText = aiMessage.text();
|
|
||||||
if (FinishReason.STOP.equals(finishReason) || null == finishReason) {
|
|
||||||
// 正常结束
|
|
||||||
EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END);
|
|
||||||
try {
|
|
||||||
log.debug("[AI应用]接收LLM返回消息完成:{}", respText);
|
|
||||||
emitter.send(SseEmitter.event().data(eventData));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
closeSSE(emitter, eventData);
|
|
||||||
} else {
|
|
||||||
// 异常结束
|
|
||||||
log.error("调用模型异常:" + respText);
|
|
||||||
if (respText.contains("insufficient Balance")) {
|
|
||||||
respText = "大预言模型账号余额不足!";
|
|
||||||
}
|
|
||||||
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR);
|
|
||||||
eventData.setData(EventFlowData.builder().success(false).message(respText).build());
|
|
||||||
closeSSE(emitter, eventData);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onError((Throwable error) -> {
|
|
||||||
// sse
|
|
||||||
String errMsg = "调用大模型接口失败:" + error.getMessage();
|
|
||||||
log.error(errMsg, error);
|
|
||||||
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR);
|
|
||||||
eventData.setData(EventFlowData.builder().success(false).message(errMsg).build());
|
|
||||||
closeSSE(emitter, eventData);
|
closeSSE(emitter, eventData);
|
||||||
})
|
} else {
|
||||||
.start();
|
// 异常结束
|
||||||
return emitter;
|
log.error("调用模型异常:" + respText);
|
||||||
}
|
if (respText.contains("insufficient Balance")) {
|
||||||
|
respText = "大预言模型账号余额不足!";
|
||||||
|
}
|
||||||
|
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR);
|
||||||
|
eventData.setData(EventFlowData.builder().success(false).message(respText).build());
|
||||||
|
closeSSE(emitter, eventData);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onError((Throwable error) -> {
|
||||||
|
// sse
|
||||||
|
String errMsg = "调用大模型接口失败:" + error.getMessage();
|
||||||
|
log.error(errMsg, error);
|
||||||
|
EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR);
|
||||||
|
eventData.setData(EventFlowData.builder().success(false).message(errMsg).build());
|
||||||
|
closeSSE(emitter, eventData);
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
return emitter;
|
||||||
}
|
}
|
||||||
|
//update-end---author:wangshuai---date:2026-01-05---for:【QQYUN-14479】增加一个开启记忆的按钮。下面为提示词和记忆,将记忆提示词单独拆分---
|
||||||
|
|
||||||
private static void closeSSE(SseEmitter emitter, EventData eventData) {
|
private static void closeSSE(SseEmitter emitter, EventData eventData) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,194 @@
|
|||||||
|
package org.jeecg.modules.airag.app.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONArray;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import dev.langchain4j.agent.tool.ToolSpecification;
|
||||||
|
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
|
||||||
|
import dev.langchain4j.service.tool.ToolExecutor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jeecg.common.util.SpringContextUtils;
|
||||||
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
|
import org.jeecg.modules.airag.app.consts.AiAppConsts;
|
||||||
|
import org.jeecg.modules.airag.app.entity.AiragApp;
|
||||||
|
import org.jeecg.modules.airag.app.service.IAiragVariableService;
|
||||||
|
import org.jeecg.modules.airag.app.vo.AppVariableVo;
|
||||||
|
import org.jeecg.modules.airag.common.handler.AIChatParams;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: AI应用变量服务实现
|
||||||
|
* @Author: jeecg-boot
|
||||||
|
* @Date: 2025-02-26
|
||||||
|
* @Version: V1.0
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class AiragVariableServiceImpl implements IAiragVariableService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate redisTemplate;
|
||||||
|
|
||||||
|
private static final String CACHE_PREFIX = "airag:app:var:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化变量(仅不存在时设置)
|
||||||
|
*
|
||||||
|
* @param username
|
||||||
|
* @param appId
|
||||||
|
* @param name
|
||||||
|
* @param defaultValue
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initVariable(String username, String appId, String name, String defaultValue) {
|
||||||
|
if (oConvertUtils.isEmpty(username) || oConvertUtils.isEmpty(appId) || oConvertUtils.isEmpty(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String key = CACHE_PREFIX + appId + ":" + username;
|
||||||
|
redisTemplate.opsForHash().putIfAbsent(key, name, defaultValue != null ? defaultValue : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追加提示词
|
||||||
|
*
|
||||||
|
* @param username
|
||||||
|
* @param app
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String additionalPrompt(String username, AiragApp app) {
|
||||||
|
String memoryPrompt = app.getMemoryPrompt();
|
||||||
|
String prompt = app.getPrompt();
|
||||||
|
|
||||||
|
if (oConvertUtils.isEmpty(memoryPrompt)) {
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
String variablesStr = app.getVariables();
|
||||||
|
if (oConvertUtils.isEmpty(variablesStr)) {
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AppVariableVo> variableList = JSONArray.parseArray(variablesStr, AppVariableVo.class);
|
||||||
|
if (variableList == null || variableList.isEmpty()) {
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = CACHE_PREFIX + app.getId() + ":" + username;
|
||||||
|
Map<Object, Object> savedValues = redisTemplate.opsForHash().entries(key);
|
||||||
|
|
||||||
|
for (AppVariableVo variable : variableList) {
|
||||||
|
if (variable.getEnable() != null && !variable.getEnable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String name = variable.getName();
|
||||||
|
String value = variable.getDefaultValue();
|
||||||
|
|
||||||
|
// 优先使用Redis中的值
|
||||||
|
if (savedValues.containsKey(name)) {
|
||||||
|
Object savedVal = savedValues.get(name);
|
||||||
|
if (savedVal != null) {
|
||||||
|
value = String.valueOf(savedVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换 {{name}}
|
||||||
|
memoryPrompt = memoryPrompt.replace("{{" + name + "}}", value);
|
||||||
|
}
|
||||||
|
return prompt + "\n" + memoryPrompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新变量值
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
* @param appId
|
||||||
|
* @param name
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void updateVariable(String userId, String appId, String name, String value) {
|
||||||
|
if (oConvertUtils.isEmpty(userId) || oConvertUtils.isEmpty(appId) || oConvertUtils.isEmpty(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String key = CACHE_PREFIX + appId + ":" + userId;
|
||||||
|
redisTemplate.opsForHash().put(key, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加变量更新工具
|
||||||
|
*
|
||||||
|
* @param params
|
||||||
|
* @param aiApp
|
||||||
|
* @param username
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addUpdateVariableTool(AiragApp aiApp, String username, AIChatParams params) {
|
||||||
|
if (params.getTools() == null) {
|
||||||
|
params.setTools(new HashMap<>());
|
||||||
|
}
|
||||||
|
if (!AiAppConsts.IZ_OPEN_MEMORY.equals(aiApp.getIzOpenMemory())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 构建变量描述信息
|
||||||
|
String variablesStr = aiApp.getVariables();
|
||||||
|
List<AppVariableVo> variableList = null;
|
||||||
|
if (oConvertUtils.isNotEmpty(variablesStr)) {
|
||||||
|
variableList = JSONArray.parseArray(variablesStr, AppVariableVo.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
//工具描述
|
||||||
|
StringBuilder descriptionBuilder = new StringBuilder("更新应用变量的值。仅当检测到变量的新值与当前值不一致时调用。如果已调用过或值未变,请勿重复调用。");
|
||||||
|
if (variableList != null && !variableList.isEmpty()) {
|
||||||
|
descriptionBuilder.append("\n\n可用变量列表:");
|
||||||
|
for (AppVariableVo var : variableList) {
|
||||||
|
if (var.getEnable() != null && !var.getEnable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
descriptionBuilder.append("\n- ").append(var.getName());
|
||||||
|
if (oConvertUtils.isNotEmpty(var.getDescription())) {
|
||||||
|
descriptionBuilder.append(": ").append(var.getDescription());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descriptionBuilder.append("\n\n注意:variableName必须是上述列表中的名称之一。");
|
||||||
|
}
|
||||||
|
|
||||||
|
//构建更新变量的工具
|
||||||
|
ToolSpecification spec = ToolSpecification.builder()
|
||||||
|
.name("update_variable")
|
||||||
|
.description(descriptionBuilder.toString())
|
||||||
|
.parameters(JsonObjectSchema.builder()
|
||||||
|
.addStringProperty("variableName", "变量名称")
|
||||||
|
.addStringProperty("value", "变量值")
|
||||||
|
.required("variableName", "value")
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
//监听工具的调用
|
||||||
|
ToolExecutor executor = (toolExecutionRequest, memoryId) -> {
|
||||||
|
try {
|
||||||
|
JSONObject args = JSONObject.parseObject(toolExecutionRequest.arguments());
|
||||||
|
String name = args.getString("variableName");
|
||||||
|
String value = args.getString("value");
|
||||||
|
IAiragVariableService variableService = SpringContextUtils.getBean(IAiragVariableService.class);
|
||||||
|
//更新变量值
|
||||||
|
variableService.updateVariable(username, aiApp.getId(), name, value);
|
||||||
|
return "变量 " + name + " 已更新为: " + value;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新变量失败", e);
|
||||||
|
return "更新变量失败: " + e.getMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
params.getTools().put(spec, executor);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package org.jeecg.modules.airag.app.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: ai写作生成实体类
|
||||||
|
*
|
||||||
|
* @author: wangshuai
|
||||||
|
* @date: 2026/1/12 15:59
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AiWriteGenerateVo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写作类型
|
||||||
|
*/
|
||||||
|
private String activeMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写作内容提示
|
||||||
|
*/
|
||||||
|
private String prompt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原文
|
||||||
|
*/
|
||||||
|
private String originalContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 长度
|
||||||
|
*/
|
||||||
|
private String length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式
|
||||||
|
*/
|
||||||
|
private String format;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语气
|
||||||
|
*/
|
||||||
|
private String tone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言
|
||||||
|
*/
|
||||||
|
private String language;
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package org.jeecg.modules.airag.app.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 应用变量配置
|
||||||
|
* @Author: jeecg-boot
|
||||||
|
* @Date: 2025-02-26
|
||||||
|
* @Version: V1.0
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AppVariableVo implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变量名
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认值
|
||||||
|
*/
|
||||||
|
private String defaultValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用
|
||||||
|
*/
|
||||||
|
private Boolean enable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动作
|
||||||
|
*/
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
private Integer orderNum;
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ import org.jeecg.modules.airag.common.vo.MessageHistory;
|
|||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: 聊天会话
|
* @Description: 聊天会话
|
||||||
@ -39,4 +40,21 @@ public class ChatConversation {
|
|||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
private Date createTime;
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程入参配置(工作流的额外参数设置)
|
||||||
|
* key: 参数field, value: 参数值
|
||||||
|
* for [issues/8545]新建AI应用的时候只能选择没有自定义参数的AI流程
|
||||||
|
*/
|
||||||
|
private Map<String, Object> flowInputs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* portal 应用门户
|
||||||
|
*/
|
||||||
|
private String sessionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否保存会话
|
||||||
|
*/
|
||||||
|
private Boolean izSaveSession;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import lombok.Data;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: 发送消息的入参
|
* @Description: 发送消息的入参
|
||||||
@ -46,4 +47,56 @@ public class ChatSendParams {
|
|||||||
*/
|
*/
|
||||||
private List<String> images;
|
private List<String> images;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件列表
|
||||||
|
*/
|
||||||
|
private List<String> files;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流额外入参配置
|
||||||
|
* key: 参数field, value: 参数值
|
||||||
|
* for [issues/8545]新建AI应用的时候只能选择没有自定义参数的AI流程
|
||||||
|
*/
|
||||||
|
private Map<String, Object> flowInputs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否开启网络搜索(仅千问模型支持)
|
||||||
|
*/
|
||||||
|
private Boolean enableSearch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否开启深度思考
|
||||||
|
*/
|
||||||
|
private Boolean enableThink;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话类型: portal 应用门户
|
||||||
|
*/
|
||||||
|
private String sessionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否开启生成绘画
|
||||||
|
*/
|
||||||
|
private Boolean enableDraw;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘画模型的id
|
||||||
|
*/
|
||||||
|
private String drawModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片尺寸
|
||||||
|
*/
|
||||||
|
private String imageSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一张图片
|
||||||
|
*/
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否保存会话
|
||||||
|
*/
|
||||||
|
private Boolean izSaveSession;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package org.jeecg.modules.airag.demo;
|
package org.jeecg.modules.airag.demo;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jeecg.modules.airag.flow.component.enhance.IAiRagEnhanceJava;
|
import org.jeecg.modules.airag.flow.component.enhance.IAiRagEnhanceJava;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@ -11,12 +12,15 @@ import java.util.Map;
|
|||||||
* @Author: chenrui
|
* @Author: chenrui
|
||||||
* @Date: 2025/3/6 11:42
|
* @Date: 2025/3/6 11:42
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Component("testAiragEnhance")
|
@Component("testAiragEnhance")
|
||||||
public class TestAiragEnhance implements IAiRagEnhanceJava {
|
public class TestAiragEnhance implements IAiRagEnhanceJava {
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> process(Map<String, Object> inputParams) {
|
public Map<String, Object> process(Map<String, Object> inputParams) {
|
||||||
Object arg1 = inputParams.get("arg1");
|
Object arg1 = inputParams.get("arg1");
|
||||||
Object arg2 = inputParams.get("arg2");
|
Object arg2 = inputParams.get("arg2");
|
||||||
|
Object index = inputParams.get("index");
|
||||||
|
log.info("arg1={}, arg2={}, index={}", arg1, arg2, index);
|
||||||
return Collections.singletonMap("result",arg1.toString()+"java拼接"+arg2.toString());
|
return Collections.singletonMap("result",arg1.toString()+"java拼接"+arg2.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,209 @@
|
|||||||
|
package org.jeecg.modules.airag.llm.consts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 流程插件常量
|
||||||
|
*
|
||||||
|
* @author: wangshuai
|
||||||
|
* @date: 2025/12/23 19:37
|
||||||
|
*/
|
||||||
|
public interface FlowPluginContent {
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
String NAME = "name";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
String DESCRIPTION = "description";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应
|
||||||
|
*/
|
||||||
|
String RESPONSES = "responses";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*/
|
||||||
|
String TYPE = "type";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数
|
||||||
|
*/
|
||||||
|
String PARAMETERS = "parameters";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否必须
|
||||||
|
*/
|
||||||
|
String REQUIRED = "required";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认值
|
||||||
|
*/
|
||||||
|
String DEFAULT_VALUE = "defaultValue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路径
|
||||||
|
*/
|
||||||
|
String PATH = "path";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法
|
||||||
|
*/
|
||||||
|
String METHOD = "method";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 位置
|
||||||
|
*/
|
||||||
|
String LOCATION = "location";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证类型
|
||||||
|
*/
|
||||||
|
String AUTH_TYPE = "authType";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token参数名称
|
||||||
|
*/
|
||||||
|
String TOKEN_PARAM_NAME = "tokenParamName";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token参数值
|
||||||
|
*/
|
||||||
|
String TOKEN_PARAM_VALUE = "tokenParamValue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token
|
||||||
|
*/
|
||||||
|
String TOKEN = "token";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path位置
|
||||||
|
*/
|
||||||
|
String LOCATION_PATH = "Path";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header位置
|
||||||
|
*/
|
||||||
|
String LOCATION_HEADER = "Header";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query位置
|
||||||
|
*/
|
||||||
|
String LOCATION_QUERY = "Query";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Body位置
|
||||||
|
*/
|
||||||
|
String LOCATION_BODY = "Body";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form-Data位置
|
||||||
|
*/
|
||||||
|
String LOCATION_FORM_DATA = "Form-Data";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String类型
|
||||||
|
*/
|
||||||
|
String TYPE_STRING = "String";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* string类型
|
||||||
|
*/
|
||||||
|
String TYPE_STRING_LOWER = "string";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number类型
|
||||||
|
*/
|
||||||
|
String TYPE_NUMBER = "Number";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* number类型
|
||||||
|
*/
|
||||||
|
String TYPE_NUMBER_LOWER = "number";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integer类型
|
||||||
|
*/
|
||||||
|
String TYPE_INTEGER = "Integer";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* integer类型
|
||||||
|
*/
|
||||||
|
String TYPE_INTEGER_LOWER = "integer";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean类型
|
||||||
|
*/
|
||||||
|
String TYPE_BOOLEAN = "Boolean";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* boolean类型
|
||||||
|
*/
|
||||||
|
String TYPE_BOOLEAN_LOWER = "boolean";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具数量
|
||||||
|
*/
|
||||||
|
String TOOL_COUNT = "tool_count";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用
|
||||||
|
*/
|
||||||
|
String ENABLED = "enabled";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输入
|
||||||
|
*/
|
||||||
|
String INPUTS = "inputs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出
|
||||||
|
*/
|
||||||
|
String OUTPUTS = "outputs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST请求
|
||||||
|
*/
|
||||||
|
String POST = "POST";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token名称
|
||||||
|
*/
|
||||||
|
String X_ACCESS_TOKEN = "X-Access-Token";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件名称
|
||||||
|
*/
|
||||||
|
String PLUGIN_NAME = "流程调用";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件描述
|
||||||
|
*/
|
||||||
|
String PLUGIN_DESC = "调用工作流";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件请求地址
|
||||||
|
*/
|
||||||
|
String PLUGIN_REQUEST_URL = "/airag/flow/plugin/run/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记忆库插件名称
|
||||||
|
*/
|
||||||
|
String PLUGIN_MEMORY_NAME = "记忆库";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记忆库插件描述
|
||||||
|
*/
|
||||||
|
String PLUGIN_MEMORY_DESC = "用于记录长期记忆";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加记忆路径
|
||||||
|
*/
|
||||||
|
String PLUGIN_MEMORY_ADD_PATH = "/airag/knowledge/plugin/add";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询记忆路径
|
||||||
|
*/
|
||||||
|
String PLUGIN_MEMORY_QUERY_PATH = "/airag/knowledge/plugin/query";
|
||||||
|
}
|
||||||
@ -1,5 +1,8 @@
|
|||||||
package org.jeecg.modules.airag.llm.consts;
|
package org.jeecg.modules.airag.llm.consts;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,6 +38,11 @@ public class LLMConsts {
|
|||||||
*/
|
*/
|
||||||
public static final String MODEL_TYPE_LLM = "LLM";
|
public static final String MODEL_TYPE_LLM = "LLM";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型类型: 图像生成
|
||||||
|
*/
|
||||||
|
public static final String MODEL_TYPE_IMAGE = "IMAGE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向量模型:默认维度
|
* 向量模型:默认维度
|
||||||
*/
|
*/
|
||||||
@ -80,4 +88,34 @@ public class LLMConsts {
|
|||||||
*/
|
*/
|
||||||
public static final String KNOWLEDGE_DOC_METADATA_SOURCES_PATH = "sourcesPath";
|
public static final String KNOWLEDGE_DOC_METADATA_SOURCES_PATH = "sourcesPath";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DEEPSEEK推理模型
|
||||||
|
*/
|
||||||
|
public static final String DEEPSEEK_REASONER = "deepseek-reasoner";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库类型:知识库
|
||||||
|
*/
|
||||||
|
public static final String KNOWLEDGE_TYPE_KNOWLEDGE = "knowledge";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库类型:记忆库
|
||||||
|
*/
|
||||||
|
public static final String KNOWLEDGE_TYPE_MEMORY = "memory";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支持文件的后缀
|
||||||
|
*/
|
||||||
|
public static final Set<String> CHAT_FILE_EXT_WHITELIST = new HashSet<>(Arrays.asList("txt", "pdf", "docx", "doc", "pptx", "ppt", "xlsx", "xls", "md"));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件内容最大长度
|
||||||
|
*/
|
||||||
|
public static final int CHAT_FILE_TEXT_MAX_LENGTH = 20000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件对打数量
|
||||||
|
*/
|
||||||
|
public static final int CHAT_FILE_MAX_COUNT = 3;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
package org.jeecg.modules.airag.llm.controller;
|
||||||
|
|
||||||
|
import org.jeecg.common.airag.api.IAiragBaseApi;
|
||||||
|
import org.jeecg.modules.airag.llm.service.impl.AiragBaseApiImpl;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* airag baseAPI Controller
|
||||||
|
*
|
||||||
|
* @author sjlei
|
||||||
|
* @date 2025-12-30
|
||||||
|
*/
|
||||||
|
@RestController("airagBaseApiController")
|
||||||
|
public class AiragBaseApiController implements IAiragBaseApi {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
AiragBaseApiImpl airagBaseApi;
|
||||||
|
|
||||||
|
@PostMapping("/airag/api/knowledgeWriteTextDocument")
|
||||||
|
public String knowledgeWriteTextDocument(
|
||||||
|
@RequestParam("knowledgeId") String knowledgeId,
|
||||||
|
@RequestParam("title") String title,
|
||||||
|
@RequestParam("content") String content
|
||||||
|
) {
|
||||||
|
return airagBaseApi.knowledgeWriteTextDocument(knowledgeId, title, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user