diff --git a/jeecg-boot/jeecg-boot-base-core/pom.xml b/jeecg-boot/jeecg-boot-base-core/pom.xml
index 9ad9b1ee..9edcc854 100644
--- a/jeecg-boot/jeecg-boot-base-core/pom.xml
+++ b/jeecg-boot/jeecg-boot-base-core/pom.xml
@@ -111,6 +111,11 @@
mybatis-plus-boot-starter
${mybatis-plus.version}
+
+
+ org.jeecgframework
+ minidao-spring-boot-starter
+
@@ -314,10 +319,5 @@
org.jeecgframework.boot
jeecg-boot-starter-chatgpt
-
-
- org.jeecgframework
- minidao-spring-boot-starter
-
\ No newline at end of file
diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/vo/Result.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/vo/Result.java
index 2db1edc2..902c6674 100644
--- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/vo/Result.java
+++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/vo/Result.java
@@ -1,6 +1,7 @@
package org.jeecg.common.api.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
+
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.jeecg.common.constant.CommonConstant;
diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/DySmsEnum.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/DySmsEnum.java
index c1e6f34a..d39b43c1 100644
--- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/DySmsEnum.java
+++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/DySmsEnum.java
@@ -9,14 +9,14 @@ import org.apache.commons.lang3.StringUtils;
public enum DySmsEnum {
/**登录短信模板编码*/
- LOGIN_TEMPLATE_CODE("SMS_175435174","敲敲云","code"),
+ LOGIN_TEMPLATE_CODE("SMS_175435174","敲敲云","code"),
/**忘记密码短信模板编码*/
- FORGET_PASSWORD_TEMPLATE_CODE("SMS_175435174","敲敲云","code"),
- /**修改密码短信模板编码*/
- CHANGE_PASSWORD_TEMPLATE_CODE("SMS_465391221","敲敲云","code"),
- /**注册账号短信模板编码*/
- REGISTER_TEMPLATE_CODE("SMS_175430166","敲敲云","code");
-
+ FORGET_PASSWORD_TEMPLATE_CODE("SMS_175435174","敲敲云","code"),
+ /**修改密码短信模板编码*/
+ CHANGE_PASSWORD_TEMPLATE_CODE("SMS_465391221","敲敲云","code"),
+ /**注册账号短信模板编码*/
+ REGISTER_TEMPLATE_CODE("SMS_175430166","敲敲云","code");
+
/**
* 短信模板编码
*/
diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
index 18965999..d13f58b8 100644
--- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
+++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
@@ -1,6 +1,7 @@
package org.jeecg.common.exception;
import cn.hutool.core.util.ObjectUtil;
+import io.undertow.server.RequestTooBigException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.shiro.SecurityUtils;
@@ -30,6 +31,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
+import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.annotation.Resource;
@@ -56,7 +58,7 @@ public class JeecgBootExceptionHandler {
addSysLog(e);
return Result.error("校验失败!" + e.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(",")));
}
-
+
/**
* 处理自定义异常
*/
@@ -166,6 +168,27 @@ public class JeecgBootExceptionHandler {
return Result.error("文件大小超出10MB限制, 请压缩或降低文件质量! ");
}
+ /**
+ * 处理文件过大异常.
+ * jdk17中的MultipartException异常类已经被拆分成了MultipartException和MaxUploadSizeExceededException
+ * for [QQYUN-11716]上传大图片失败没有精确提示
+ * @param e
+ * @return
+ * @author chenrui
+ * @date 2025/4/8 16:13
+ */
+ @ExceptionHandler(MultipartException.class)
+ public Result> handleMaxUploadSizeExceededException(MultipartException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof IllegalStateException && cause.getCause() instanceof RequestTooBigException) {
+ log.error("文件大小超出限制: {}", cause.getMessage(), e);
+ addSysLog(e);
+ return Result.error("文件大小超出限制, 请压缩或降低文件质量!");
+ } else {
+ return handleException(e);
+ }
+ }
+
@ExceptionHandler(DataIntegrityViolationException.class)
public Result> handleDataIntegrityViolationException(DataIntegrityViolationException e) {
log.error(e.getMessage(), e);
@@ -221,11 +244,16 @@ public class JeecgBootExceptionHandler {
} catch (NullPointerException | BeansException ignored) {
}
if (null != request) {
+ //update-begin---author:chenrui ---date:20250408 for:[QQYUN-11716]上传大图片失败没有精确提示------------
//请求的参数
- Map parameterMap = request.getParameterMap();
- if(!CollectionUtils.isEmpty(parameterMap)){
- log.setMethod(oConvertUtils.mapToString(request.getParameterMap()));
+ if (!isTooBigException(e)) {
+ // 文件上传过大异常时不能获取参数,否则会报错
+ Map parameterMap = request.getParameterMap();
+ if(!CollectionUtils.isEmpty(parameterMap)) {
+ log.setMethod(oConvertUtils.mapToString(request.getParameterMap()));
+ }
}
+ //update-end---author:chenrui ---date:20250408 for:[QQYUN-11716]上传大图片失败没有精确提示------------
// 请求地址
log.setRequestUrl(request.getRequestURI());
//设置IP地址
@@ -251,4 +279,26 @@ public class JeecgBootExceptionHandler {
}
//update-end---author:chenrui ---date:20240423 for:[QQYUN-8732]把错误的日志都抓取了 方便后续处理,单独弄个日志类型------------
+ /**
+ * 是否文件过大异常
+ * for [QQYUN-11716]上传大图片失败没有精确提示
+ * @param e
+ * @return
+ * @author chenrui
+ * @date 2025/4/8 20:21
+ */
+ private static boolean isTooBigException(Throwable e) {
+ boolean isTooBigException = false;
+ if(e instanceof MultipartException){
+ Throwable cause = e.getCause();
+ if (cause instanceof IllegalStateException && cause.getCause() instanceof RequestTooBigException){
+ isTooBigException = true;
+ }
+ }
+ if(e instanceof MaxUploadSizeExceededException){
+ isTooBigException = true;
+ }
+ return isTooBigException;
+ }
+
}
diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/entity/JeecgEntity.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/entity/JeecgEntity.java
index 4d49cf7d..c4e6603a 100644
--- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/entity/JeecgEntity.java
+++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/entity/JeecgEntity.java
@@ -2,7 +2,6 @@ package org.jeecg.common.system.base.entity;
import java.io.Serializable;
-import io.swagger.v3.oas.annotations.media.Schema;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
@@ -10,6 +9,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RestUtil.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RestUtil.java
index 91f83390..f045c7da 100644
--- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RestUtil.java
+++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RestUtil.java
@@ -36,6 +36,7 @@ public class RestUtil {
}
return domain;
}
+
private static String getPath() {
if (path == null) {
path = SpringContextUtils.getApplicationContext().getEnvironment().getProperty("server.servlet.context-path");
diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java
index 853551c2..673fe734 100644
--- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java
+++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java
@@ -5,7 +5,6 @@ import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.exception.JeecgSqlInjectionException;
-
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/dynamic/db/DbTypeUtils.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/dynamic/db/DbTypeUtils.java
index 96a0a085..20aa47bc 100644
--- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/dynamic/db/DbTypeUtils.java
+++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/dynamic/db/DbTypeUtils.java
@@ -68,6 +68,13 @@ public class DbTypeUtils {
return dbTypeIf(dbType, DbType.ORACLE, DbType.ORACLE_12C, DbType.DM);
}
+ /**
+ * 是否是达梦
+ */
+ public static boolean dbTypeIsDm(DbType dbType) {
+ return dbTypeIf(dbType, DbType.DM);
+ }
+
public static boolean dbTypeIsSqlServer(DbType dbType) {
return dbTypeIf(dbType, DbType.SQL_SERVER, DbType.SQL_SERVER2005);
}
diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/oConvertUtils.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/oConvertUtils.java
index 11f16b7b..33e6fda0 100644
--- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/oConvertUtils.java
+++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/oConvertUtils.java
@@ -7,6 +7,7 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.SymbolConstant;
+import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
import org.springframework.beans.BeanUtils;
import javax.servlet.http.HttpServletRequest;
@@ -1133,5 +1134,14 @@ public class oConvertUtils {
public static boolean isIn(T obj, T... objs) {
return isIn(obj, objs);
}
+
+ /**
+ * 判断租户ID是否有效
+ * @param tenantId
+ * @return
+ */
+ public static boolean isEffectiveTenant(String tenantId) {
+ return MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL && isNotEmpty(tenantId) && !("0").equals(tenantId);
+ }
}
diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger2Config.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger2Config.java
index 484624b9..59daba8f 100644
--- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger2Config.java
+++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger2Config.java
@@ -1,8 +1,9 @@
//package org.jeecg.config;
//
//
-//import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
+//import io.swagger.v3.oas.annotations.Operation;
//import org.jeecg.common.constant.CommonConstant;
+//import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
//import org.springframework.beans.BeansException;
//import org.springframework.beans.factory.config.BeanPostProcessor;
//import org.springframework.context.annotation.Bean;
@@ -18,15 +19,13 @@
//import springfox.documentation.builders.ParameterBuilder;
//import springfox.documentation.builders.PathSelectors;
//import springfox.documentation.builders.RequestHandlerSelectors;
-//import springfox.documentation.oas.annotations.EnableOpenApi;
//import springfox.documentation.schema.ModelRef;
//import springfox.documentation.service.*;
//import springfox.documentation.spi.DocumentationType;
//import springfox.documentation.spi.service.contexts.SecurityContext;
//import springfox.documentation.spring.web.plugins.Docket;
-//import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
//import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
-//import springfox.documentation.swagger2.annotations.EnableSwagger2;
+//import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
//
//import java.lang.reflect.Field;
//import java.util.ArrayList;
@@ -38,8 +37,7 @@
// * @Author scott
// */
//@Configuration
-//@EnableSwagger2 //开启 Swagger2
-//@EnableKnife4j //开启 knife4j,可以不写
+//@EnableSwagger2WebMvc
//@Import(BeanValidatorPluginsConfiguration.class)
//public class Swagger2Config implements WebMvcConfigurer {
//
@@ -97,6 +95,14 @@
// List pars = new ArrayList<>();
// tokenPar.name(CommonConstant.X_ACCESS_TOKEN).description("token").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
// pars.add(tokenPar.build());
+// //update-begin-author:liusq---date:2024-08-15--for: 开启多租户时,全局参数增加租户id
+// if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL){
+// ParameterBuilder tenantPar = new ParameterBuilder();
+// tenantPar.name(CommonConstant.TENANT_ID).description("租户ID").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
+// pars.add(tenantPar.build());
+// }
+// //update-end-author:liusq---date:2024-08-15--for: 开启多租户时,全局参数增加租户id
+//
// return pars;
// }
//
@@ -151,7 +157,7 @@
//
// @Override
// public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
-// if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
+// if (bean instanceof WebMvcRequestHandlerProvider) {
// customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
// }
// return bean;
diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/firewall/interceptor/LowCodeModeInterceptor.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/firewall/interceptor/LowCodeModeInterceptor.java
index 9bdcbd64..bb207d65 100644
--- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/firewall/interceptor/LowCodeModeInterceptor.java
+++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/firewall/interceptor/LowCodeModeInterceptor.java
@@ -6,12 +6,15 @@ import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.CommonAPI;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.CommonUtils;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.config.JeecgBaseConfig;
+import org.jeecg.config.firewall.interceptor.enums.LowCodeUrlsEnum;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
diff --git a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
index e2a68a96..ba9a0dbf 100644
--- a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
+++ b/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
@@ -221,6 +221,7 @@ public class ShiroConfig {
registration.addUrlPatterns("/airag/flow/debug");
registration.addUrlPatterns("/airag/chat/send");
registration.addUrlPatterns("/airag/app/debug");
+ registration.addUrlPatterns("/airag/app/prompt/generate");
//支持异步
registration.setAsyncSupported(true);
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
@@ -320,7 +321,7 @@ public class ShiroConfig {
return sentinelManager;
}
-
+
// redis 单机支持,在集群为空,或者集群无机器时候使用 add by jzyadmin@163.com
if (lettuceConnectionFactory.getClusterConfiguration() == null || lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().isEmpty()) {
RedisManager redisManager = new RedisManager();
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/pom.xml b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/pom.xml
index c2c1ed9b..bce5b662 100644
--- a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/pom.xml
+++ b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/pom.xml
@@ -53,13 +53,29 @@
jeecg-system-cloud-api
-->
+
- org.jeecgframework.boot
- jeecg-aiflow
- 1.0.4
+ org.jeecgframework
+ ai-flow
+ 1.1.0
+
+
+
+ com.yomahub
+ liteflow-spring-boot-starter
+ ${liteflow.version}
+
+
+ commons-lang
+ commons-lang
+
+
+
+
+ com.yomahub
+ liteflow-rule-sql
+ ${liteflow.version}
-
-
com.yomahub
liteflow-script-graaljs
@@ -84,6 +100,11 @@
runtime
+
+ org.jetbrains.kotlin
+ kotlin-scripting-jsr223
+ ${kotlin.version}
+
com.yomahub
liteflow-script-aviator
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/consts/AiAppConsts.java b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/consts/AiAppConsts.java
index c38ee0e2..b8626942 100644
--- a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/consts/AiAppConsts.java
+++ b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/consts/AiAppConsts.java
@@ -16,6 +16,10 @@ public class AiAppConsts {
* 状态:禁用
*/
public static final String STATUS_DISABLE = "disable";
+ /**
+ * 状态:发布
+ */
+ public static final String STATUS_RELEASE = "release";
/**
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/controller/AiragAppController.java b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/controller/AiragAppController.java
index 6a81852f..e13f13e9 100644
--- a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/controller/AiragAppController.java
+++ b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/controller/AiragAppController.java
@@ -4,10 +4,13 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.AssertUtils;
+import org.jeecg.common.util.TokenUtils;
+import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
import org.jeecg.config.shiro.IgnoreAuth;
import org.jeecg.modules.airag.app.consts.AiAppConsts;
import org.jeecg.modules.airag.app.entity.AiragApp;
@@ -19,7 +22,6 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.servlet.http.HttpServletRequest;
-import java.util.Arrays;
/**
* @Description: AI应用
@@ -64,6 +66,7 @@ public class AiragAppController extends JeecgController edit(@RequestBody AiragApp airagApp) {
AssertUtils.assertNotEmpty("参数异常", airagApp);
AssertUtils.assertNotEmpty("请输入应用名称", airagApp.getName());
@@ -73,6 +76,28 @@ public class AiragAppController extends JeecgController release(@RequestParam(name = "id") String id, @RequestParam(name = "release") Boolean release) {
+ AssertUtils.assertNotEmpty("id必须填写", id);
+ if (release == null) {
+ release = true;
+ }
+ AiragApp airagApp = new AiragApp();
+ airagApp.setId(id);
+ if (release) {
+ airagApp.setStatus(AiAppConsts.STATUS_RELEASE);
+ } else {
+ airagApp.setStatus(AiAppConsts.STATUS_ENABLE);
+ }
+ airagAppService.updateById(airagApp);
+ return Result.OK(release ? "发布成功" : "取消发布成功");
+ }
+
/**
* 通过id删除
*
@@ -80,23 +105,23 @@ public class AiragAppController extends JeecgController delete(@RequestParam(name = "id", required = true) String id) {
+ @RequiresPermissions("airag:app:delete")
+ public Result delete(HttpServletRequest request,@RequestParam(name = "id", required = true) String id) {
+ //update-begin---author:chenrui ---date:20250606 for:[issues/8337]关于ai工作列表的数据权限问题 #8337------------
+ //如果是saas隔离的情况下,判断当前租户id是否是当前租户下的
+ if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
+ AiragApp app = airagAppService.getById(id);
+ //获取当前租户
+ String currentTenantId = TokenUtils.getTenantIdByRequest(request);
+ if (null == app || !app.getTenantId().equals(currentTenantId)) {
+ return Result.error("删除AI应用失败,不能删除其他租户的AI应用!");
+ }
+ }
+ //update-end---author:chenrui ---date:20250606 for:[issues/8337]关于ai工作列表的数据权限问题 #8337------------
airagAppService.removeById(id);
return Result.OK("删除成功!");
}
- /**
- * 批量删除
- *
- * @param ids
- * @return
- */
- @DeleteMapping(value = "/deleteBatch")
- public Result deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
- this.airagAppService.removeByIds(Arrays.asList(ids.split(",")));
- return Result.OK("批量删除成功!");
- }
-
/**
* 通过id查询
*
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/controller/AiragChatController.java b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/controller/AiragChatController.java
index 1c1aebb4..62fec9a3 100644
--- a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/controller/AiragChatController.java
+++ b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/controller/AiragChatController.java
@@ -2,14 +2,22 @@ package org.jeecg.modules.airag.app.controller;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.util.CommonUtils;
import org.jeecg.config.shiro.IgnoreAuth;
import org.jeecg.modules.airag.app.service.IAiragChatService;
import org.jeecg.modules.airag.app.vo.ChatConversation;
import org.jeecg.modules.airag.app.vo.ChatSendParams;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
/**
* airag应用-chat
@@ -25,6 +33,15 @@ public class AiragChatController {
@Autowired
IAiragChatService chatService;
+ @Value(value = "${jeecg.path.upload}")
+ private String uploadpath;
+
+ /**
+ * 本地:local minio:minio 阿里:alioss
+ */
+ @Value(value="${jeecg.uploadType}")
+ private String uploadType;
+
/**
* 发送消息
@@ -59,6 +76,19 @@ public class AiragChatController {
return chatService.send(chatSendParams);
}
+ /**
+ * 获取所有对话
+ *
+ * @return 返回一个Result对象,包含所有对话的信息
+ * @author chenrui
+ * @date 2025/2/25 11:42
+ */
+ @IgnoreAuth
+ @GetMapping(value = "/init")
+ public Result> initChat(@RequestParam(name = "id", required = true) String id) {
+ return chatService.initChat(id);
+ }
+
/**
* 获取所有对话
*
@@ -141,4 +171,36 @@ public class AiragChatController {
return chatService.stop(requestId);
}
+
+ /**
+ * 上传文件
+ * for [QQYUN-12135]AI聊天,上传图片提示非法token
+ *
+ * @param request
+ * @param response
+ * @return
+ * @throws Exception
+ * @author chenrui
+ * @date 2025/4/25 11:04
+ */
+ @IgnoreAuth
+ @PostMapping(value = "/upload")
+ public Result> upload(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ String bizPath = "airag";
+
+ MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
+ // 获取上传文件对象
+ MultipartFile file = multipartRequest.getFile("file");
+ String savePath;
+ if (CommonConstant.UPLOAD_TYPE_LOCAL.equals(uploadType)) {
+ savePath = CommonUtils.uploadLocal(file, bizPath, uploadpath);
+ } else {
+ savePath = CommonUtils.upload(file, bizPath, uploadType);
+ }
+ Result> result = new Result<>();
+ result.setMessage(savePath);
+ result.setSuccess(true);
+ return result;
+ }
+
}
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/entity/AiragApp.java b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/entity/AiragApp.java
index e5204b5a..0659c877 100644
--- a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/entity/AiragApp.java
+++ b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/entity/AiragApp.java
@@ -39,12 +39,13 @@ public class AiragApp implements Serializable {
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
- private String id;
+ private java.lang.String id;
/**
* 创建人
*/
@Schema(description = "创建人")
- private String createBy;
+ @Dict(dictTable = "sys_user",dicCode = "username",dicText = "realname")
+ private java.lang.String createBy;
/**
* 创建日期
*/
@@ -56,7 +57,7 @@ public class AiragApp implements Serializable {
* 更新人
*/
@Schema(description = "更新人")
- private String updateBy;
+ private java.lang.String updateBy;
/**
* 更新日期
*/
@@ -68,95 +69,95 @@ public class AiragApp implements Serializable {
* 所属部门
*/
@Schema(description = "所属部门")
- private String sysOrgCode;
+ private java.lang.String sysOrgCode;
/**
* 租户id
*/
@Excel(name = "租户id", width = 15)
@Schema(description = "租户id")
- private String tenantId;
+ private java.lang.String tenantId;
/**
* 应用名称
*/
@Excel(name = "应用名称", width = 15)
@Schema(description = "应用名称")
- private String name;
+ private java.lang.String name;
/**
* 应用描述
*/
@Excel(name = "应用描述", width = 15)
@Schema(description = "应用描述")
- private String descr;
+ private java.lang.String descr;
/**
* 应用图标
*/
@Excel(name = "应用图标", width = 15)
@Schema(description = "应用图标")
- private String icon;
+ private java.lang.String icon;
/**
* 应用类型
*/
@Excel(name = "应用类型", width = 15, dicCode = "ai_app_type")
@Dict(dicCode = "ai_app_type")
@Schema(description = "应用类型")
- private String type;
+ private java.lang.String type;
/**
* 开场白
*/
@Excel(name = "开场白", width = 15)
@Schema(description = "开场白")
- private String prologue;
+ private java.lang.String prologue;
/**
* 预设问题
*/
@Excel(name = "预设问题", width = 15)
@Schema(description = "预设问题")
- private String presetQuestion;
+ private java.lang.String presetQuestion;
/**
* 提示词
*/
@Excel(name = "提示词", width = 15)
@Schema(description = "提示词")
- private String prompt;
+ private java.lang.String prompt;
/**
* 模型配置
*/
@Excel(name = "模型配置", width = 15, dictTable = "airag_model where model_type = 'LLM' ", dicText = "name", dicCode = "id")
@Dict(dictTable = "airag_model where model_type = 'LLM' ", dicText = "name", dicCode = "id")
@Schema(description = "模型配置")
- private String modelId;
+ private java.lang.String modelId;
/**
* 历史消息数
*/
@Excel(name = "历史消息数", width = 15)
@Schema(description = "历史消息数")
- private Integer msgNum;
+ private java.lang.Integer msgNum;
/**
* 知识库
*/
@Excel(name = "知识库", width = 15, dictTable = "airag_knowledge where status = 'enable'", dicText = "name", dicCode = "id")
@Dict(dictTable = "airag_knowledge where status = 'enable'", dicText = "name", dicCode = "id")
@Schema(description = "知识库")
- private String knowledgeIds;
+ private java.lang.String knowledgeIds;
/**
* 流程
*/
@Excel(name = "流程", width = 15, dictTable = "airag_flow where status = 'enable' ", dicText = "name", dicCode = "id")
@Dict(dictTable = "airag_flow where status = 'enable' ", dicText = "name", dicCode = "id")
@Schema(description = "流程")
- private String flowId;
+ private java.lang.String flowId;
/**
* 快捷指令
*/
@Excel(name = "快捷指令", width = 15)
@Schema(description = "快捷指令")
- private String quickCommand;
+ private java.lang.String quickCommand;
/**
- * 状态
+ * 状态(enable=启用、disable=禁用、release=发布)
*/
@Excel(name = "状态", width = 15)
@Schema(description = "状态")
- private String status;
+ private java.lang.String status;
/**
@@ -164,7 +165,7 @@ public class AiragApp implements Serializable {
*/
@Excel(name = "元数据", width = 15)
@Schema(description = "元数据")
- private String metadata;
+ private java.lang.String metadata;
/**
* 知识库ids
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/mapper/AiragAppMapper.java b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/mapper/AiragAppMapper.java
index 05e870d8..718c016e 100644
--- a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/mapper/AiragAppMapper.java
+++ b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/mapper/AiragAppMapper.java
@@ -1,5 +1,6 @@
package org.jeecg.modules.airag.app.mapper;
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.airag.app.entity.AiragApp;
@@ -11,4 +12,14 @@ import org.jeecg.modules.airag.app.entity.AiragApp;
*/
public interface AiragAppMapper extends BaseMapper {
+ /**
+ * 根据ID查询app信息(忽略租户)
+ * @param id
+ * @return
+ * @author chenrui
+ * @date 2025/4/21 16:03
+ */
+ @InterceptorIgnore(tenantLine = "true")
+ AiragApp getByIdIgnoreTenant(String id);
+
}
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/mapper/xml/AiragAppMapper.xml b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/mapper/xml/AiragAppMapper.xml
index 2dce98bf..f3f9da6f 100644
--- a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/mapper/xml/AiragAppMapper.xml
+++ b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/mapper/xml/AiragAppMapper.xml
@@ -2,4 +2,8 @@
+
+
\ No newline at end of file
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/service/IAiragChatService.java b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/service/IAiragChatService.java
index 916c9ed1..fc8e197a 100644
--- a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/service/IAiragChatService.java
+++ b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/service/IAiragChatService.java
@@ -92,4 +92,14 @@ public interface IAiragChatService {
* @date 2025/3/3 19:49
*/
Result> clearMessage(String conversationId);
+
+ /**
+ * 初始化聊天(忽略租户)
+ * [QQYUN-12113]分享之后的聊天,应用、模型、知识库不根据租户查询
+ * @param appId
+ * @return
+ * @author chenrui
+ * @date 2025/4/21 14:17
+ */
+ Result> initChat(String appId);
}
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/service/impl/AiragChatServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/service/impl/AiragChatServiceImpl.java
index c198b215..9bfbd423 100644
--- a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/service/impl/AiragChatServiceImpl.java
+++ b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/service/impl/AiragChatServiceImpl.java
@@ -13,6 +13,7 @@ import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.*;
import org.jeecg.modules.airag.app.consts.AiAppConsts;
import org.jeecg.modules.airag.app.entity.AiragApp;
+import org.jeecg.modules.airag.app.mapper.AiragAppMapper;
import org.jeecg.modules.airag.app.service.IAiragAppService;
import org.jeecg.modules.airag.app.service.IAiragChatService;
import org.jeecg.modules.airag.app.vo.AppDebugParams;
@@ -63,7 +64,7 @@ public class AiragChatServiceImpl implements IAiragChatService {
RedisTemplate redisTemplate;
@Autowired
- IAiragAppService airagAppService;
+ AiragAppMapper airagAppMapper;
@Autowired
IAiragFlowService airagFlowService;
@@ -85,7 +86,7 @@ public class AiragChatServiceImpl implements IAiragChatService {
// 获取app信息
AiragApp app = null;
if (oConvertUtils.isNotEmpty(chatSendParams.getAppId())) {
- app = airagAppService.getById(chatSendParams.getAppId());
+ app = airagAppMapper.getByIdIgnoreTenant(chatSendParams.getAppId());
}
ChatConversation chatConversation = getOrCreateChatConversation(app, conversationId);
// 更新标题
@@ -146,13 +147,19 @@ public class AiragChatServiceImpl implements IAiragChatService {
try {
// 发送完成事件
emitter.send(SseEmitter.event().data(eventData));
- } catch (IOException e) {
+ } catch (Exception e) {
log.error("终止会话时发生错误", e);
+ try {
+ // 防止异常冒泡
+ emitter.completeWithError(e);
+ } catch (Exception ignore) {}
} finally {
// 从缓存中移除emitter
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE, eventData.getRequestId());
// 关闭emitter
- emitter.complete();
+ try {
+ emitter.complete();
+ } catch (Exception ignore) {}
}
}
@@ -237,6 +244,12 @@ public class AiragChatServiceImpl implements IAiragChatService {
return Result.ok();
}
+ @Override
+ public Result> initChat(String appId) {
+ AiragApp app = airagAppMapper.getByIdIgnoreTenant(appId);
+ return Result.ok(app);
+ }
+
@Override
public Result> deleteConversation(String conversationId) {
AssertUtils.assertNotEmpty("请选择要删除的会话", conversationId);
@@ -278,7 +291,7 @@ public class AiragChatServiceImpl implements IAiragChatService {
* @author chenrui
* @date 2025/2/25 19:27
*/
- private String getConversationCacheKey(String conversationId,HttpServletRequest httpRequest) {
+ private String getConversationCacheKey(String conversationId, HttpServletRequest httpRequest) {
if (oConvertUtils.isEmpty(conversationId)) {
return null;
}
@@ -376,7 +389,7 @@ public class AiragChatServiceImpl implements IAiragChatService {
* @author chenrui
* @date 2025/2/25 19:27
*/
- private void saveChatConversation(ChatConversation chatConversation, boolean temp,HttpServletRequest httpRequest) {
+ private void saveChatConversation(ChatConversation chatConversation, boolean temp, HttpServletRequest httpRequest) {
if (null == chatConversation) {
return;
}
@@ -414,8 +427,7 @@ public class AiragChatServiceImpl implements IAiragChatService {
case AiragConsts.MESSAGE_ROLE_USER:
List contents = new ArrayList<>();
List images = history.getImages();
- if (oConvertUtils.isObjectNotEmpty(images)
- && !images.isEmpty()) {
+ if (oConvertUtils.isObjectNotEmpty(images) && !images.isEmpty()) {
contents.addAll(images.stream().map(imageHistory -> {
if (oConvertUtils.isNotEmpty(imageHistory.getUrl())) {
return ImageContent.from(imageHistory.getUrl());
@@ -452,8 +464,7 @@ public class AiragChatServiceImpl implements IAiragChatService {
* @author chenrui
* @date 2025/2/25 19:05
*/
- private void appendMessage(List messages, ChatMessage message, ChatConversation
- chatConversation, String topicId) {
+ private void appendMessage(List messages, ChatMessage message, ChatConversation chatConversation, String topicId) {
if (message.type().equals(ChatMessageType.SYSTEM)) {
// 系统消息,放到消息列表最前面,并且不记录历史
@@ -467,11 +478,7 @@ public class AiragChatServiceImpl implements IAiragChatService {
histories = new ArrayList<>();
}
// 消息记录
- MessageHistory historyMessage = MessageHistory.builder()
- .conversationId(chatConversation.getId())
- .topicId(topicId)
- .datetime(DateUtils.now())
- .build();
+ MessageHistory historyMessage = MessageHistory.builder().conversationId(chatConversation.getId()).topicId(topicId).datetime(DateUtils.now()).build();
if (message.type().equals(ChatMessageType.USER)) {
historyMessage.setRole(AiragConsts.MESSAGE_ROLE_USER);
StringBuilder textContent = new StringBuilder();
@@ -516,8 +523,21 @@ public class AiragChatServiceImpl implements IAiragChatService {
// 每次会话都生成一个新的,用来缓存emitter
String requestId = UUIDGenerator.generate();
SseEmitter emitter = new SseEmitter(-0L);
+ emitter.onError(throwable -> {
+ log.warn("SEE向客户端发送消息失败: {}", throwable.getMessage());
+ AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE, requestId);
+ try {
+ emitter.complete();
+ } catch (Exception ignore) {}
+ });
+ EventData eventRequestId = new EventData(requestId, null, EventData.EVENT_INIT_REQUEST_ID, chatConversation.getId(), topicId);
+ eventRequestId.setData(EventMessageData.builder().message("").build());
+ sendMessage2Client(emitter, eventRequestId);
// 缓存emitter
AiragLocalCache.put(AiragConsts.CACHE_TYPE_SSE, requestId, emitter);
+ // 缓存开始发送时间
+ log.info("[AI-CHAT]开始发送消息,requestId:{}", requestId);
+ AiragLocalCache.put(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId, System.currentTimeMillis());
try {
// 组装用户消息
UserMessage userMessage = aiChatHandler.buildUserMessage(sendParams.getContent(), sendParams.getImages());
@@ -589,36 +609,51 @@ public class AiragChatServiceImpl implements IAiragChatService {
SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
flowRunParams.setEventCallback(eventData -> {
if (EventData.EVENT_FLOW_FINISHED.equals(eventData.getEvent())) {
+ // 打印耗时日志
+ printChatDuration(requestId, "流程执行完毕");
+ // 已经执行完了,删除时间缓存
+ AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId);
EventFlowData data = (EventFlowData) eventData.getData();
- Object outputs = data.getOutputs();
- if (oConvertUtils.isObjectNotEmpty(outputs)) {
- AiMessage aiMessage;
- if (outputs instanceof String) {
- // 兼容推理模型
- String messageText = String.valueOf(outputs);
- messageText = messageText.replaceAll("([\\s\\S]*?)", "> $1");
- aiMessage = new AiMessage(messageText);
- } else {
- aiMessage = new AiMessage(JSONObject.toJSONString(outputs));
+ if(data.isSuccess()) {
+ Object outputs = data.getOutputs();
+ if (oConvertUtils.isObjectNotEmpty(outputs)) {
+ AiMessage aiMessage;
+ if (outputs instanceof String) {
+ // 兼容推理模型
+ String messageText = String.valueOf(outputs);
+ messageText = messageText.replaceAll("([\\s\\S]*?)", "> $1");
+ aiMessage = new AiMessage(messageText);
+ } else {
+ aiMessage = new AiMessage(JSONObject.toJSONString(outputs));
+ }
+ EventData msgEventData = new EventData(requestId, null, EventData.EVENT_MESSAGE, chatConversation.getId(), topicId);
+ EventMessageData messageEventData = EventMessageData.builder().message(aiMessage.text()).build();
+ msgEventData.setData(messageEventData);
+ msgEventData.setRequestId(requestId);
+ sendMessage2Client(emitter, msgEventData);
+ appendMessage(messages, aiMessage, chatConversation, topicId);
+ // 保存会话
+ saveChatConversation(chatConversation, false, httpRequest);
}
- EventData msgEventData = new EventData(requestId, null, EventData.EVENT_MESSAGE, chatConversation.getId(), topicId);
- EventMessageData messageEventData = EventMessageData.builder()
- .message(aiMessage.text())
- .build();
- msgEventData.setData(messageEventData);
- try {
- String eventStr = JSONObject.toJSONString(msgEventData);
- log.debug("[AI应用]接收FLOW返回消息:{}", eventStr);
- emitter.send(SseEmitter.event().data(eventStr));
- } catch (IOException e) {
- throw new RuntimeException(e);
+ }else{
+ //update-begin---author:chenrui ---date:20250425 for:[QQYUN-12203]AI 聊天,超时或者服务器报错,给个友好提示------------
+ // 失败
+ String message = data.getMessage();
+ if (message != null && message.contains(FlowConsts.FLOW_ERROR_MSG_LLM_TIMEOUT)) {
+ message = "当前用户较多,排队中,请稍后再试!";
+ EventData errEventData = new EventData(requestId, null, EventData.EVENT_MESSAGE, chatConversation.getId(), topicId);
+ errEventData.setData(EventMessageData.builder().message("\n" + message).build());
+ sendMessage2Client(emitter, errEventData);
+ errEventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END, chatConversation.getId(), topicId);
+ // 如果是超时,主动关闭SSE,防止流程切面中返回异常消息导致前端不能正常展示上面的{普通消息}.
+ closeSSE(emitter, errEventData);
}
- appendMessage(messages, aiMessage, chatConversation, topicId);
- // 保存会话
- saveChatConversation(chatConversation, false, httpRequest);
+ //update-end---author:chenrui ---date:20250425 for:[QQYUN-12203]AI 聊天,超时或者服务器报错,给个友好提示------------
}
}
});
+ // 打印流程耗时日志
+ printChatDuration(requestId, "开始执行流程");
airagFlowService.runFlow(flowRunParams);
}
@@ -649,24 +684,26 @@ public class AiragChatServiceImpl implements IAiragChatService {
String metadataStr = aiApp.getMetadata();
if (oConvertUtils.isNotEmpty(metadataStr)) {
JSONObject metadata = JSONObject.parseObject(metadataStr);
- if(oConvertUtils.isNotEmpty(metadata)){
+ if (oConvertUtils.isNotEmpty(metadata)) {
if (metadata.containsKey("temperature")) {
aiChatParams.setTemperature(metadata.getDouble("temperature"));
}
if (metadata.containsKey("topP")) {
- aiChatParams.setTopP(metadata.getDouble("temperature"));
+ aiChatParams.setTopP(metadata.getDouble("topP"));
}
if (metadata.containsKey("presencePenalty")) {
- aiChatParams.setPresencePenalty(metadata.getDouble("temperature"));
+ aiChatParams.setPresencePenalty(metadata.getDouble("presencePenalty"));
}
if (metadata.containsKey("frequencyPenalty")) {
- aiChatParams.setFrequencyPenalty(metadata.getDouble("temperature"));
+ aiChatParams.setFrequencyPenalty(metadata.getDouble("frequencyPenalty"));
}
if (metadata.containsKey("maxTokens")) {
- aiChatParams.setMaxTokens(metadata.getInteger("temperature"));
+ aiChatParams.setMaxTokens(metadata.getInteger("maxTokens"));
}
}
}
+ // 打印流程耗时日志
+ printChatDuration(requestId, "构造应用自定义参数完成");
// 发消息
sendWithDefault(requestId, chatConversation, topicId, modelId, messages, aiChatParams);
}
@@ -683,10 +720,9 @@ public class AiragChatServiceImpl implements IAiragChatService {
* @author chenrui
* @date 2025/2/25 19:24
*/
- private void sendWithDefault(String requestId, ChatConversation chatConversation, String topicId, String modelId,
- List messages,AIChatParams aiChatParams) {
+ private void sendWithDefault(String requestId, ChatConversation chatConversation, String topicId, String modelId, List messages, AIChatParams aiChatParams) {
// 调用ai聊天
- if(null == aiChatParams){
+ if (null == aiChatParams) {
aiChatParams = new AIChatParams();
}
aiChatParams.setKnowIds(chatConversation.getApp().getKnowIds());
@@ -694,13 +730,15 @@ public class AiragChatServiceImpl implements IAiragChatService {
HttpServletRequest httpRequest = SpringContextUtils.getHttpServletRequest();
TokenStream chatStream;
try {
+ // 打印流程耗时日志
+ printChatDuration(requestId, "开始向LLM发送消息");
if (oConvertUtils.isNotEmpty(modelId)) {
chatStream = aiChatHandler.chat(modelId, messages, aiChatParams);
} else {
chatStream = aiChatHandler.chatByDefaultModel(messages, aiChatParams);
}
} catch (Exception e) {
- log.error(e.getMessage(),e);
+ log.error(e.getMessage(), e);
throw new JeecgBootBizTipException("调用大模型接口失败:" + e.getMessage());
}
/**
@@ -709,91 +747,120 @@ public class AiragChatServiceImpl implements IAiragChatService {
AtomicBoolean isThinking = new AtomicBoolean(false);
// ai聊天响应逻辑
chatStream.onNext((String resMessage) -> {
- // 兼容推理模型
- if ("".equals(resMessage)) {
- isThinking.set(true);
- resMessage = "> ";
- }
- if ("".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, chatConversation.getId(), topicId);
- EventMessageData messageEventData = EventMessageData.builder()
- .message(resMessage)
- .build();
- eventData.setData(messageEventData);
- // sse
- SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
- if (null == emitter) {
- log.warn("[AI应用]接收LLM返回会话已关闭");
- return;
- }
- try {
- String eventStr = JSONObject.toJSONString(eventData);
- log.debug("[AI应用]接收LLM返回消息:{}", eventStr);
- emitter.send(SseEmitter.event().data(eventStr));
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- })
- .onComplete((responseMessage) -> {
- // 记录ai的回复
- AiMessage aiMessage = responseMessage.content();
- FinishReason finishReason = responseMessage.finishReason();
- String respText = aiMessage.text();
- // sse
- SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
- if (null == emitter) {
- log.warn("[AI应用]接收LLM返回会话已关闭");
- return;
- }
- if (FinishReason.STOP.equals(finishReason) || null == finishReason) {
- // 正常结束
- EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END, chatConversation.getId(), topicId);
- try {
- log.debug("[AI应用]接收LLM返回消息完成:{}", respText);
- emitter.send(SseEmitter.event().data(eventData));
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- appendMessage(messages, aiMessage, chatConversation, topicId);
- // 保存会话
- saveChatConversation(chatConversation,false,httpRequest);
- closeSSE(emitter, eventData);
- } else if (FinishReason.TOOL_EXECUTION.equals(finishReason)) {
- // 需要执行工具
- // TODO author: chenrui for: date:2025/3/7
- } else {
- // 异常结束
- log.error("调用模型异常:" + respText);
- if (respText.contains("insufficient Balance")) {
- respText = "大预言模型账号余额不足!";
- }
- EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
- eventData.setData(EventFlowData.builder().success(false).message(respText).build());
- closeSSE(emitter, eventData);
- }
- })
- .onError((Throwable error) -> {
- // sse
- SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
- if (null == emitter) {
- log.warn("[AI应用]接收LLM返回会话已关闭");
- return;
- }
- String errMsg = "调用大模型接口失败:" + error.getMessage();
- log.error(errMsg, error);
- EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
- eventData.setData(EventFlowData.builder().success(false).message(errMsg).build());
- closeSSE(emitter, eventData);
- })
- .start();
+ // 兼容推理模型
+ if ("".equals(resMessage)) {
+ isThinking.set(true);
+ resMessage = "> ";
+ }
+ if ("".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, chatConversation.getId(), topicId);
+ EventMessageData messageEventData = EventMessageData.builder().message(resMessage).build();
+ eventData.setData(messageEventData);
+ eventData.setRequestId(requestId);
+ // sse
+ SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
+ if (null == emitter) {
+ log.warn("[AI应用]接收LLM返回会话已关闭");
+ return;
+ }
+ sendMessage2Client(emitter, eventData);
+ }).onComplete((responseMessage) -> {
+ // 打印流程耗时日志
+ printChatDuration(requestId, "LLM输出消息完成");
+ AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId);
+ // 记录ai的回复
+ AiMessage aiMessage = responseMessage.content();
+ FinishReason finishReason = responseMessage.finishReason();
+ String respText = aiMessage.text();
+ // sse
+ SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
+ if (null == emitter) {
+ log.warn("[AI应用]接收LLM返回会话已关闭");
+ return;
+ }
+ if (FinishReason.STOP.equals(finishReason) || null == finishReason) {
+ // 正常结束
+ EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END, chatConversation.getId(), topicId);
+ appendMessage(messages, aiMessage, chatConversation, topicId);
+ // 保存会话
+ saveChatConversation(chatConversation, false, httpRequest);
+ closeSSE(emitter, eventData);
+ } else if (FinishReason.TOOL_EXECUTION.equals(finishReason)) {
+ // 需要执行工具
+ // TODO author: chenrui for: date:2025/3/7
+ } else if (FinishReason.LENGTH.equals(finishReason)) {
+ // 上下文长度超过限制
+ log.error("调用模型异常:上下文长度超过限制:{}", responseMessage.tokenUsage());
+ EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE, chatConversation.getId(), topicId);
+ eventData.setData(EventMessageData.builder().message("\n上下文长度超过限制,请调整模型最大Tokens").build());
+ sendMessage2Client(emitter, eventData);
+ eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END, chatConversation.getId(), topicId);
+ closeSSE(emitter, eventData);
+ } else {
+ // 异常结束
+ log.error("调用模型异常:" + respText);
+ if (respText.contains("insufficient Balance")) {
+ respText = "大预言模型账号余额不足!";
+ }
+ EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
+ eventData.setData(EventFlowData.builder().success(false).message(respText).build());
+ closeSSE(emitter, eventData);
+ }
+ }).onError((Throwable error) -> {
+ // 打印流程耗时日志
+ printChatDuration(requestId, "LLM输出消息异常");
+ AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId);
+ // sse
+ SseEmitter emitter = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE, requestId);
+ if (null == emitter) {
+ log.warn("[AI应用]接收LLM返回会话已关闭{}", requestId);
+ return;
+ }
+ log.error(error.getMessage(), error);
+ String errMsg = error.getMessage();
+ if (errMsg != null && errMsg.contains("timeout")) {
+ //update-begin---author:chenrui ---date:20250425 for:[QQYUN-12203]AI 聊天,超时或者服务器报错,给个友好提示------------
+ errMsg = "当前用户较多,排队中,请稍后再试!";
+ EventData eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE, chatConversation.getId(), topicId);
+ eventData.setData(EventMessageData.builder().message("\n" + errMsg).build());
+ sendMessage2Client(emitter, eventData);
+ eventData = new EventData(requestId, null, EventData.EVENT_MESSAGE_END, chatConversation.getId(), topicId);
+ closeSSE(emitter, eventData);
+ //update-end---author:chenrui ---date:20250425 for:[QQYUN-12203]AI 聊天,超时或者服务器报错,给个友好提示------------
+ } else {
+ errMsg = "调用大模型接口失败:" + errMsg;
+ EventData eventData = new EventData(requestId, null, EventData.EVENT_FLOW_ERROR, chatConversation.getId(), topicId);
+ eventData.setData(EventFlowData.builder().success(false).message(errMsg).build());
+ closeSSE(emitter, eventData);
+ }
+ }).start();
+ }
+
+ /**
+ * 发送消息到客户端
+ *
+ * @param emitter
+ * @param eventData
+ * @author chenrui
+ * @date 2025/4/22 19:58
+ */
+ private static void sendMessage2Client(SseEmitter emitter, EventData eventData) {
+ try {
+ log.info("发送消息:{}", eventData.getRequestId());
+ String eventStr = JSONObject.toJSONString(eventData);
+ log.debug("[AI应用]接收LLM返回消息:{}", eventStr);
+ emitter.send(SseEmitter.event().data(eventStr));
+ } catch (IOException e) {
+ log.error("发送消息失败", e);
+ }
}
/**
@@ -837,12 +904,7 @@ public class AiragChatServiceImpl implements IAiragChatService {
}
CompletableFuture.runAsync(() -> {
List messages = new LinkedList<>();
- String systemMsgStr = "根据用户的问题,总结会话标题.\n" +
- "要求如下:\n" +
- "1. 使用中文回答.\n" +
- "2. 标题长度控制在5个汉字10个英文字符以内\n" +
- "3. 直接回复会话标题,不要有其他任何无关描述\n" +
- "4. 如果无法总结,回复不知道\n";
+ String systemMsgStr = "根据用户的问题,总结会话标题.\n" + "要求如下:\n" + "1. 使用中文回答.\n" + "2. 标题长度控制在5个汉字10个英文字符以内\n" + "3. 直接回复会话标题,不要有其他任何无关描述\n" + "4. 如果无法总结,回复不知道\n";
messages.add(new SystemMessage(systemMsgStr));
messages.add(new UserMessage(question));
String summaryTitle;
@@ -876,6 +938,7 @@ public class AiragChatServiceImpl implements IAiragChatService {
/**
* 获取用户名
+ *
* @param httpRequest
* @return
* @author chenrui
@@ -885,9 +948,9 @@ public class AiragChatServiceImpl implements IAiragChatService {
try {
TokenUtils.getTokenByRequest();
String token;
- if(null != httpRequest){
+ if (null != httpRequest) {
token = TokenUtils.getTokenByRequest(httpRequest);
- }else{
+ } else {
token = TokenUtils.getTokenByRequest();
}
if (TokenUtils.verifyToken(token, sysBaseApi, redisUtil)) {
@@ -898,4 +961,19 @@ public class AiragChatServiceImpl implements IAiragChatService {
}
return null;
}
+
+
+ /**
+ * 打印耗时
+ * @param requestId
+ * @param message
+ * @author chenrui
+ * @date 2025/4/28 15:15
+ */
+ private static void printChatDuration(String requestId,String message) {
+ Long beginTime = AiragLocalCache.get(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId);
+ if (null != beginTime) {
+ log.info("[AI-CHAT]{},requestId:{},耗时:{}s", message, requestId, (System.currentTimeMillis() - beginTime) / 1000);
+ }
+ }
}
\ No newline at end of file
diff --git a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/demo/JimuDataReader.java b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/demo/JimuDataReader.java
new file mode 100644
index 00000000..12355f82
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/demo/JimuDataReader.java
@@ -0,0 +1,83 @@
+package org.jeecg.modules.airag.demo;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.exception.JeecgBootBizTipException;
+import org.jeecg.modules.airag.flow.component.enhance.IAiRagEnhanceJava;
+import org.jeecgframework.poi.excel.ExcelImportUtil;
+import org.jeecgframework.poi.excel.entity.ImportParams;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Java增强Demo: Excel数据读取器
+ * for [QQYUN-11718]【AI】积木报表对接AI流程编排接口展示报表
+ * @Author: chenrui
+ * @Date: 2025/4/29 16:51
+ */
+@Component("jimuDataReader")
+@Slf4j
+public class JimuDataReader implements IAiRagEnhanceJava {
+
+
+ @Override
+ public Map process(Map inputParams) {
+ // inputParams: {"bizData":"/xxxx/xxxx/xxxx/xxxx.xls"}
+ try {
+ String filePath = (String) inputParams.get("bizData");
+ if (filePath == null || filePath.isEmpty()) {
+ throw new IllegalArgumentException("File path is empty");
+ }
+
+ File excelFile = new File(filePath);
+ if (!excelFile.exists() || !excelFile.isFile()) {
+ throw new IllegalArgumentException("File not found: " + filePath);
+ }
+
+ // Since we don't know the target entity class, we'll read the Excel generically
+ return readExcelData(excelFile);
+ } catch (Exception e) {
+ log.error("Error processing Excel file", e);
+ throw new JeecgBootBizTipException("调用java增强失败", e);
+ }
+ }
+
+ /**
+ * Excel导入工具方法,基于ExcelImportUtil
+ *
+ * @param file Excel文件
+ * @return Excel读取结果,包含字段和数据
+ * @throws Exception 导入过程中的异常
+ */
+ public static Map readExcelData(File file) throws Exception {
+ Map result = new HashMap<>();
+
+ // 设置导入参数
+ ImportParams params = new ImportParams();
+ params.setTitleRows(0); // 没有标题
+ params.setHeadRows(1); // 第一行是表头
+
+ // 读取Excel数据
+ List