feat:初始化 spring boot 4.x 升级

pull/256/head
YunaiV 2026-06-27 10:43:13 -07:00
parent edb292f622
commit 62d8fa4cfd
166 changed files with 1393 additions and 1152 deletions

View File

@ -38,15 +38,15 @@
<properties> <properties>
<revision>2026.05-SNAPSHOT</revision> <revision>2026.05-SNAPSHOT</revision>
<!-- Maven 相关 --> <!-- Maven 相关 -->
<java.version>17</java.version> <java.version>25</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target> <maven.compiler.target>${java.version}</maven.compiler.target>
<maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version> <maven-surefire-plugin.version>3.5.5</maven-surefire-plugin.version>
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version> <maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
<flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version> <flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version>
<!-- maven-surefire-plugin 暂时无法通过 bom 的依赖读取(兼容老版本 IDEA 2024 及以前版本) --> <!-- maven-surefire-plugin 暂时无法通过 bom 的依赖读取(兼容老版本 IDEA 2024 及以前版本) -->
<lombok.version>1.18.46</lombok.version> <lombok.version>1.18.46</lombok.version>
<spring.boot.version>3.5.15</spring.boot.version> <spring.boot.version>4.1.0</spring.boot.version>
<mapstruct.version>1.6.3</mapstruct.version> <mapstruct.version>1.6.3</mapstruct.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>

View File

@ -17,11 +17,11 @@
<revision>2026.05-SNAPSHOT</revision> <revision>2026.05-SNAPSHOT</revision>
<flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version> <flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version>
<!-- 统一依赖管理 --> <!-- 统一依赖管理 -->
<spring.boot.version>3.5.15</spring.boot.version> <spring.boot.version>4.1.0</spring.boot.version>
<spring.cloud.version>2025.0.1</spring.cloud.version> <spring.cloud.version>2025.1.2</spring.cloud.version>
<spring.cloud.alibaba.version>2025.0.0.0</spring.cloud.alibaba.version> <spring.cloud.alibaba.version>2025.1.0.0</spring.cloud.alibaba.version>
<!-- Web 相关 --> <!-- Web 相关 -->
<springdoc.version>2.8.17</springdoc.version> <springdoc.version>3.0.3</springdoc.version>
<knife4j.version>4.5.0</knife4j.version> <knife4j.version>4.5.0</knife4j.version>
<!-- DB 相关 --> <!-- DB 相关 -->
<druid.version>1.2.28</druid.version> <druid.version>1.2.28</druid.version>
@ -45,7 +45,7 @@
<lock4j.version>2.2.7</lock4j.version> <lock4j.version>2.2.7</lock4j.version>
<!-- 监控相关 --> <!-- 监控相关 -->
<skywalking.version>9.6.0</skywalking.version> <skywalking.version>9.6.0</skywalking.version>
<spring-boot-admin.version>3.5.9</spring-boot-admin.version> <spring-boot-admin.version>4.1.1</spring-boot-admin.version>
<opentracing.version>0.33.0</opentracing.version> <opentracing.version>0.33.0</opentracing.version>
<!-- Test 测试相关 --> <!-- Test 测试相关 -->
<podam.version>8.0.2.RELEASE</podam.version> <podam.version>8.0.2.RELEASE</podam.version>
@ -88,7 +88,7 @@
<jimureport.version>2.3.4</jimureport.version> <jimureport.version>2.3.4</jimureport.version>
<jimubi.version>2.3.2</jimubi.version> <jimubi.version>2.3.2</jimubi.version>
<weixin-java.version>4.8.4-20260623.211820</weixin-java.version> <weixin-java.version>4.8.4-20260623.211820</weixin-java.version>
<bouncycastle.version>1.80</bouncycastle.version> <bouncycastle.version>1.84</bouncycastle.version>
<alipay-sdk-java.version>4.40.865.ALL</alipay-sdk-java.version> <alipay-sdk-java.version>4.40.865.ALL</alipay-sdk-java.version>
</properties> </properties>
@ -159,6 +159,11 @@
<artifactId>spring-boot-configuration-processor</artifactId> <artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version> <version>${spring.boot.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-jackson</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency> <dependency>
<groupId>cn.iocoder.cloud</groupId> <groupId>cn.iocoder.cloud</groupId>
@ -216,7 +221,7 @@
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId> <artifactId>druid-spring-boot-4-starter</artifactId>
<version>${druid.version}</version> <version>${druid.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@ -227,7 +232,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId> <artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<version>${mybatis-plus.version}</version> <version>${mybatis-plus.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@ -242,7 +247,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 --> <artifactId>dynamic-datasource-spring-boot4-starter</artifactId> <!-- 多数据源 -->
<version>${dynamic-datasource.version}</version> <version>${dynamic-datasource.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@ -287,18 +292,6 @@
<groupId>org.redisson</groupId> <groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId> <artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version> <version>${redisson.version}</version>
<exclusions>
<exclusion>
<groupId>org.redisson</groupId>
<!-- Redisson 4.6.x 默认依赖 redisson-spring-data-41适配 Spring Data Redis 4.x排除后使用下方的 spring-data-35 适配 Spring Boot 3.5 -->
<artifactId>redisson-spring-data-41</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-35</artifactId>
<version>${redisson.version}</version>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -44,7 +44,6 @@
<artifactId>spring-boot-configuration-processor</artifactId> <artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- Web 相关 --> <!-- Web 相关 -->
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
@ -104,17 +103,17 @@
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-annotations</artifactId>
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 --> <scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>tools.jackson.core</groupId>
<artifactId>jackson-core</artifactId> <artifactId>jackson-core</artifactId>
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 --> <scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.datatype</groupId> <groupId>tools.jackson.core</groupId>
<artifactId>jackson-datatype-jsr310</artifactId> <artifactId>jackson-databind</artifactId>
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 --> <scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency> </dependency>

View File

@ -6,20 +6,18 @@ import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer; import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer; import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.Getter; import lombok.Getter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import tools.jackson.core.JacksonException;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
@ -38,14 +36,14 @@ public class JsonUtils {
private static ObjectMapper objectMapper = buildObjectMapper(); private static ObjectMapper objectMapper = buildObjectMapper();
private static ObjectMapper buildObjectMapper() { private static ObjectMapper buildObjectMapper() {
SimpleModule simpleModule = new JavaTimeModule() SimpleModule simpleModule = new SimpleModule()
// 解决 LocalDateTime 的序列化 // 解决 LocalDateTime 的序列化
.addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE) .addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE)
.addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE); .addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);
return JsonMapper.builder() return JsonMapper.builder()
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.defaultPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL)) .changeDefaultPropertyInclusion(value -> JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL))
.addModule(simpleModule) .addModule(simpleModule)
.build(); .build();
} }
@ -120,7 +118,7 @@ public class JsonUtils {
} }
try { try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type)); return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));
} catch (IOException e) { } catch (JacksonException e) {
log.error("json parse err,json:{}", text, e); log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -148,7 +146,7 @@ public class JsonUtils {
} }
try { try {
return objectMapper.readValue(bytes, clazz); return objectMapper.readValue(bytes, clazz);
} catch (IOException e) { } catch (JacksonException e) {
log.error("json parse err,json:{}", bytes, e); log.error("json parse err,json:{}", bytes, e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -251,7 +249,7 @@ public class JsonUtils {
public static JsonNode parseTree(byte[] text) { public static JsonNode parseTree(byte[] text) {
try { try {
return objectMapper.readTree(text); return objectMapper.readTree(text);
} catch (IOException e) { } catch (JacksonException e) {
log.error("json parse err,json:{}", text, e); log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -1,10 +1,9 @@
package cn.iocoder.yudao.framework.common.util.json.databind; package cn.iocoder.yudao.framework.common.util.json.databind;
import com.fasterxml.jackson.core.JsonGenerator; import tools.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.SerializerProvider; import tools.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.annotation.JacksonStdImpl;
import java.io.IOException;
/** /**
* Long * Long
@ -14,7 +13,7 @@ import java.io.IOException;
* @author * @author
*/ */
@JacksonStdImpl @JacksonStdImpl
public class NumberSerializer extends com.fasterxml.jackson.databind.ser.std.NumberSerializer { public class NumberSerializer extends tools.jackson.databind.ser.jdk.NumberSerializer {
private static final long MAX_SAFE_INTEGER = 9007199254740991L; private static final long MAX_SAFE_INTEGER = 9007199254740991L;
private static final long MIN_SAFE_INTEGER = -9007199254740991L; private static final long MIN_SAFE_INTEGER = -9007199254740991L;
@ -26,7 +25,7 @@ public class NumberSerializer extends com.fasterxml.jackson.databind.ser.std.Num
} }
@Override @Override
public void serialize(Number value, JsonGenerator gen, SerializerProvider serializers) throws IOException { public void serialize(Number value, JsonGenerator gen, SerializationContext serializers) throws JacksonException {
// 超出范围 序列化位字符串 // 超出范围 序列化位字符串
if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) { if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
super.serialize(value, gen, serializers); super.serialize(value, gen, serializers);

View File

@ -1,25 +1,24 @@
package cn.iocoder.yudao.framework.common.util.json.databind; package cn.iocoder.yudao.framework.common.util.json.databind;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.ValueDeserializer;
/** /**
* LocalDateTime * LocalDateTime
* *
* @author * @author
*/ */
public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> { public class TimestampLocalDateTimeDeserializer extends ValueDeserializer<LocalDateTime> {
public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer(); public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();
@Override @Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
// 将 Long 时间戳,转换为 LocalDateTime 对象 // 将 Long 时间戳,转换为 LocalDateTime 对象
return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault()); return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
} }

View File

@ -5,12 +5,12 @@ import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ser.std.StdScalarSerializer;
import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
@ -25,18 +25,22 @@ import java.util.concurrent.ConcurrentHashMap;
* @author * @author
*/ */
@Slf4j @Slf4j
public class TimestampLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> { public class TimestampLocalDateTimeSerializer extends StdScalarSerializer<LocalDateTime> {
public static final TimestampLocalDateTimeSerializer INSTANCE = new TimestampLocalDateTimeSerializer(); public static final TimestampLocalDateTimeSerializer INSTANCE = new TimestampLocalDateTimeSerializer();
private static final Map<Class<?>, Map<String, Field>> FIELD_CACHE = new ConcurrentHashMap<>(); private static final Map<Class<?>, Map<String, Field>> FIELD_CACHE = new ConcurrentHashMap<>();
public TimestampLocalDateTimeSerializer() {
super(LocalDateTime.class);
}
@Override @Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException { public void serialize(LocalDateTime value, JsonGenerator gen, SerializationContext serializers) throws JacksonException {
// 情况一:有 JsonFormat 自定义注解则使用它。https://github.com/YunaiV/ruoyi-vue-pro/pull/1019 // 情况一:有 JsonFormat 自定义注解则使用它。https://github.com/YunaiV/ruoyi-vue-pro/pull/1019
String fieldName = gen.getOutputContext().getCurrentName(); String fieldName = gen.streamWriteContext().currentName();
if (fieldName != null) { if (fieldName != null) {
Object currentValue = gen.getOutputContext().getCurrentValue(); Object currentValue = gen.currentValue();
if (currentValue != null) { if (currentValue != null) {
Class<?> clazz = currentValue.getClass(); Class<?> clazz = currentValue.getClass();
Map<String, Field> fieldMap = FIELD_CACHE.computeIfAbsent(clazz, this::buildFieldMap); Map<String, Field> fieldMap = FIELD_CACHE.computeIfAbsent(clazz, this::buildFieldMap);

View File

@ -26,10 +26,9 @@ public class ServletUtils {
* @param response * @param response
* @param object JSON * @param object JSON
*/ */
@SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE否则会乱码
public static void writeJSON(HttpServletResponse response, Object object) { public static void writeJSON(HttpServletResponse response, Object object) {
String content = JsonUtils.toJsonString(object); String content = JsonUtils.toJsonString(object);
JakartaServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE); JakartaServletUtil.write(response, content, MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
} }
/** /**

View File

@ -129,6 +129,7 @@ public class YudaoTenantAutoConfiguration {
* *
* @return URL * @return URL
*/ */
@SuppressWarnings("removal")
private Set<String> getTenantIgnoreUrls() { private Set<String> getTenantIgnoreUrls() {
Set<String> ignoreUrls = new HashSet<>(); Set<String> ignoreUrls = new HashSet<>();
// 获得接口对应的 HandlerMethod 集合 // 获得接口对应的 HandlerMethod 集合

View File

@ -2,8 +2,8 @@ package cn.iocoder.yudao.framework.tenant.core.mq.kafka;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
/** /**

View File

@ -1,2 +1,2 @@
org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.EnvironmentPostProcessor=\
cn.iocoder.yudao.framework.tenant.core.mq.kafka.TenantKafkaEnvironmentPostProcessor cn.iocoder.yudao.framework.tenant.core.mq.kafka.TenantKafkaEnvironmentPostProcessor

View File

@ -24,7 +24,7 @@
<!-- Spring 核心 --> <!-- Spring 核心 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId> <artifactId>spring-boot-starter-aspectj</artifactId>
</dependency> </dependency>
<!-- Web 相关 --> <!-- Web 相关 -->
@ -41,6 +41,11 @@
</dependency> </dependency>
<!-- 监控相关 --> <!-- 监控相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency> <dependency>
<groupId>io.opentracing</groupId> <groupId>io.opentracing</groupId>
<artifactId>opentracing-util</artifactId> <artifactId>opentracing-util</artifactId>

View File

@ -2,12 +2,11 @@ package cn.iocoder.yudao.framework.tracer.config;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.micrometer.metrics.autoconfigure.MeterRegistryCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/** /**
* Metrics * Metrics

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.framework.mq.rabbitmq.config; package cn.iocoder.yudao.framework.mq.rabbitmq.config;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.JacksonJsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -18,11 +18,11 @@ import org.springframework.context.annotation.Bean;
public class YudaoRabbitMQAutoConfiguration { public class YudaoRabbitMQAutoConfiguration {
/** /**
* Jackson2JsonMessageConverter Bean使 jackson * JacksonJsonMessageConverter Bean使 jackson
*/ */
@Bean @Bean
public MessageConverter createMessageConverter() { public MessageConverter createMessageConverter() {
return new Jackson2JsonMessageConverter(); return new JacksonJsonMessageConverter();
} }
} }

View File

@ -66,11 +66,11 @@
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId> <artifactId>druid-spring-boot-4-starter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId> <artifactId>mybatis-plus-spring-boot4-starter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
@ -78,13 +78,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 --> <artifactId>dynamic-datasource-spring-boot4-starter</artifactId> <!-- 多数据源 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.framework.datasource.config; package cn.iocoder.yudao.framework.datasource.config;
import cn.iocoder.yudao.framework.datasource.core.filter.DruidAdRemoveFilter; import cn.iocoder.yudao.framework.datasource.core.filter.DruidAdRemoveFilter;
import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties; import com.alibaba.druid.spring.boot4.autoconfigure.properties.DruidStatProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;

View File

@ -6,8 +6,8 @@ import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;

View File

@ -9,19 +9,19 @@ import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler; import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.baomidou.mybatisplus.extension.handlers.Jackson3TypeHandler;
import com.baomidou.mybatisplus.extension.incrementer.*; import com.baomidou.mybatisplus.extension.incrementer.*;
import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal; import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal;
import com.baomidou.mybatisplus.extension.parser.cache.JdkSerialCaffeineJsqlParseCache; import com.baomidou.mybatisplus.extension.parser.cache.JdkSerialCaffeineJsqlParseCache;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import tools.jackson.databind.ObjectMapper;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -81,15 +81,15 @@ public class YudaoMybatisAutoConfiguration {
throw new IllegalArgumentException(StrUtil.format("DbType{} 找不到合适的 IKeyGenerator 实现类", dbType)); throw new IllegalArgumentException(StrUtil.format("DbType{} 找不到合适的 IKeyGenerator 实现类", dbType));
} }
@Bean // 特殊:返回结果使用 Object 而不用 JacksonTypeHandler 的原因,避免因为 JacksonTypeHandler 被 mybatis 全局使用! @Bean // 特殊:返回结果使用 Object 而不用 Jackson3TypeHandler 的原因,避免因为 Jackson3TypeHandler 被 mybatis 全局使用!
public Object jacksonTypeHandler(List<ObjectMapper> objectMappers) { public Object jacksonTypeHandler(List<ObjectMapper> objectMappers) {
// 特殊:设置 JacksonTypeHandler 的 ObjectMapper // 特殊:设置 Jackson3TypeHandler 的 ObjectMapper
ObjectMapper objectMapper = CollUtil.getFirst(objectMappers); ObjectMapper objectMapper = CollUtil.getFirst(objectMappers);
if (objectMapper == null) { if (objectMapper == null) {
objectMapper = JsonUtils.getObjectMapper(); objectMapper = JsonUtils.getObjectMapper();
} }
JacksonTypeHandler.setObjectMapper(objectMapper); Jackson3TypeHandler.setObjectMapper(objectMapper);
return new JacksonTypeHandler(Object.class); return new Jackson3TypeHandler(Object.class);
} }
} }

View File

@ -1,2 +1,2 @@
org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.EnvironmentPostProcessor=\
cn.iocoder.yudao.framework.mybatis.config.IdTypeEnvironmentPostProcessor cn.iocoder.yudao.framework.mybatis.config.IdTypeEnvironmentPostProcessor

View File

@ -26,10 +26,6 @@
<groupId>org.redisson</groupId> <groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId> <artifactId>redisson-spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-35</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -37,8 +33,8 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.datatype</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>jackson-datatype-jsr310</artifactId> <artifactId>spring-boot-jackson</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.redis.config;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager; import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.cache.CacheProperties; import org.springframework.boot.cache.autoconfigure.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;

View File

@ -1,9 +1,6 @@
package cn.iocoder.yudao.framework.redis.config; package cn.iocoder.yudao.framework.redis.config;
import cn.hutool.core.util.ReflectUtil; import org.redisson.spring.starter.RedissonAutoConfigurationV4;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.redisson.spring.starter.RedissonAutoConfigurationV2;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
@ -13,7 +10,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
/** /**
* Redis * Redis
*/ */
@AutoConfiguration(before = RedissonAutoConfigurationV2.class) // 目的:使用自己定义的 RedisTemplate Bean @AutoConfiguration(before = RedissonAutoConfigurationV4.class) // 目的:使用自己定义的 RedisTemplate Bean
public class YudaoRedisAutoConfiguration { public class YudaoRedisAutoConfiguration {
/** /**
@ -35,11 +32,11 @@ public class YudaoRedisAutoConfiguration {
return template; return template;
} }
@SuppressWarnings("UnnecessaryLocalVariable")
public static RedisSerializer<?> buildRedisSerializer() { public static RedisSerializer<?> buildRedisSerializer() {
RedisSerializer<Object> json = RedisSerializer.json(); RedisSerializer<Object> json = RedisSerializer.json();
// 解决 LocalDateTime 的序列化 // 特殊spring boot 4.x 无需解决 LocalDateTime 的序列化
ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper"); // 原因Spring Data Redis 4 使用 Jackson 3RedisSerializer.json() 已支持 Java Time 类型
objectMapper.registerModules(new JavaTimeModule());
return json; return json;
} }

View File

@ -27,7 +27,7 @@
<!-- Spring 核心 --> <!-- Spring 核心 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId> <artifactId>spring-boot-starter-aspectj</artifactId>
</dependency> </dependency>
<!-- Web 相关 --> <!-- Web 相关 -->

View File

@ -156,6 +156,7 @@ public class YudaoWebSecurityConfigurerAdapter {
return webProperties.getAppApi().getPrefix() + url; return webProperties.getAppApi().getPrefix() + url;
} }
@SuppressWarnings("removal")
private Multimap<HttpMethod, String> getPermitAllUrlsFromAnnotations() { private Multimap<HttpMethod, String> getPermitAllUrlsFromAnnotations() {
Multimap<HttpMethod, String> result = HashMultimap.create(); Multimap<HttpMethod, String> result = HashMultimap.create();
// 获得接口对应的 HandlerMethod 集合 // 获得接口对应的 HandlerMethod 集合
@ -166,7 +167,7 @@ public class YudaoWebSecurityConfigurerAdapter {
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) { for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = entry.getValue(); HandlerMethod handlerMethod = entry.getValue();
if (!handlerMethod.hasMethodAnnotation(PermitAll.class) // 方法级 if (!handlerMethod.hasMethodAnnotation(PermitAll.class) // 方法级
&& !handlerMethod.getBeanType().isAnnotationPresent(PermitAll.class)) { // 接口级 && !handlerMethod.getBeanType().isAnnotationPresent(PermitAll.class)) { // 接口级
continue; continue;
} }
Set<String> urls = new HashSet<>(); Set<String> urls = new HashSet<>();

View File

@ -1,8 +1,8 @@
package cn.iocoder.yudao.framework.test.config; package cn.iocoder.yudao.framework.test.config;
import com.github.fppt.jedismock.RedisServer; import com.github.fppt.jedismock.RedisServer;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties;
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.Lazy; import org.springframework.context.annotation.Lazy;
@ -16,14 +16,14 @@ import java.io.IOException;
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@Lazy(false) // 禁止延迟加载 @Lazy(false) // 禁止延迟加载
@EnableConfigurationProperties(RedisProperties.class) @EnableConfigurationProperties(DataRedisProperties.class)
public class RedisTestConfiguration { public class RedisTestConfiguration {
/** /**
* Redis Server * Redis Server
*/ */
@Bean @Bean
public RedisServer redisServer(RedisProperties properties) throws IOException { public RedisServer redisServer(DataRedisProperties properties) throws IOException {
RedisServer redisServer = new RedisServer(properties.getPort()); RedisServer redisServer = new RedisServer(properties.getPort());
// 一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样就导致端口被占用无法启动。。。 // 一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样就导致端口被占用无法启动。。。
try { try {

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.test.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties; import org.springframework.boot.sql.autoconfigure.init.SqlInitializationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
@ -17,7 +17,7 @@ import javax.sql.DataSource;
/** /**
* SQL Configuration * SQL Configuration
* *
* 使 org.springframework.boot.autoconfigure.sql.init.DataSourceInitializationConfiguration * 使 org.springframework.boot.sql.autoconfigure.init.DataSourceInitializationConfiguration
* 使 spring.main.lazy-initialization true DataSourceInitializationConfiguration * 使 spring.main.lazy-initialization true DataSourceInitializationConfiguration
* DataSourceInitializationConfiguration * DataSourceInitializationConfiguration
* *

View File

@ -6,12 +6,12 @@ import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration; import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration; import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure; import com.alibaba.druid.spring.boot4.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import org.redisson.spring.starter.RedissonAutoConfigurationV2; import org.redisson.spring.starter.RedissonAutoConfigurationV4;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
@ -43,8 +43,8 @@ public class BaseDbAndRedisUnitTest {
// Redis 配置类 // Redis 配置类
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类 YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
RedisAutoConfiguration.class, // Spring Redis 自动配置类 DataRedisAutoConfiguration.class, // Spring Redis 自动配置类
RedissonAutoConfigurationV2.class, // Redisson 自动配置类 RedissonAutoConfigurationV4.class, // Redisson 自动配置类
// 其它配置类 // 其它配置类
SpringUtil.class SpringUtil.class

View File

@ -4,11 +4,11 @@ import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration; import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration; import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration; import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure; import com.alibaba.druid.spring.boot4.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.github.yulichang.autoconfigure.MybatisPlusJoinAutoConfiguration; import com.github.yulichang.autoconfigure.MybatisPlusJoinAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;

View File

@ -3,8 +3,8 @@ package cn.iocoder.yudao.framework.test.core.ut;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration; import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
import org.redisson.spring.starter.RedissonAutoConfigurationV2; import org.redisson.spring.starter.RedissonAutoConfigurationV4;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
@ -23,9 +23,9 @@ public class BaseRedisUnitTest {
@Import({ @Import({
// Redis 配置类 // Redis 配置类
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
RedisAutoConfiguration.class, // Spring Redis 自动配置类 DataRedisAutoConfiguration.class, // Spring Redis 自动配置类
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类 YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
RedissonAutoConfigurationV2.class, // Redisson 自动配置类 RedissonAutoConfigurationV4.class, // Redisson 自动配置类
// 其它配置类 // 其它配置类
SpringUtil.class SpringUtil.class

View File

@ -33,6 +33,14 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-restclient</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@ -19,7 +19,6 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.config.WebProperties; import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter; import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
@ -35,6 +34,7 @@ import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import tools.jackson.databind.JsonNode;
import static cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor.ATTRIBUTE_HANDLER_METHOD; import static cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor.ATTRIBUTE_HANDLER_METHOD;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.desensitize.core.base.annotation;
import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler; import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
import cn.iocoder.yudao.framework.desensitize.core.base.serializer.StringDesensitizeSerializer; import cn.iocoder.yudao.framework.desensitize.core.base.serializer.StringDesensitizeSerializer;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import tools.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.*; import java.lang.annotation.*;

View File

@ -7,16 +7,15 @@ import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy; import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler; import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.BeanProperty;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -28,7 +27,7 @@ import java.lang.reflect.Field;
* @author gaibu * @author gaibu
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer { public class StringDesensitizeSerializer extends StdSerializer<String> {
@Getter @Getter
@Setter @Setter
@ -39,7 +38,7 @@ public class StringDesensitizeSerializer extends StdSerializer<String> implement
} }
@Override @Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) { public ValueSerializer<?> createContextual(SerializationContext serializerProvider, BeanProperty beanProperty) {
DesensitizeBy annotation = beanProperty.getAnnotation(DesensitizeBy.class); DesensitizeBy annotation = beanProperty.getAnnotation(DesensitizeBy.class);
if (annotation == null) { if (annotation == null) {
return this; return this;
@ -52,7 +51,7 @@ public class StringDesensitizeSerializer extends StdSerializer<String> implement
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void serialize(String value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException { public void serialize(String value, JsonGenerator gen, SerializationContext serializerProvider) throws JacksonException {
if (StrUtil.isBlank(value)) { if (StrUtil.isBlank(value)) {
gen.writeNull(); gen.writeNull();
return; return;
@ -83,7 +82,7 @@ public class StringDesensitizeSerializer extends StdSerializer<String> implement
* @return * @return
*/ */
private Field getField(JsonGenerator generator) { private Field getField(JsonGenerator generator) {
String currentName = generator.getOutputContext().getCurrentName(); String currentName = generator.streamWriteContext().currentName();
Object currentValue = generator.currentValue(); Object currentValue = generator.currentValue();
Class<?> currentValueClass = currentValue.getClass(); Class<?> currentValueClass = currentValue.getClass();
return ReflectUtil.getField(currentValueClass, currentName); return ReflectUtil.getField(currentValueClass, currentName);

View File

@ -4,18 +4,18 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.json.databind.NumberSerializer; import cn.iocoder.yudao.framework.common.util.json.databind.NumberSerializer;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer; import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer; import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ext.javatime.deser.LocalDateDeserializer;
import tools.jackson.databind.ext.javatime.deser.LocalTimeDeserializer;
import tools.jackson.databind.ext.javatime.ser.LocalDateSerializer;
import tools.jackson.databind.ext.javatime.ser.LocalTimeSerializer;
import tools.jackson.databind.module.SimpleModule;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -29,26 +29,15 @@ public class YudaoJacksonAutoConfiguration {
* Builder 使 *ByType handledType * Builder 使 *ByType handledType
*/ */
@Bean @Bean
public Jackson2ObjectMapperBuilderCustomizer ldtEpochMillisCustomizer() { public JsonMapperBuilderCustomizer ldtEpochMillisCustomizer(JacksonModule timestampSupportModuleBean) {
return builder -> builder return builder -> builder.addModule(timestampSupportModuleBean);
// Long -> Number
.serializerByType(Long.class, NumberSerializer.INSTANCE)
.serializerByType(Long.TYPE, NumberSerializer.INSTANCE)
// LocalDate / LocalTime
.serializerByType(LocalDate.class, LocalDateSerializer.INSTANCE)
.deserializerByType(LocalDate.class, LocalDateDeserializer.INSTANCE)
.serializerByType(LocalTime.class, LocalTimeSerializer.INSTANCE)
.deserializerByType(LocalTime.class, LocalTimeDeserializer.INSTANCE)
// LocalDateTime < - > EpochMillis
.serializerByType(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE)
.deserializerByType(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);
} }
/** /**
* Bean ModuleBoot ObjectMapper * Bean ModuleBoot ObjectMapper
*/ */
@Bean @Bean
public Module timestampSupportModuleBean() { public JacksonModule timestampSupportModuleBean() {
SimpleModule m = new SimpleModule("TimestampSupportModule"); SimpleModule m = new SimpleModule("TimestampSupportModule");
// Long -> Number避免前端精度丢失 // Long -> Number避免前端精度丢失
m.addSerializer(Long.class, NumberSerializer.INSTANCE); m.addSerializer(Long.class, NumberSerializer.INSTANCE);

View File

@ -14,11 +14,11 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.restclient.autoconfigure.RestTemplateAutoConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.webmvc.autoconfigure.WebMvcRegistrations;
import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;

View File

@ -15,7 +15,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.common.util.concurrent.UncheckedExecutionException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolation;
@ -39,6 +38,7 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchExcep
import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException; import org.springframework.web.servlet.resource.NoResourceFoundException;
import tools.jackson.databind.exc.InvalidFormatException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;

View File

@ -5,12 +5,11 @@ import cn.iocoder.yudao.framework.xss.core.clean.JsoupXssCleaner;
import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner; import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import cn.iocoder.yudao.framework.xss.core.filter.XssFilter; import cn.iocoder.yudao.framework.xss.core.filter.XssFilter;
import cn.iocoder.yudao.framework.xss.core.json.XssStringJsonDeserializer; import cn.iocoder.yudao.framework.xss.core.json.XssStringJsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -38,17 +37,17 @@ public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
/** /**
* Jackson json xss * Jackson json xss
* *
* @return Jackson2ObjectMapperBuilderCustomizer * @return JsonMapperBuilderCustomizer
*/ */
@Bean @Bean
@ConditionalOnMissingBean(name = "xssJacksonCustomizer") @ConditionalOnMissingBean(name = "xssJacksonCustomizer")
@ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true") @ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssProperties properties, public JsonMapperBuilderCustomizer xssJacksonCustomizer(XssProperties properties,
PathMatcher pathMatcher, PathMatcher pathMatcher,
XssCleaner xssCleaner) { XssCleaner xssCleaner) {
// 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer在序列化时进行处理 // 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer在序列化时进行处理
return builder -> return builder -> builder.addModule(new tools.jackson.databind.module.SimpleModule("XssStringModule")
builder.deserializerByType(String.class, new XssStringJsonDeserializer(properties, pathMatcher, xssCleaner)); .addDeserializer(String.class, new XssStringJsonDeserializer(properties, pathMatcher, xssCleaner)));
} }
/** /**

View File

@ -3,16 +3,15 @@ package cn.iocoder.yudao.framework.xss.core.json;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.xss.config.XssProperties; import cn.iocoder.yudao.framework.xss.config.XssProperties;
import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner; import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import tools.jackson.core.JacksonException;
import java.io.IOException; import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.deser.jdk.StringDeserializer;
/** /**
* XSS jackson * XSS jackson
@ -36,19 +35,19 @@ public class XssStringJsonDeserializer extends StringDeserializer {
private final XssCleaner xssCleaner; private final XssCleaner xssCleaner;
@Override @Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { public String deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
// 1. 白名单 URL 的处理 // 1. 白名单 URL 的处理
HttpServletRequest request = ServletUtils.getRequest(); HttpServletRequest request = ServletUtils.getRequest();
if (request != null) { if (request != null) {
String uri = ServletUtils.getRequest().getRequestURI(); String uri = ServletUtils.getRequest().getRequestURI();
if (properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, uri))) { if (properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, uri))) {
return p.getText(); return p.getString();
} }
} }
// 2. 真正使用 xssCleaner 进行过滤 // 2. 真正使用 xssCleaner 进行过滤
if (p.hasToken(JsonToken.VALUE_STRING)) { if (p.hasToken(JsonToken.VALUE_STRING)) {
return xssCleaner.clean(p.getText()); return xssCleaner.clean(p.getString());
} }
JsonToken t = p.currentToken(); JsonToken t = p.currentToken();
// [databind#381] // [databind#381]

View File

@ -107,6 +107,7 @@ public class AccessLogFilter implements GlobalFilter, Ordered {
} }
@Override @Override
@SuppressWarnings("removal")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 将 Request 中可以直接获取到的参数,设置到网关日志 // 将 Request 中可以直接获取到的参数,设置到网关日志
ServerHttpRequest request = exchange.getRequest(); ServerHttpRequest request = exchange.getRequest();
@ -117,7 +118,7 @@ public class AccessLogFilter implements GlobalFilter, Ordered {
gatewayLog.setRequestMethod(request.getMethod().name()); gatewayLog.setRequestMethod(request.getMethod().name());
gatewayLog.setRequestUrl(request.getURI().getRawPath()); gatewayLog.setRequestUrl(request.getURI().getRawPath());
gatewayLog.setQueryParams(request.getQueryParams()); gatewayLog.setQueryParams(request.getQueryParams());
gatewayLog.setRequestHeaders(request.getHeaders()); gatewayLog.setRequestHeaders(request.getHeaders().asMultiValueMap());
gatewayLog.setStartTime(LocalDateTime.now()); gatewayLog.setStartTime(LocalDateTime.now());
gatewayLog.setUserIp(WebFrameworkUtils.getClientIP(exchange)); gatewayLog.setUserIp(WebFrameworkUtils.getClientIP(exchange));
@ -192,7 +193,7 @@ public class AccessLogFilter implements GlobalFilter, Ordered {
// 设置其它字段 // 设置其它字段
gatewayLog.setUserId(SecurityFrameworkUtils.getLoginUserId(exchange)); gatewayLog.setUserId(SecurityFrameworkUtils.getLoginUserId(exchange));
gatewayLog.setUserType(SecurityFrameworkUtils.getLoginUserType(exchange)); gatewayLog.setUserType(SecurityFrameworkUtils.getLoginUserType(exchange));
gatewayLog.setResponseHeaders(response.getHeaders()); gatewayLog.setResponseHeaders(response.getHeaders().asMultiValueMap());
gatewayLog.setHttpStatus((HttpStatus) response.getStatusCode()); gatewayLog.setHttpStatus((HttpStatus) response.getStatusCode());
// 获取响应类型,如果是 json 就打印 // 获取响应类型,如果是 json 就打印

View File

@ -9,7 +9,7 @@ import cn.iocoder.yudao.gateway.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.gateway.util.WebFrameworkUtils; import cn.iocoder.yudao.gateway.util.WebFrameworkUtils;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi; import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO; import cn.iocoder.yudao.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import com.fasterxml.jackson.core.type.TypeReference; import tools.jackson.core.type.TypeReference;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction; import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.gateway.handler;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.gateway.util.WebFrameworkUtils; import cn.iocoder.yudao.gateway.util.WebFrameworkUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; import org.springframework.boot.webflux.error.ErrorWebExceptionHandler;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponse;

View File

@ -4,20 +4,21 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.json.databind.NumberSerializer; import cn.iocoder.yudao.framework.common.util.json.databind.NumberSerializer;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer; import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer; import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration; import org.springframework.boot.http.codec.CodecCustomizer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.json.JacksonJsonDecoder;
import org.springframework.http.codec.json.JacksonJsonEncoder;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ext.javatime.deser.LocalDateDeserializer;
import tools.jackson.databind.ext.javatime.deser.LocalTimeDeserializer;
import tools.jackson.databind.ext.javatime.ser.LocalDateSerializer;
import tools.jackson.databind.ext.javatime.ser.LocalTimeSerializer;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.module.SimpleModule;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -31,26 +32,15 @@ public class GatewayJacksonAutoConfiguration {
* Builder 使 *ByType handledType * Builder 使 *ByType handledType
*/ */
@Bean @Bean
public Jackson2ObjectMapperBuilderCustomizer ldtEpochMillisCustomizer() { public JsonMapperBuilderCustomizer ldtEpochMillisCustomizer(JacksonModule timestampSupportModuleBean) {
return builder -> builder return builder -> builder.addModule(timestampSupportModuleBean);
// Long -> Number
.serializerByType(Long.class, NumberSerializer.INSTANCE)
.serializerByType(Long.TYPE, NumberSerializer.INSTANCE)
// LocalDate / LocalTime
.serializerByType(LocalDate.class, LocalDateSerializer.INSTANCE)
.deserializerByType(LocalDate.class, LocalDateDeserializer.INSTANCE)
.serializerByType(LocalTime.class, LocalTimeSerializer.INSTANCE)
.deserializerByType(LocalTime.class, LocalTimeDeserializer.INSTANCE)
// LocalDateTime < - > EpochMillis
.serializerByType(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE)
.deserializerByType(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);
} }
/** /**
* Bean ModuleBoot ObjectMapper * Bean ModuleBoot ObjectMapper
*/ */
@Bean @Bean
public Module timestampSupportModuleBean() { public JacksonModule timestampSupportModuleBean() {
SimpleModule m = new SimpleModule("TimestampSupportModule"); SimpleModule m = new SimpleModule("TimestampSupportModule");
// Long -> Number // Long -> Number
m.addSerializer(Long.class, NumberSerializer.INSTANCE); m.addSerializer(Long.class, NumberSerializer.INSTANCE);
@ -81,12 +71,12 @@ public class GatewayJacksonAutoConfiguration {
* WebFlux 使 ObjectMapper * WebFlux 使 ObjectMapper
*/ */
@Bean @Bean
public CodecCustomizer unifyJackson(ObjectMapper om) { public CodecCustomizer unifyJackson(JsonMapper om) {
return configurer -> { return configurer -> {
Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(om); JacksonJsonDecoder decoder = new JacksonJsonDecoder(om);
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(om); JacksonJsonEncoder encoder = new JacksonJsonEncoder(om);
configurer.defaultCodecs().jackson2JsonDecoder(decoder); configurer.defaultCodecs().jacksonJsonDecoder(decoder);
configurer.defaultCodecs().jackson2JsonEncoder(encoder); configurer.defaultCodecs().jacksonJsonEncoder(encoder);
}; };
} }
} }

View File

@ -69,7 +69,7 @@ public class SecurityFrameworkUtils {
*/ */
public static ServerWebExchange removeLoginUser(ServerWebExchange exchange) { public static ServerWebExchange removeLoginUser(ServerWebExchange exchange) {
// 如果不包含,直接返回 // 如果不包含,直接返回
if (!exchange.getRequest().getHeaders().containsKey(LOGIN_USER_HEADER)) { if (!exchange.getRequest().getHeaders().containsHeader(LOGIN_USER_HEADER)) {
return exchange; return exchange;
} }
// 如果包含,则移除。参考 RemoveRequestHeaderGatewayFilterFactory 实现 // 如果包含,则移除。参考 RemoveRequestHeaderGatewayFilterFactory 实现

View File

@ -54,11 +54,10 @@ public class WebFrameworkUtils {
* @param exchange * @param exchange
* @param object JSON * @param object JSON
*/ */
@SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE否则会乱码
public static Mono<Void> writeJSON(ServerWebExchange exchange, Object object) { public static Mono<Void> writeJSON(ServerWebExchange exchange, Object object) {
// 设置 header // 设置 header
ServerHttpResponse response = exchange.getResponse(); ServerHttpResponse response = exchange.getResponse();
response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8); response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
// 设置 body // 设置 body
return response.writeWith(Mono.fromSupplier(() -> { return response.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory(); DataBufferFactory bufferFactory = response.bufferFactory();

View File

@ -9,14 +9,6 @@ spring:
codecs: codecs:
max-in-memory-size: 10MB # 调整缓冲区大小https://gitee.com/zhijiantianya/yudao-cloud/pulls/176 max-in-memory-size: 10MB # 调整缓冲区大小https://gitee.com/zhijiantianya/yudao-cloud/pulls/176
# Jackson 配置项
jackson:
serialization:
write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
fail-on-empty-beans: false # 允许序列化无属性的 Bean
main: main:
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。 allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。

View File

@ -19,9 +19,9 @@
国外OpenAI、Ollama、Midjourney、StableDiffusion、Suno 国外OpenAI、Ollama、Midjourney、StableDiffusion、Suno
</description> </description>
<properties> <properties>
<spring-ai.version>1.1.5</spring-ai.version> <spring-ai.version>2.0.0</spring-ai.version>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba --> <!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba -->
<alibaba-ai.version>1.1.2.2</alibaba-ai.version> <alibaba-ai.version>2.0.0-M1.1</alibaba-ai.version>
<tinyflow.version>1.2.6</tinyflow.version> <tinyflow.version>1.2.6</tinyflow.version>
</properties> </properties>
@ -122,11 +122,6 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-azure-openai</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.ai</groupId> <groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-anthropic</artifactId> <artifactId>spring-ai-starter-model-anthropic</artifactId>
@ -147,18 +142,6 @@
<artifactId>spring-ai-starter-model-stability-ai</artifactId> <artifactId>spring-ai-starter-model-stability-ai</artifactId>
<version>${spring-ai.version}</version> <version>${spring-ai.version}</version>
</dependency> </dependency>
<dependency>
<!-- 智谱 GLM -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-minimax</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency> <dependency>
<!-- 通义千问 --> <!-- 通义千问 -->
<groupId>com.alibaba.cloud.ai</groupId> <groupId>com.alibaba.cloud.ai</groupId>
@ -166,19 +149,6 @@
<version>${alibaba-ai.version}</version> <version>${alibaba-ai.version}</version>
</dependency> </dependency>
<dependency>
<!-- 文心一言 -->
<groupId>org.springaicommunity</groupId>
<artifactId>qianfan-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<!-- 月之暗面 -->
<groupId>org.springaicommunity</groupId>
<artifactId>moonshot-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 向量存储https://db-engines.com/en/ranking/vector+dbms --> <!-- 向量存储https://db-engines.com/en/ranking/vector+dbms -->
<dependency> <dependency>
<!-- Qdranthttps://qdrant.tech/ --> <!-- Qdranthttps://qdrant.tech/ -->
@ -247,12 +217,22 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-mcp-server-common</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency> <dependency>
<!-- 客户端 --> <!-- 客户端 -->
<groupId>org.springframework.ai</groupId> <groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId> <artifactId>spring-ai-starter-mcp-client</artifactId>
<version>${spring-ai.version}</version> <version>${spring-ai.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-mcp-client-common</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- TinyFlowAI 工作流 --> <!-- TinyFlowAI 工作流 -->
<dependency> <dependency>
@ -315,4 +295,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@ -10,25 +10,38 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel
import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient; import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient;
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient; import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient;
import cn.iocoder.yudao.module.ai.tool.method.PersonService; import cn.iocoder.yudao.module.ai.tool.method.PersonService;
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import com.alibaba.cloud.ai.dashscope.embedding.text.DashScopeEmbeddingModel;
import com.alibaba.cloud.ai.dashscope.embedding.text.DashScopeEmbeddingOptions;
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationRegistry;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.deepseek.DeepSeekChatModel; import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.DeepSeekChatOptions; import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.ai.deepseek.api.DeepSeekApi; import org.springframework.ai.deepseek.api.DeepSeekApi;
import org.springframework.ai.document.MetadataMode;
import org.springframework.ai.embedding.BatchingStrategy; import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.TokenCountBatchingStrategy; import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.support.ToolCallbacks; import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
import org.springframework.ai.tokenizer.TokenCountEstimator; import org.springframework.ai.tokenizer.TokenCountEstimator;
@ -40,11 +53,11 @@ import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStorePr
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.beans.factory.annotation.Value;
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 java.util.List; import java.util.List;
import java.util.Optional;
/** /**
* AI * AI
@ -74,6 +87,73 @@ public class AiAutoConfiguration {
// ========== 各种 AI Client 创建 ========== // ========== 各种 AI Client 创建 ==========
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.ai.dashscope.api-key")
public DashScopeChatModel dashScopeChatModel(@Value("${spring.ai.dashscope.api-key}") String apiKey,
ToolCallingManager toolCallingManager,
ObservationRegistry observationRegistry) {
return buildTongYiChatModel(apiKey, toolCallingManager, observationRegistry);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.ai.dashscope.api-key")
public DashScopeImageModel dashScopeImageModel(@Value("${spring.ai.dashscope.api-key}") String apiKey,
ObservationRegistry observationRegistry) {
return buildTongYiImagesModel(apiKey, observationRegistry);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.ai.dashscope.api-key")
public DashScopeEmbeddingModel dashScopeEmbeddingModel(@Value("${spring.ai.dashscope.api-key}") String apiKey,
ObservationRegistry observationRegistry) {
return buildTongYiEmbeddingModel(apiKey, null, observationRegistry);
}
public static DashScopeChatModel buildTongYiChatModel(String apiKey) {
return buildTongYiChatModel(apiKey, getToolCallingManager(), getObservationRegistry());
}
private static DashScopeChatModel buildTongYiChatModel(String apiKey, ToolCallingManager toolCallingManager,
ObservationRegistry observationRegistry) {
DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build();
DashScopeChatOptions options = DashScopeChatOptions.builder()
.model(DashScopeApi.DEFAULT_CHAT_MODEL)
.temperature(0.7)
.build();
return new DashScopeChatModel(dashScopeApi, options, toolCallingManager, RetryUtils.DEFAULT_RETRY_TEMPLATE,
observationRegistry);
}
public static DashScopeImageModel buildTongYiImagesModel(String apiKey) {
return buildTongYiImagesModel(apiKey, getObservationRegistry());
}
private static DashScopeImageModel buildTongYiImagesModel(String apiKey, ObservationRegistry observationRegistry) {
DashScopeImageApi dashScopeImageApi = DashScopeImageApi.builder().apiKey(apiKey).build();
DashScopeImageOptions options = DashScopeImageOptions.builder()
.model(DashScopeImageApi.DEFAULT_IMAGE_MODEL)
.build();
return new DashScopeImageModel(dashScopeImageApi, options, RetryUtils.DEFAULT_RETRY_TEMPLATE,
observationRegistry);
}
public static DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) {
return buildTongYiEmbeddingModel(apiKey, model, getObservationRegistry());
}
private static DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model,
ObservationRegistry observationRegistry) {
DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build();
DashScopeEmbeddingOptions options = DashScopeEmbeddingOptions.builder()
.model(StrUtil.blankToDefault(model, DashScopeApi.DEFAULT_EMBEDDING_MODEL))
.build();
return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, options, RetryUtils.DEFAULT_RETRY_TEMPLATE,
observationRegistry);
}
@Bean @Bean
@ConditionalOnProperty(value = "yudao.ai.gemini.enable", havingValue = "true") @ConditionalOnProperty(value = "yudao.ai.gemini.enable", havingValue = "true")
public GeminiChatModel geminiChatModel(YudaoAiProperties yudaoAiProperties) { public GeminiChatModel geminiChatModel(YudaoAiProperties yudaoAiProperties) {
@ -86,18 +166,14 @@ public class AiAutoConfiguration {
properties.setModel(GeminiChatModel.MODEL_DEFAULT); properties.setModel(GeminiChatModel.MODEL_DEFAULT);
} }
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder() .options(OpenAiChatOptions.builder()
.baseUrl(GeminiChatModel.BASE_URL) .baseUrl(GeminiChatModel.BASE_URL)
.completionsPath(GeminiChatModel.COMPLETE_PATH)
.apiKey(properties.getApiKey()) .apiKey(properties.getApiKey())
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(properties.getModel()) .model(properties.getModel())
.temperature(properties.getTemperature()) .temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens()) .maxTokens(properties.getMaxTokens())
.topP(properties.getTopP()) .topP(properties.getTopP())
.build()) .build())
.toolCallingManager(getToolCallingManager())
.build(); .build();
return new GeminiChatModel(openAiChatModel); return new GeminiChatModel(openAiChatModel);
} }
@ -114,18 +190,14 @@ public class AiAutoConfiguration {
properties.setModel(DouBaoChatModel.MODEL_DEFAULT); properties.setModel(DouBaoChatModel.MODEL_DEFAULT);
} }
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder() .options(OpenAiChatOptions.builder()
.baseUrl(DouBaoChatModel.BASE_URL) .baseUrl(DouBaoChatModel.BASE_URL)
.completionsPath(DouBaoChatModel.COMPLETE_PATH)
.apiKey(properties.getApiKey()) .apiKey(properties.getApiKey())
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(properties.getModel()) .model(properties.getModel())
.temperature(properties.getTemperature()) .temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens()) .maxTokens(properties.getMaxTokens())
.topP(properties.getTopP()) .topP(properties.getTopP())
.build()) .build())
.toolCallingManager(getToolCallingManager())
.build(); .build();
return new DouBaoChatModel(openAiChatModel); return new DouBaoChatModel(openAiChatModel);
} }
@ -146,13 +218,12 @@ public class AiAutoConfiguration {
.baseUrl(SiliconFlowApiConstants.DEFAULT_BASE_URL) .baseUrl(SiliconFlowApiConstants.DEFAULT_BASE_URL)
.apiKey(properties.getApiKey()) .apiKey(properties.getApiKey())
.build()) .build())
.defaultOptions(DeepSeekChatOptions.builder() .options(DeepSeekChatOptions.builder()
.model(properties.getModel()) .model(properties.getModel())
.temperature(properties.getTemperature()) .temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens()) .maxTokens(properties.getMaxTokens())
.topP(properties.getTopP()) .topP(properties.getTopP())
.build()) .build())
.toolCallingManager(getToolCallingManager())
.build(); .build();
return new SiliconFlowChatModel(openAiChatModel); return new SiliconFlowChatModel(openAiChatModel);
} }
@ -181,13 +252,12 @@ public class AiAutoConfiguration {
.completionsPath(HunYuanChatModel.COMPLETE_PATH) .completionsPath(HunYuanChatModel.COMPLETE_PATH)
.apiKey(properties.getApiKey()) .apiKey(properties.getApiKey())
.build()) .build())
.defaultOptions(DeepSeekChatOptions.builder() .options(DeepSeekChatOptions.builder()
.model(properties.getModel()) .model(properties.getModel())
.temperature(properties.getTemperature()) .temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens()) .maxTokens(properties.getMaxTokens())
.topP(properties.getTopP()) .topP(properties.getTopP())
.build()) .build())
.toolCallingManager(getToolCallingManager())
.build(); .build();
return new HunYuanChatModel(openAiChatModel); return new HunYuanChatModel(openAiChatModel);
} }
@ -203,23 +273,17 @@ public class AiAutoConfiguration {
if (StrUtil.isEmpty(properties.getModel())) { if (StrUtil.isEmpty(properties.getModel())) {
properties.setModel(XingHuoChatModel.MODEL_DEFAULT); properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
} }
OpenAiApi.Builder builder = OpenAiApi.builder() String baseUrl = "x1".equals(properties.getModel()) ? XingHuoChatModel.BASE_URL_V2
.baseUrl(XingHuoChatModel.BASE_URL_V1) : XingHuoChatModel.BASE_URL_V1;
.apiKey(properties.getAppKey() + ":" + properties.getSecretKey());
if ("x1".equals(properties.getModel())) {
builder.baseUrl(XingHuoChatModel.BASE_URL_V2)
.completionsPath(XingHuoChatModel.BASE_COMPLETIONS_PATH_V2);
}
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(builder.build()) .options(OpenAiChatOptions.builder()
.defaultOptions(OpenAiChatOptions.builder() .baseUrl(baseUrl)
.apiKey(properties.getAppKey() + ":" + properties.getSecretKey())
.model(properties.getModel()) .model(properties.getModel())
.temperature(properties.getTemperature()) .temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens()) .maxTokens(properties.getMaxTokens())
.topP(properties.getTopP()) .topP(properties.getTopP())
.build()) .build())
// TODO @芋艿:星火的 function call 有 bug会报 ToolResponseMessage must have an id 错误!!!
.toolCallingManager(getToolCallingManager())
.build(); .build();
return new XingHuoChatModel(openAiChatModel); return new XingHuoChatModel(openAiChatModel);
} }
@ -236,21 +300,116 @@ public class AiAutoConfiguration {
properties.setModel(BaiChuanChatModel.MODEL_DEFAULT); properties.setModel(BaiChuanChatModel.MODEL_DEFAULT);
} }
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder() .options(OpenAiChatOptions.builder()
.baseUrl(BaiChuanChatModel.BASE_URL) .baseUrl(BaiChuanChatModel.BASE_URL)
.apiKey(properties.getApiKey()) .apiKey(properties.getApiKey())
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(properties.getModel()) .model(properties.getModel())
.temperature(properties.getTemperature()) .temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens()) .maxTokens(properties.getMaxTokens())
.topP(properties.getTopP()) .topP(properties.getTopP())
.build()) .build())
.toolCallingManager(getToolCallingManager())
.build(); .build();
return new BaiChuanChatModel(openAiChatModel); return new BaiChuanChatModel(openAiChatModel);
} }
@Bean
@ConditionalOnProperty(value = "yudao.ai.yiyan.enable", havingValue = "true")
public YiYanChatModel yiYanChatClient(YudaoAiProperties yudaoAiProperties) {
YudaoAiProperties.YiYan properties = yudaoAiProperties.getYiyan();
return buildYiYanChatClient(properties);
}
public YiYanChatModel buildYiYanChatClient(YudaoAiProperties.YiYan properties) {
if (StrUtil.isEmpty(properties.getModel())) {
properties.setModel(YiYanChatModel.MODEL_DEFAULT);
}
return new YiYanChatModel(buildDeepSeekCompatibleChatModel(
StrUtil.blankToDefault(properties.getBaseUrl(), YiYanChatModel.BASE_URL),
null, properties.getApiKey(), properties.getModel(), properties.getTemperature(),
properties.getMaxTokens(), properties.getTopP()));
}
@Bean
@ConditionalOnProperty(value = "yudao.ai.zhipu.enable", havingValue = "true")
public ZhiPuChatModel zhiPuChatClient(YudaoAiProperties yudaoAiProperties) {
YudaoAiProperties.ZhiPu properties = yudaoAiProperties.getZhipu();
return buildZhiPuChatClient(properties);
}
public ZhiPuChatModel buildZhiPuChatClient(YudaoAiProperties.ZhiPu properties) {
if (StrUtil.isEmpty(properties.getModel())) {
properties.setModel(ZhiPuChatModel.MODEL_DEFAULT);
}
DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()
.deepSeekApi(DeepSeekApi.builder()
.baseUrl(StrUtil.blankToDefault(properties.getBaseUrl(), ZhiPuChatModel.BASE_URL))
.apiKey(properties.getApiKey())
.build())
.options(DeepSeekChatOptions.builder()
.model(properties.getModel())
.temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens())
.topP(properties.getTopP())
.build())
.build();
return new ZhiPuChatModel(deepSeekChatModel);
}
@Bean
@ConditionalOnProperty(value = "yudao.ai.minimax.enable", havingValue = "true")
public MiniMaxChatModel miniMaxChatClient(YudaoAiProperties yudaoAiProperties) {
YudaoAiProperties.MiniMax properties = yudaoAiProperties.getMinimax();
return buildMiniMaxChatClient(properties);
}
public MiniMaxChatModel buildMiniMaxChatClient(YudaoAiProperties.MiniMax properties) {
if (StrUtil.isEmpty(properties.getModel())) {
properties.setModel(MiniMaxChatModel.MODEL_DEFAULT);
}
return new MiniMaxChatModel(buildDeepSeekCompatibleChatModel(
StrUtil.blankToDefault(properties.getBaseUrl(), MiniMaxChatModel.BASE_URL),
null, properties.getApiKey(), properties.getModel(), properties.getTemperature(),
properties.getMaxTokens(), properties.getTopP()));
}
@Bean
@ConditionalOnProperty(value = "yudao.ai.moonshot.enable", havingValue = "true")
public MoonshotChatModel moonshotChatClient(YudaoAiProperties yudaoAiProperties) {
YudaoAiProperties.Moonshot properties = yudaoAiProperties.getMoonshot();
return buildMoonshotChatClient(properties);
}
public MoonshotChatModel buildMoonshotChatClient(YudaoAiProperties.Moonshot properties) {
if (StrUtil.isEmpty(properties.getModel())) {
properties.setModel(MoonshotChatModel.MODEL_DEFAULT);
}
return new MoonshotChatModel(buildDeepSeekCompatibleChatModel(
StrUtil.blankToDefault(properties.getBaseUrl(), MoonshotChatModel.BASE_URL),
MoonshotChatModel.COMPLETE_PATH, properties.getApiKey(), properties.getModel(),
properties.getTemperature(), properties.getMaxTokens(), properties.getTopP()));
}
private static DeepSeekChatModel buildDeepSeekCompatibleChatModel(String baseUrl, String completionsPath,
String apiKey, String model,
Double temperature, Integer maxTokens,
Double topP) {
DeepSeekApi.Builder apiBuilder = DeepSeekApi.builder()
.baseUrl(baseUrl)
.apiKey(apiKey);
if (StrUtil.isNotEmpty(completionsPath)) {
apiBuilder.completionsPath(completionsPath);
}
return DeepSeekChatModel.builder()
.deepSeekApi(apiBuilder.build())
.options(DeepSeekChatOptions.builder()
.model(model)
.temperature(temperature)
.maxTokens(maxTokens)
.topP(topP)
.build())
.build();
}
@Bean @Bean
@ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true") @ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true")
public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) { public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) {
@ -269,21 +428,16 @@ public class AiAutoConfiguration {
properties.setModel(GrokChatModel.MODEL_DEFAULT); properties.setModel(GrokChatModel.MODEL_DEFAULT);
} }
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder() .options(OpenAiChatOptions.builder()
.baseUrl(Optional.ofNullable(properties.getBaseUrl()) .baseUrl(StrUtil.blankToDefault(properties.getBaseUrl(), GrokChatModel.BASE_URL))
.orElse(GrokChatModel.BASE_URL))
.completionsPath(GrokChatModel.COMPLETE_PATH)
.apiKey(properties.getApiKey()) .apiKey(properties.getApiKey())
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(properties.getModel()) .model(properties.getModel())
.temperature(properties.getTemperature()) .temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens()) .maxTokens(properties.getMaxTokens())
.topP(properties.getTopP()) .topP(properties.getTopP())
.build()) .build())
.toolCallingManager(getToolCallingManager())
.build(); .build();
return new DouBaoChatModel(openAiChatModel); return new GrokChatModel(openAiChatModel);
} }
// ========== RAG 相关 ========== // ========== RAG 相关 ==========
@ -302,6 +456,10 @@ public class AiAutoConfiguration {
return SpringUtil.getBean(ToolCallingManager.class); return SpringUtil.getBean(ToolCallingManager.class);
} }
private static ObservationRegistry getObservationRegistry() {
return SpringUtil.getBean(ObservationRegistry.class);
}
// ========== Web Search 相关 ========== // ========== Web Search 相关 ==========
@Bean @Bean
@ -320,4 +478,4 @@ public class AiAutoConfiguration {
return List.of(ToolCallbacks.from(personService)); return List.of(ToolCallbacks.from(personService));
} }
} }

View File

@ -43,6 +43,26 @@ public class YudaoAiProperties {
*/ */
private BaiChuan baichuan; private BaiChuan baichuan;
/**
*
*/
private YiYan yiyan;
/**
*
*/
private ZhiPu zhipu;
/**
* MiniMax
*/
private MiniMax minimax;
/**
*
*/
private Moonshot moonshot;
/** /**
* Midjourney * Midjourney
*/ */
@ -140,6 +160,62 @@ public class YudaoAiProperties {
} }
@Data
public static class YiYan {
private String enable;
private String baseUrl;
private String apiKey;
private String model;
private Double temperature;
private Integer maxTokens;
private Double topP;
}
@Data
public static class ZhiPu {
private String enable;
private String baseUrl;
private String apiKey;
private String model;
private Double temperature;
private Integer maxTokens;
private Double topP;
}
@Data
public static class MiniMax {
private String enable;
private String baseUrl;
private String apiKey;
private String model;
private Double temperature;
private Integer maxTokens;
private Double topP;
}
@Data
public static class Moonshot {
private String enable;
private String baseUrl;
private String apiKey;
private String model;
private Double temperature;
private Integer maxTokens;
private Double topP;
}
@Data @Data
public static class Midjourney { public static class Midjourney {

View File

@ -8,7 +8,6 @@ import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.RuntimeUtil; import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
import cn.iocoder.yudao.module.ai.framework.ai.config.AiAutoConfiguration; import cn.iocoder.yudao.module.ai.framework.ai.config.AiAutoConfiguration;
import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties; import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
@ -17,12 +16,16 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel
import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi; import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel;
import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration; import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration;
import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration; import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration;
import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration; import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration;
@ -30,30 +33,14 @@ import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi; import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel; import com.alibaba.cloud.ai.dashscope.embedding.text.DashScopeEmbeddingModel;
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions; import com.alibaba.cloud.ai.dashscope.embedding.text.DashScopeEmbeddingOptions;
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel; import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.core.credential.KeyCredential;
import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationRegistry;
import io.milvus.client.MilvusServiceClient; import io.milvus.client.MilvusServiceClient;
import io.qdrant.client.QdrantClient; import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient; import io.qdrant.client.QdrantGrpcClient;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.springaicommunity.moonshot.MoonshotChatModel;
import org.springaicommunity.moonshot.MoonshotChatOptions;
import org.springaicommunity.moonshot.api.MoonshotApi;
import org.springaicommunity.moonshot.autoconfigure.MoonshotChatAutoConfiguration;
import org.springaicommunity.qianfan.QianFanChatModel;
import org.springaicommunity.qianfan.QianFanEmbeddingModel;
import org.springaicommunity.qianfan.QianFanEmbeddingOptions;
import org.springaicommunity.qianfan.QianFanImageModel;
import org.springaicommunity.qianfan.api.QianFanApi;
import org.springaicommunity.qianfan.api.QianFanImageApi;
import org.springaicommunity.qianfan.autoconfigure.QianFanChatAutoConfiguration;
import org.springaicommunity.qianfan.autoconfigure.QianFanEmbeddingAutoConfiguration;
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.deepseek.DeepSeekChatModel; import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.DeepSeekChatOptions; import org.springframework.ai.deepseek.DeepSeekChatOptions;
@ -63,27 +50,13 @@ import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
import org.springframework.ai.image.ImageModel; import org.springframework.ai.image.ImageModel;
import org.springframework.ai.minimax.MiniMaxChatModel;
import org.springframework.ai.minimax.MiniMaxChatOptions;
import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
import org.springframework.ai.minimax.MiniMaxEmbeddingOptions;
import org.springframework.ai.minimax.api.MiniMaxApi;
import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration; import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration;
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration;
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration;
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties;
import org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration; import org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration;
import org.springframework.ai.model.minimax.autoconfigure.MiniMaxChatAutoConfiguration;
import org.springframework.ai.model.minimax.autoconfigure.MiniMaxEmbeddingAutoConfiguration;
import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration; import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration;
import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration; import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration;
import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration; import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration;
import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration; import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration;
import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration; import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration;
import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration;
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiEmbeddingAutoConfiguration;
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiImageAutoConfiguration;
import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.OllamaEmbeddingModel; import org.springframework.ai.ollama.OllamaEmbeddingModel;
import org.springframework.ai.ollama.api.OllamaApi; import org.springframework.ai.ollama.api.OllamaApi;
@ -92,11 +65,10 @@ import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.OpenAiEmbeddingModel;
import org.springframework.ai.openai.OpenAiEmbeddingOptions; import org.springframework.ai.openai.OpenAiEmbeddingOptions;
import org.springframework.ai.openai.OpenAiImageModel; import org.springframework.ai.openai.OpenAiImageModel;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.openai.api.OpenAiImageApi;
import org.springframework.ai.openai.api.common.OpenAiApiConstants;
import org.springframework.ai.anthropic.AnthropicChatModel; import org.springframework.ai.anthropic.AnthropicChatModel;
import org.springframework.ai.anthropic.api.AnthropicApi; import org.springframework.ai.anthropic.AnthropicChatOptions;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.OpenAiImageOptions;
import org.springframework.ai.stabilityai.StabilityAiImageModel; import org.springframework.ai.stabilityai.StabilityAiImageModel;
import org.springframework.ai.stabilityai.api.StabilityAiApi; import org.springframework.ai.stabilityai.api.StabilityAiApi;
import org.springframework.ai.vectorstore.SimpleVectorStore; import org.springframework.ai.vectorstore.SimpleVectorStore;
@ -114,14 +86,13 @@ import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStore
import org.springframework.ai.vectorstore.redis.RedisVectorStore; import org.springframework.ai.vectorstore.redis.RedisVectorStore;
import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration; import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration;
import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties; import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties;
import org.springframework.ai.zhipuai.*;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties;
import org.springframework.web.client.RestClient; import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.JedisPooled; import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.RedisClient;
import java.io.File; import java.io.File;
import java.time.Duration; import java.time.Duration;
@ -131,7 +102,6 @@ import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE;
/** /**
* AI Model * AI Model
@ -179,7 +149,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
case OLLAMA: case OLLAMA:
return buildOllamaChatModel(url); return buildOllamaChatModel(url);
case GROK: case GROK:
return buildGrokChatModel(apiKey,url); return buildGrokChatModel(apiKey, url);
default: default:
throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
} }
@ -193,7 +163,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
case TONG_YI: case TONG_YI:
return SpringUtil.getBean(DashScopeChatModel.class); return SpringUtil.getBean(DashScopeChatModel.class);
case YI_YAN: case YI_YAN:
return SpringUtil.getBean(QianFanChatModel.class); return SpringUtil.getBean(YiYanChatModel.class);
case DEEP_SEEK: case DEEP_SEEK:
return SpringUtil.getBean(DeepSeekChatModel.class); return SpringUtil.getBean(DeepSeekChatModel.class);
case DOU_BAO: case DOU_BAO:
@ -203,7 +173,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
case SILICON_FLOW: case SILICON_FLOW:
return SpringUtil.getBean(SiliconFlowChatModel.class); return SpringUtil.getBean(SiliconFlowChatModel.class);
case ZHI_PU: case ZHI_PU:
return SpringUtil.getBean(ZhiPuAiChatModel.class); return SpringUtil.getBean(ZhiPuChatModel.class);
case MINI_MAX: case MINI_MAX:
return SpringUtil.getBean(MiniMaxChatModel.class); return SpringUtil.getBean(MiniMaxChatModel.class);
case MOONSHOT: case MOONSHOT:
@ -214,8 +184,6 @@ public class AiModelFactoryImpl implements AiModelFactory {
return SpringUtil.getBean(BaiChuanChatModel.class); return SpringUtil.getBean(BaiChuanChatModel.class);
case OPENAI: case OPENAI:
return SpringUtil.getBean(OpenAiChatModel.class); return SpringUtil.getBean(OpenAiChatModel.class);
case AZURE_OPENAI:
return SpringUtil.getBean(AzureOpenAiChatModel.class);
case ANTHROPIC: case ANTHROPIC:
return SpringUtil.getBean(AnthropicChatModel.class); return SpringUtil.getBean(AnthropicChatModel.class);
case GEMINI: case GEMINI:
@ -233,10 +201,6 @@ public class AiModelFactoryImpl implements AiModelFactory {
switch (platform) { switch (platform) {
case TONG_YI: case TONG_YI:
return SpringUtil.getBean(DashScopeImageModel.class); return SpringUtil.getBean(DashScopeImageModel.class);
case YI_YAN:
return SpringUtil.getBean(QianFanImageModel.class);
case ZHI_PU:
return SpringUtil.getBean(ZhiPuAiImageModel.class);
case SILICON_FLOW: case SILICON_FLOW:
return SpringUtil.getBean(SiliconFlowImageModel.class); return SpringUtil.getBean(SiliconFlowImageModel.class);
case OPENAI: case OPENAI:
@ -254,10 +218,6 @@ public class AiModelFactoryImpl implements AiModelFactory {
switch (platform) { switch (platform) {
case TONG_YI: case TONG_YI:
return buildTongYiImagesModel(apiKey); return buildTongYiImagesModel(apiKey);
case YI_YAN:
return buildQianFanImageModel(apiKey);
case ZHI_PU:
return buildZhiPuAiImageModel(apiKey, url);
case OPENAI: case OPENAI:
return buildOpenAiImageModel(apiKey, url); return buildOpenAiImageModel(apiKey, url);
case SILICON_FLOW: case SILICON_FLOW:
@ -294,16 +254,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
switch (platform) { switch (platform) {
case TONG_YI: case TONG_YI:
return buildTongYiEmbeddingModel(apiKey, model); return buildTongYiEmbeddingModel(apiKey, model);
case YI_YAN:
return buildYiYanEmbeddingModel(apiKey, model);
case ZHI_PU:
return buildZhiPuEmbeddingModel(apiKey, url, model);
case MINI_MAX:
return buildMiniMaxEmbeddingModel(apiKey, url, model);
case OPENAI: case OPENAI:
return buildOpenAiEmbeddingModel(apiKey, url, model); return buildOpenAiEmbeddingModel(apiKey, url, model);
case AZURE_OPENAI:
return buildAzureOpenAiEmbeddingModel(apiKey, url, model);
case OLLAMA: case OLLAMA:
return buildOllamaEmbeddingModel(url, model); return buildOllamaEmbeddingModel(url, model);
default: default:
@ -347,50 +299,20 @@ public class AiModelFactoryImpl implements AiModelFactory {
* {@link DashScopeChatAutoConfiguration} dashscopeChatModel * {@link DashScopeChatAutoConfiguration} dashscopeChatModel
*/ */
private static DashScopeChatModel buildTongYiChatModel(String key) { private static DashScopeChatModel buildTongYiChatModel(String key) {
DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(key).build(); return AiAutoConfiguration.buildTongYiChatModel(key);
DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(DashScopeApi.DEFAULT_CHAT_MODEL)
.withTemperature(0.7).build();
return DashScopeChatModel.builder()
.dashScopeApi(dashScopeApi)
.defaultOptions(options)
.toolCallingManager(getToolCallingManager())
.build();
} }
/** /**
* {@link DashScopeImageAutoConfiguration} dashScopeImageModel * {@link DashScopeImageAutoConfiguration} dashScopeImageModel
*/ */
private static DashScopeImageModel buildTongYiImagesModel(String key) { private static DashScopeImageModel buildTongYiImagesModel(String key) {
DashScopeImageApi dashScopeImageApi = DashScopeImageApi.builder().apiKey(key).build(); return AiAutoConfiguration.buildTongYiImagesModel(key);
return DashScopeImageModel.builder()
.dashScopeApi(dashScopeImageApi)
.build();
} }
/** private ChatModel buildYiYanChatModel(String apiKey) {
* {@link QianFanChatAutoConfiguration} qianFanChatModel YudaoAiProperties.YiYan properties = new YudaoAiProperties.YiYan()
*/ .setApiKey(apiKey);
private static QianFanChatModel buildYiYanChatModel(String key) { return new AiAutoConfiguration().buildYiYanChatClient(properties);
// TODO spring ai qianfan 有 bug无法使用 https://github.com/spring-ai-community/qianfan/issues/6
List<String> keys = StrUtil.split(key, '|');
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
String appKey = keys.get(0);
String secretKey = keys.get(1);
QianFanApi qianFanApi = new QianFanApi(appKey, secretKey);
return new QianFanChatModel(qianFanApi);
}
/**
* {@link QianFanEmbeddingAutoConfiguration} qianFanImageModel
*/
private QianFanImageModel buildQianFanImageModel(String key) {
// TODO spring ai qianfan 有 bug无法使用 https://github.com/spring-ai-community/qianfan/issues/6
List<String> keys = StrUtil.split(key, '|');
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
String appKey = keys.get(0);
String secretKey = keys.get(1);
QianFanImageApi qianFanApi = new QianFanImageApi(appKey, secretKey);
return new QianFanImageModel(qianFanApi);
} }
/** /**
@ -402,8 +324,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
.temperature(0.7).build(); .temperature(0.7).build();
return DeepSeekChatModel.builder() return DeepSeekChatModel.builder()
.deepSeekApi(deepSeekApi) .deepSeekApi(deepSeekApi)
.defaultOptions(options) .options(options)
.toolCallingManager(getToolCallingManager())
.build(); .build();
} }
@ -435,52 +356,30 @@ public class AiModelFactoryImpl implements AiModelFactory {
} }
/** /**
* {@link ZhiPuAiChatAutoConfiguration} zhiPuAiChatModel * {@link AiAutoConfiguration#zhiPuChatClient(YudaoAiProperties)}
*/ */
private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { private ZhiPuChatModel buildZhiPuChatModel(String apiKey, String url) {
ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey); YudaoAiProperties.ZhiPu properties = new YudaoAiProperties.ZhiPu()
if (StrUtil.isNotEmpty(url)) { .setBaseUrl(url).setApiKey(apiKey);
zhiPuAiApiBuilder.baseUrl(url); return new AiAutoConfiguration().buildZhiPuChatClient(properties);
}
ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
return new ZhiPuAiChatModel(zhiPuAiApiBuilder.build(), options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE,
getObservationRegistry().getIfAvailable());
} }
/** /**
* {@link ZhiPuAiImageAutoConfiguration} zhiPuAiImageModel * {@link AiAutoConfiguration#miniMaxChatClient(YudaoAiProperties)}
*/
private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey)
: new ZhiPuAiImageApi(url, apiKey, RestClient.builder());
return new ZhiPuAiImageModel(zhiPuAiApi);
}
/**
* {@link MiniMaxChatAutoConfiguration} miniMaxChatModel
*/ */
private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) { private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) {
MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey) YudaoAiProperties.MiniMax properties = new YudaoAiProperties.MiniMax()
: new MiniMaxApi(url, apiKey); .setBaseUrl(url).setApiKey(apiKey);
MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build(); return new AiAutoConfiguration().buildMiniMaxChatClient(properties);
return new MiniMaxChatModel(miniMaxApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE);
} }
/** /**
* {@link MoonshotChatAutoConfiguration} moonshotChatModel * {@link AiAutoConfiguration#moonshotChatClient(YudaoAiProperties)}
*/ */
private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) { private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) {
MoonshotApi.Builder moonshotApiBuilder = MoonshotApi.builder() YudaoAiProperties.Moonshot properties = new YudaoAiProperties.Moonshot()
.apiKey(apiKey); .setBaseUrl(url).setApiKey(apiKey);
if (StrUtil.isNotEmpty(url)) { return new AiAutoConfiguration().buildMoonshotChatClient(properties);
moonshotApiBuilder.baseUrl(url);
}
MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build();
return MoonshotChatModel.builder()
.moonshotApi(moonshotApiBuilder.build())
.defaultOptions(options)
.toolCallingManager(getToolCallingManager())
.build();
} }
/** /**
@ -507,39 +406,37 @@ public class AiModelFactoryImpl implements AiModelFactory {
* {@link OpenAiChatAutoConfiguration} openAiChatModel * {@link OpenAiChatAutoConfiguration} openAiChatModel
*/ */
private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) { private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) {
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build();
return OpenAiChatModel.builder() return OpenAiChatModel.builder()
.openAiApi(openAiApi) .options(buildOpenAiChatOptions(openAiToken, url).build())
.toolCallingManager(getToolCallingManager())
.build(); .build();
} }
/** private static OpenAiChatModel buildAzureOpenAiChatModel(String openAiToken, String url) {
* {@link AzureOpenAiChatAutoConfiguration} return OpenAiChatModel.builder()
*/ .options(buildOpenAiChatOptions(openAiToken, url)
private static AzureOpenAiChatModel buildAzureOpenAiChatModel(String apiKey, String url) { .azure(true)
// TODO @芋艿:使用前,请测试,暂时没密钥!!! .build())
OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder()
.endpoint(url).credential(new KeyCredential(apiKey));
return AzureOpenAiChatModel.builder()
.openAIClientBuilder(openAIClientBuilder)
.toolCallingManager(getToolCallingManager())
.build(); .build();
} }
private static OpenAiChatOptions.Builder buildOpenAiChatOptions(String apiKey, String url) {
OpenAiChatOptions.Builder optionsBuilder = OpenAiChatOptions.builder().apiKey(apiKey);
if (StrUtil.isNotEmpty(url)) {
optionsBuilder.baseUrl(url);
}
return optionsBuilder;
}
/** /**
* {@link AnthropicChatAutoConfiguration} anthropicApi * {@link AnthropicChatAutoConfiguration} anthropicApi
*/ */
private static AnthropicChatModel buildAnthropicChatModel(String apiKey, String url) { private static AnthropicChatModel buildAnthropicChatModel(String apiKey, String url) {
AnthropicApi.Builder builder = AnthropicApi.builder().apiKey(apiKey); AnthropicChatOptions.Builder optionsBuilder = AnthropicChatOptions.builder().apiKey(apiKey);
if (StrUtil.isNotEmpty(url)) { if (StrUtil.isNotEmpty(url)) {
builder.baseUrl(url); optionsBuilder.baseUrl(url);
} }
AnthropicApi anthropicApi = builder.build();
return AnthropicChatModel.builder() return AnthropicChatModel.builder()
.anthropicApi(anthropicApi) .options(optionsBuilder.build())
.toolCallingManager(getToolCallingManager())
.build(); .build();
} }
@ -556,9 +453,13 @@ public class AiModelFactoryImpl implements AiModelFactory {
* {@link OpenAiImageAutoConfiguration} openAiImageModel * {@link OpenAiImageAutoConfiguration} openAiImageModel
*/ */
private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) { private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) {
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL); OpenAiImageOptions.Builder optionsBuilder = OpenAiImageOptions.builder().apiKey(openAiToken);
OpenAiImageApi openAiApi = OpenAiImageApi.builder().baseUrl(url).apiKey(openAiToken).build(); if (StrUtil.isNotEmpty(url)) {
return new OpenAiImageModel(openAiApi); optionsBuilder.baseUrl(url);
}
return OpenAiImageModel.builder()
.options(optionsBuilder.build())
.build();
} }
/** /**
@ -577,7 +478,6 @@ public class AiModelFactoryImpl implements AiModelFactory {
OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build(); OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build();
return OllamaChatModel.builder() return OllamaChatModel.builder()
.ollamaApi(ollamaApi) .ollamaApi(ollamaApi)
.toolCallingManager(getToolCallingManager())
.build(); .build();
} }
@ -600,47 +500,10 @@ public class AiModelFactoryImpl implements AiModelFactory {
// ========== 各种创建 EmbeddingModel 的方法 ========== // ========== 各种创建 EmbeddingModel 的方法 ==========
/** /**
* {@link DashScopeEmbeddingAutoConfiguration} dashscopeEmbeddingModel * {@link DashScopeEmbeddingAutoConfiguration} DashScopeEmbeddingModel
*/ */
private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) { private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) {
DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build(); return AiAutoConfiguration.buildTongYiEmbeddingModel(apiKey, model);
DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model).build();
return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions);
}
/**
* {@link ZhiPuAiEmbeddingAutoConfiguration} zhiPuAiEmbeddingModel
*/
private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) {
ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey);
if (StrUtil.isNotEmpty(url)) {
zhiPuAiApiBuilder.baseUrl(url);
}
ZhiPuAiEmbeddingOptions zhiPuAiEmbeddingOptions = ZhiPuAiEmbeddingOptions.builder().model(model).build();
return new ZhiPuAiEmbeddingModel(zhiPuAiApiBuilder.build(), MetadataMode.EMBED, zhiPuAiEmbeddingOptions);
}
/**
* {@link MiniMaxEmbeddingAutoConfiguration} miniMaxEmbeddingModel
*/
private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) {
MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey)
: new MiniMaxApi(url, apiKey);
MiniMaxEmbeddingOptions miniMaxEmbeddingOptions = MiniMaxEmbeddingOptions.builder().model(model).build();
return new MiniMaxEmbeddingModel(miniMaxApi, MetadataMode.EMBED, miniMaxEmbeddingOptions);
}
/**
* {@link QianFanEmbeddingAutoConfiguration} qianFanEmbeddingModel
*/
private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) {
List<String> keys = StrUtil.split(key, '|');
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
String appKey = keys.get(0);
String secretKey = keys.get(1);
QianFanApi qianFanApi = new QianFanApi(appKey, secretKey);
QianFanEmbeddingOptions qianFanEmbeddingOptions = QianFanEmbeddingOptions.builder().model(model).build();
return new QianFanEmbeddingModel(qianFanApi, MetadataMode.EMBED, qianFanEmbeddingOptions);
} }
private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) { private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) {
@ -648,7 +511,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
OllamaEmbeddingOptions ollamaOptions = OllamaEmbeddingOptions.builder().model(model).build(); OllamaEmbeddingOptions ollamaOptions = OllamaEmbeddingOptions.builder().model(model).build();
return OllamaEmbeddingModel.builder() return OllamaEmbeddingModel.builder()
.ollamaApi(ollamaApi) .ollamaApi(ollamaApi)
.defaultOptions(ollamaOptions) .options(ollamaOptions)
.build(); .build();
} }
@ -656,25 +519,16 @@ public class AiModelFactoryImpl implements AiModelFactory {
* {@link OpenAiEmbeddingAutoConfiguration} openAiEmbeddingModel * {@link OpenAiEmbeddingAutoConfiguration} openAiEmbeddingModel
*/ */
private OpenAiEmbeddingModel buildOpenAiEmbeddingModel(String openAiToken, String url, String model) { private OpenAiEmbeddingModel buildOpenAiEmbeddingModel(String openAiToken, String url, String model) {
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL); OpenAiEmbeddingOptions.Builder optionsBuilder = OpenAiEmbeddingOptions.builder()
OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build(); .apiKey(openAiToken)
OpenAiEmbeddingOptions openAiEmbeddingProperties = OpenAiEmbeddingOptions.builder().model(model).build(); .model(model);
return new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED, openAiEmbeddingProperties); if (StrUtil.isNotEmpty(url)) {
} optionsBuilder.baseUrl(url);
}
/** return OpenAiEmbeddingModel.builder()
* {@link AzureOpenAiEmbeddingAutoConfiguration} azureOpenAiEmbeddingModel .metadataMode(MetadataMode.EMBED)
*/ .options(optionsBuilder.build())
private AzureOpenAiEmbeddingModel buildAzureOpenAiEmbeddingModel(String apiKey, String url, String model) { .build();
// TODO @芋艿:手头暂时没密钥,使用建议再测试下
AzureOpenAiEmbeddingAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiEmbeddingAutoConfiguration();
// 创建 OpenAIClientBuilder 对象
OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder()
.endpoint(url).credential(new KeyCredential(apiKey));
// 获取 AzureOpenAiChatProperties 对象
AzureOpenAiEmbeddingProperties embeddingProperties = SpringUtil.getBean(AzureOpenAiEmbeddingProperties.class);
return azureOpenAiAutoConfiguration.azureOpenAiEmbeddingModel(openAIClientBuilder, embeddingProperties,
getObservationRegistry(), getEmbeddingModelObservationConvention());
} }
// ========== 各种创建 VectorStore 的方法 ========== // ========== 各种创建 VectorStore 的方法 ==========
@ -737,13 +591,11 @@ public class AiModelFactoryImpl implements AiModelFactory {
*/ */
private RedisVectorStore buildRedisVectorStore(EmbeddingModel embeddingModel, private RedisVectorStore buildRedisVectorStore(EmbeddingModel embeddingModel,
Map<String, Class<?>> metadataFields) { Map<String, Class<?>> metadataFields) {
// 创建 JedisPooled 对象 // 创建 RedisClient 对象
RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class); RedisClient redisClient = buildRedisClient();
JedisPooled jedisPooled = new JedisPooled(redisProperties.getHost(), redisProperties.getPort(),
redisProperties.getUsername(), redisProperties.getPassword());
// 创建 RedisVectorStoreProperties 对象 // 创建 RedisVectorStoreProperties 对象
RedisVectorStoreProperties properties = SpringUtil.getBean(RedisVectorStoreProperties.class); RedisVectorStoreProperties properties = SpringUtil.getBean(RedisVectorStoreProperties.class);
RedisVectorStore redisVectorStore = RedisVectorStore.builder(jedisPooled, embeddingModel) RedisVectorStore redisVectorStore = RedisVectorStore.builder(redisClient, embeddingModel)
.indexName(properties.getIndexName()).prefix(properties.getPrefix()) .indexName(properties.getIndexName()).prefix(properties.getPrefix())
.initializeSchema(properties.isInitializeSchema()) .initializeSchema(properties.isInitializeSchema())
.metadataFields(convertList(metadataFields.entrySet(), entry -> { .metadataFields(convertList(metadataFields.entrySet(), entry -> {
@ -766,6 +618,43 @@ public class AiModelFactoryImpl implements AiModelFactory {
return redisVectorStore; return redisVectorStore;
} }
private RedisClient buildRedisClient() {
DataRedisProperties redisProperties = SpringUtil.getBean(DataRedisProperties.class);
Assert.isNull(redisProperties.getCluster(), "RedisVectorStore 暂不支持 Redis Cluster 模式");
Assert.isNull(redisProperties.getSentinel(), "RedisVectorStore 暂不支持 Redis Sentinel 模式");
Assert.isNull(redisProperties.getMasterreplica(), "RedisVectorStore 暂不支持 Redis Master-Replica 模式");
if (StrUtil.isNotEmpty(redisProperties.getUrl())) {
return RedisClient.create(redisProperties.getUrl());
}
DefaultJedisClientConfig.Builder clientConfigBuilder = DefaultJedisClientConfig.builder()
.ssl(redisProperties.getSsl().isEnabled())
.database(redisProperties.getDatabase());
if (StrUtil.isNotEmpty(redisProperties.getUsername())) {
clientConfigBuilder.user(redisProperties.getUsername());
}
if (StrUtil.isNotEmpty(redisProperties.getPassword())) {
clientConfigBuilder.password(redisProperties.getPassword());
}
if (StrUtil.isNotEmpty(redisProperties.getClientName())) {
clientConfigBuilder.clientName(redisProperties.getClientName());
}
if (redisProperties.getTimeout() != null) {
clientConfigBuilder.socketTimeoutMillis(toMillis(redisProperties.getTimeout()));
}
if (redisProperties.getConnectTimeout() != null) {
clientConfigBuilder.connectionTimeoutMillis(toMillis(redisProperties.getConnectTimeout()));
}
JedisClientConfig clientConfig = clientConfigBuilder.build();
return RedisClient.builder()
.hostAndPort(new HostAndPort(redisProperties.getHost(), redisProperties.getPort()))
.clientConfig(clientConfig)
.build();
}
private static int toMillis(Duration duration) {
return Math.toIntExact(duration.toMillis());
}
/** /**
* {@link MilvusVectorStoreAutoConfiguration} vectorStore * {@link MilvusVectorStoreAutoConfiguration} vectorStore
*/ */
@ -827,10 +716,6 @@ public class AiModelFactoryImpl implements AiModelFactory {
return SpringUtil.getBean(BatchingStrategy.class); return SpringUtil.getBean(BatchingStrategy.class);
} }
private static ToolCallingManager getToolCallingManager() {
return SpringUtil.getBean(ToolCallingManager.class);
}
private static ObjectProvider<EmbeddingModelObservationConvention> getEmbeddingModelObservationConvention() { private static ObjectProvider<EmbeddingModelObservationConvention> getEmbeddingModelObservationConvention() {
return new ObjectProvider<>() { return new ObjectProvider<>() {

View File

@ -38,8 +38,15 @@ public class BaiChuanChatModel implements ChatModel {
} }
@Override @Override
public ChatOptions getOptions() {
return openAiChatModel.getOptions();
}
@Override
@Deprecated(forRemoval = true)
@SuppressWarnings("removal")
public ChatOptions getDefaultOptions() { public ChatOptions getDefaultOptions() {
return openAiChatModel.getDefaultOptions(); return getOptions();
} }
} }

View File

@ -38,8 +38,15 @@ public class DouBaoChatModel implements ChatModel {
} }
@Override @Override
public ChatOptions getOptions() {
return openAiChatModel.getOptions();
}
@Override
@Deprecated(forRemoval = true)
@SuppressWarnings("removal")
public ChatOptions getDefaultOptions() { public ChatOptions getDefaultOptions() {
return openAiChatModel.getDefaultOptions(); return getOptions();
} }
} }

View File

@ -39,8 +39,15 @@ public class GeminiChatModel implements ChatModel {
} }
@Override @Override
public ChatOptions getOptions() {
return openAiChatModel.getOptions();
}
@Override
@Deprecated(forRemoval = true)
@SuppressWarnings("removal")
public ChatOptions getDefaultOptions() { public ChatOptions getDefaultOptions() {
return openAiChatModel.getDefaultOptions(); return getOptions();
} }
} }

View File

@ -37,8 +37,15 @@ public class GrokChatModel implements ChatModel {
} }
@Override @Override
public ChatOptions getOptions() {
return openAiChatModel.getOptions();
}
@Override
@Deprecated(forRemoval = true)
@SuppressWarnings("removal")
public ChatOptions getDefaultOptions() { public ChatOptions getDefaultOptions() {
return openAiChatModel.getDefaultOptions(); return getOptions();
} }
} }

View File

@ -45,8 +45,15 @@ public class HunYuanChatModel implements ChatModel {
} }
@Override @Override
public ChatOptions getOptions() {
return openAiChatModel.getOptions();
}
@Override
@Deprecated(forRemoval = true)
@SuppressWarnings("removal")
public ChatOptions getDefaultOptions() { public ChatOptions getDefaultOptions() {
return openAiChatModel.getDefaultOptions(); return getOptions();
} }
} }

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import reactor.core.publisher.Flux;
/**
* MiniMax {@link ChatModel}
*
* @author
*/
@Slf4j
@RequiredArgsConstructor
public class MiniMaxChatModel implements ChatModel {
public static final String BASE_URL = "https://api.minimaxi.com/v1";
public static final String MODEL_DEFAULT = "MiniMax-M3";
/**
* OpenAI DeepSeek
*/
private final DeepSeekChatModel deepSeekChatModel;
@Override
public ChatResponse call(Prompt prompt) {
return deepSeekChatModel.call(prompt);
}
@Override
public Flux<ChatResponse> stream(Prompt prompt) {
return deepSeekChatModel.stream(prompt);
}
@Override
public ChatOptions getOptions() {
return deepSeekChatModel.getOptions();
}
@Override
@Deprecated(forRemoval = true)
@SuppressWarnings("removal")
public ChatOptions getDefaultOptions() {
return getOptions();
}
}

View File

@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import reactor.core.publisher.Flux;
/**
* {@link ChatModel}
*
* @author
*/
@Slf4j
@RequiredArgsConstructor
public class MoonshotChatModel implements ChatModel {
public static final String BASE_URL = "https://api.moonshot.cn";
public static final String COMPLETE_PATH = "/v1/chat/completions";
public static final String MODEL_DEFAULT = "kimi-k2.6";
/**
* OpenAI DeepSeek
*/
private final DeepSeekChatModel deepSeekChatModel;
@Override
public ChatResponse call(Prompt prompt) {
return deepSeekChatModel.call(prompt);
}
@Override
public Flux<ChatResponse> stream(Prompt prompt) {
return deepSeekChatModel.stream(prompt);
}
@Override
public ChatOptions getOptions() {
return deepSeekChatModel.getOptions();
}
@Override
@Deprecated(forRemoval = true)
@SuppressWarnings("removal")
public ChatOptions getDefaultOptions() {
return getOptions();
}
}

View File

@ -6,7 +6,6 @@ import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
/** /**
@ -36,8 +35,15 @@ public class SiliconFlowChatModel implements ChatModel {
} }
@Override @Override
public ChatOptions getOptions() {
return openAiChatModel.getOptions();
}
@Override
@Deprecated(forRemoval = true)
@SuppressWarnings("removal")
public ChatOptions getDefaultOptions() { public ChatOptions getDefaultOptions() {
return openAiChatModel.getDefaultOptions(); return getOptions();
} }
} }

View File

@ -21,18 +21,14 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.ai.model.ApiKey; import org.springframework.ai.model.ApiKey;
import org.springframework.ai.model.NoopApiKey; import org.springframework.ai.model.NoopApiKey;
import org.springframework.ai.model.SimpleApiKey; import org.springframework.ai.model.SimpleApiKey;
import org.springframework.ai.openai.api.OpenAiImageApi;
import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.retry.RetryUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClient;
import java.util.Map;
/** /**
* Image API * Image API
* *
@ -58,15 +54,15 @@ public class SiliconFlowImageApi {
public SiliconFlowImageApi(String baseUrl, String apiKey, RestClient.Builder restClientBuilder, public SiliconFlowImageApi(String baseUrl, String apiKey, RestClient.Builder restClientBuilder,
ResponseErrorHandler responseErrorHandler) { ResponseErrorHandler responseErrorHandler) {
this(baseUrl, apiKey, CollectionUtils.toMultiValueMap(Map.of()), restClientBuilder, responseErrorHandler); this(baseUrl, apiKey, new HttpHeaders(), restClientBuilder, responseErrorHandler);
} }
public SiliconFlowImageApi(String baseUrl, String apiKey, MultiValueMap<String, String> headers, public SiliconFlowImageApi(String baseUrl, String apiKey, HttpHeaders headers,
RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
this(baseUrl, new SimpleApiKey(apiKey), headers, restClientBuilder, responseErrorHandler); this(baseUrl, new SimpleApiKey(apiKey), headers, restClientBuilder, responseErrorHandler);
} }
public SiliconFlowImageApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, String> headers, public SiliconFlowImageApi(String baseUrl, ApiKey apiKey, HttpHeaders headers,
RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
// @formatter:off // @formatter:off
@ -83,7 +79,7 @@ public class SiliconFlowImageApi {
// @formatter:on // @formatter:on
} }
public ResponseEntity<OpenAiImageApi.OpenAiImageResponse> createImage(SiliconflowImageRequest siliconflowImageRequest) { public ResponseEntity<SiliconFlowImageResponse> createImage(SiliconflowImageRequest siliconflowImageRequest) {
Assert.notNull(siliconflowImageRequest, "Image request cannot be null."); Assert.notNull(siliconflowImageRequest, "Image request cannot be null.");
Assert.hasLength(siliconflowImageRequest.prompt(), "Prompt cannot be empty."); Assert.hasLength(siliconflowImageRequest.prompt(), "Prompt cannot be empty.");
@ -91,7 +87,7 @@ public class SiliconFlowImageApi {
.uri("v1/images/generations") .uri("v1/images/generations")
.body(siliconflowImageRequest) .body(siliconflowImageRequest)
.retrieve() .retrieve()
.toEntity(OpenAiImageApi.OpenAiImageResponse.class); .toEntity(SiliconFlowImageResponse.class);
} }
@ -112,4 +108,15 @@ public class SiliconFlowImageApi {
} }
} }
public record SiliconFlowImageResponse(
@JsonProperty("created") Long created,
@JsonProperty("data") java.util.List<Entry> data) {
public record Entry(
@JsonProperty("url") String url,
@JsonProperty("b64_json") String b64Json,
@JsonProperty("revised_prompt") String revisedPrompt) {
}
}
} }

View File

@ -27,12 +27,11 @@ import org.springframework.ai.image.observation.ImageModelObservationConvention;
import org.springframework.ai.image.observation.ImageModelObservationDocumentation; import org.springframework.ai.image.observation.ImageModelObservationDocumentation;
import org.springframework.ai.model.ModelOptionsUtils; import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.openai.OpenAiImageModel; import org.springframework.ai.openai.OpenAiImageModel;
import org.springframework.ai.openai.api.OpenAiImageApi;
import org.springframework.ai.openai.metadata.OpenAiImageGenerationMetadata; import org.springframework.ai.openai.metadata.OpenAiImageGenerationMetadata;
import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.retry.RetryUtils;
import org.springframework.core.retry.RetryTemplate;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import java.util.List; import java.util.List;
@ -46,102 +45,112 @@ import java.util.List;
*/ */
public class SiliconFlowImageModel implements ImageModel { public class SiliconFlowImageModel implements ImageModel {
private static final Logger logger = LoggerFactory.getLogger(SiliconFlowImageModel.class); private static final Logger logger = LoggerFactory.getLogger(SiliconFlowImageModel.class);
private static final ImageModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultImageModelObservationConvention(); private static final ImageModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultImageModelObservationConvention();
private final SiliconFlowImageOptions defaultOptions; private final SiliconFlowImageOptions defaultOptions;
private final RetryTemplate retryTemplate; private final RetryTemplate retryTemplate;
private final SiliconFlowImageApi siliconFlowImageApi; private final SiliconFlowImageApi siliconFlowImageApi;
private final ObservationRegistry observationRegistry; private final ObservationRegistry observationRegistry;
@Setter @Setter
private ImageModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION; private ImageModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION;
public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi) { public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi) {
this(siliconFlowImageApi, SiliconFlowImageOptions.builder().build(), RetryUtils.DEFAULT_RETRY_TEMPLATE); this(siliconFlowImageApi, SiliconFlowImageOptions.builder().build(), RetryUtils.DEFAULT_RETRY_TEMPLATE);
} }
public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi, SiliconFlowImageOptions options, RetryTemplate retryTemplate) { public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi, SiliconFlowImageOptions options, RetryTemplate retryTemplate) {
this(siliconFlowImageApi, options, retryTemplate, ObservationRegistry.NOOP); this(siliconFlowImageApi, options, retryTemplate, ObservationRegistry.NOOP);
} }
public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi, SiliconFlowImageOptions options, RetryTemplate retryTemplate, public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi, SiliconFlowImageOptions options, RetryTemplate retryTemplate,
ObservationRegistry observationRegistry) { ObservationRegistry observationRegistry) {
Assert.notNull(siliconFlowImageApi, "OpenAiImageApi must not be null"); Assert.notNull(siliconFlowImageApi, "SiliconFlowImageApi must not be null");
Assert.notNull(options, "options must not be null"); Assert.notNull(options, "options must not be null");
Assert.notNull(retryTemplate, "retryTemplate must not be null"); Assert.notNull(retryTemplate, "retryTemplate must not be null");
Assert.notNull(observationRegistry, "observationRegistry must not be null"); Assert.notNull(observationRegistry, "observationRegistry must not be null");
this.siliconFlowImageApi = siliconFlowImageApi; this.siliconFlowImageApi = siliconFlowImageApi;
this.defaultOptions = options; this.defaultOptions = options;
this.retryTemplate = retryTemplate; this.retryTemplate = retryTemplate;
this.observationRegistry = observationRegistry; this.observationRegistry = observationRegistry;
} }
@Override @Override
public ImageResponse call(ImagePrompt imagePrompt) { public ImageResponse call(ImagePrompt imagePrompt) {
SiliconFlowImageOptions requestImageOptions = mergeOptions(imagePrompt.getOptions(), this.defaultOptions); SiliconFlowImageOptions requestImageOptions = mergeOptions(imagePrompt.getOptions(), this.defaultOptions);
SiliconFlowImageApi.SiliconflowImageRequest imageRequest = createRequest(imagePrompt, requestImageOptions); SiliconFlowImageApi.SiliconflowImageRequest imageRequest = createRequest(imagePrompt, requestImageOptions);
var observationContext = ImageModelObservationContext.builder() var observationContext = ImageModelObservationContext.builder()
.imagePrompt(imagePrompt) .imagePrompt(imagePrompt)
.provider(SiliconFlowApiConstants.PROVIDER_NAME) .provider(SiliconFlowApiConstants.PROVIDER_NAME)
.imagePrompt(imagePrompt) .imagePrompt(imagePrompt)
.build(); .build();
return ImageModelObservationDocumentation.IMAGE_MODEL_OPERATION return ImageModelObservationDocumentation.IMAGE_MODEL_OPERATION
.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext,
this.observationRegistry) this.observationRegistry)
.observe(() -> { .observe(() -> {
ResponseEntity<OpenAiImageApi.OpenAiImageResponse> imageResponseEntity = this.retryTemplate ResponseEntity<SiliconFlowImageApi.SiliconFlowImageResponse> imageResponseEntity = RetryUtils.execute(
.execute(ctx -> this.siliconFlowImageApi.createImage(imageRequest)); this.retryTemplate, () -> this.siliconFlowImageApi.createImage(imageRequest));
ImageResponse imageResponse = convertResponse(imageResponseEntity, imageRequest); ImageResponse imageResponse = convertResponse(imageResponseEntity, imageRequest);
observationContext.setResponse(imageResponse); observationContext.setResponse(imageResponse);
return imageResponse; return imageResponse;
}); });
} }
private SiliconFlowImageApi.SiliconflowImageRequest createRequest(ImagePrompt imagePrompt, private SiliconFlowImageApi.SiliconflowImageRequest createRequest(ImagePrompt imagePrompt,
SiliconFlowImageOptions requestImageOptions) { SiliconFlowImageOptions requestImageOptions) {
String instructions = imagePrompt.getInstructions().get(0).getText(); String instructions = imagePrompt.getInstructions().getFirst().getText();
SiliconFlowImageApi.SiliconflowImageRequest imageRequest = new SiliconFlowImageApi.SiliconflowImageRequest(instructions, return new SiliconFlowImageApi.SiliconflowImageRequest(
SiliconFlowApiConstants.DEFAULT_IMAGE_MODEL); instructions,
ModelOptionsUtils.mergeOption(requestImageOptions.getModel(), SiliconFlowApiConstants.DEFAULT_IMAGE_MODEL),
requestImageOptions.getN(),
requestImageOptions.getNegativePrompt(),
requestImageOptions.getSeed(),
requestImageOptions.getNumInferenceSteps(),
requestImageOptions.getGuidanceScale(),
requestImageOptions.getImage());
}
return ModelOptionsUtils.merge(requestImageOptions, imageRequest, SiliconFlowImageApi.SiliconflowImageRequest.class); private ImageResponse convertResponse(ResponseEntity<SiliconFlowImageApi.SiliconFlowImageResponse> imageResponseEntity,
} SiliconFlowImageApi.SiliconflowImageRequest siliconflowImageRequest) {
SiliconFlowImageApi.SiliconFlowImageResponse imageApiResponse = imageResponseEntity.getBody();
if (imageApiResponse == null) {
logger.warn("No image response returned for request: {}", siliconflowImageRequest);
return new ImageResponse(List.of());
}
private ImageResponse convertResponse(ResponseEntity<OpenAiImageApi.OpenAiImageResponse> imageResponseEntity, List<ImageGeneration> imageGenerationList = imageApiResponse.data()
SiliconFlowImageApi.SiliconflowImageRequest siliconflowImageRequest) { .stream()
OpenAiImageApi.OpenAiImageResponse imageApiResponse = imageResponseEntity.getBody(); .map(entry -> new ImageGeneration(new Image(entry.url(), entry.b64Json()),
if (imageApiResponse == null) { new OpenAiImageGenerationMetadata(entry.revisedPrompt())))
logger.warn("No image response returned for request: {}", siliconflowImageRequest); .toList();
return new ImageResponse(List.of());
}
List<ImageGeneration> imageGenerationList = imageApiResponse.data() ImageResponseMetadata openAiImageResponseMetadata = new ImageResponseMetadata(imageApiResponse.created());
.stream() return new ImageResponse(imageGenerationList, openAiImageResponseMetadata);
.map(entry -> new ImageGeneration(new Image(entry.url(), entry.b64Json()), }
new OpenAiImageGenerationMetadata(entry.revisedPrompt())))
.toList();
ImageResponseMetadata openAiImageResponseMetadata = new ImageResponseMetadata(imageApiResponse.created());
return new ImageResponse(imageGenerationList, openAiImageResponseMetadata);
}
private SiliconFlowImageOptions mergeOptions(@Nullable ImageOptions runtimeOptions, SiliconFlowImageOptions defaultOptions) { private SiliconFlowImageOptions mergeOptions(@Nullable ImageOptions runtimeOptions, SiliconFlowImageOptions defaultOptions) {
var runtimeOptionsForProvider = ModelOptionsUtils.copyToTarget(runtimeOptions, ImageOptions.class, if (runtimeOptions == null) {
SiliconFlowImageOptions.class);
if (runtimeOptionsForProvider == null) {
return defaultOptions; return defaultOptions;
} }
SiliconFlowImageOptions runtimeOptionsForProvider = runtimeOptions instanceof SiliconFlowImageOptions siliconFlowImageOptions
? siliconFlowImageOptions
: SiliconFlowImageOptions.builder()
.model(runtimeOptions.getModel())
.batchSize(runtimeOptions.getN())
.width(runtimeOptions.getWidth())
.height(runtimeOptions.getHeight())
.build();
return SiliconFlowImageOptions.builder() return SiliconFlowImageOptions.builder()
// Handle portable image options // Handle portable image options

View File

@ -43,8 +43,15 @@ public class XingHuoChatModel implements ChatModel {
} }
@Override @Override
public ChatOptions getOptions() {
return openAiChatModelV1.getOptions();
}
@Override
@Deprecated(forRemoval = true)
@SuppressWarnings("removal")
public ChatOptions getDefaultOptions() { public ChatOptions getDefaultOptions() {
return openAiChatModelV1.getDefaultOptions(); return getOptions();
} }
} }

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import reactor.core.publisher.Flux;
/**
* {@link ChatModel}
*
* @author
*/
@Slf4j
@RequiredArgsConstructor
public class YiYanChatModel implements ChatModel {
public static final String BASE_URL = "https://qianfan.baidubce.com/v2";
public static final String MODEL_DEFAULT = "ernie-4.0-8k-latest";
/**
* OpenAI DeepSeek
*/
private final DeepSeekChatModel deepSeekChatModel;
@Override
public ChatResponse call(Prompt prompt) {
return deepSeekChatModel.call(prompt);
}
@Override
public Flux<ChatResponse> stream(Prompt prompt) {
return deepSeekChatModel.stream(prompt);
}
@Override
public ChatOptions getOptions() {
return deepSeekChatModel.getOptions();
}
@Override
@Deprecated(forRemoval = true)
@SuppressWarnings("removal")
public ChatOptions getDefaultOptions() {
return getOptions();
}
}

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import reactor.core.publisher.Flux;
/**
* {@link ChatModel}
*
* @author
*/
@Slf4j
@RequiredArgsConstructor
public class ZhiPuChatModel implements ChatModel {
public static final String BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
public static final String MODEL_DEFAULT = "glm-5.2";
/**
* OpenAI DeepSeek
*/
private final DeepSeekChatModel deepSeekChatModel;
@Override
public ChatResponse call(Prompt prompt) {
return deepSeekChatModel.call(prompt);
}
@Override
public Flux<ChatResponse> stream(Prompt prompt) {
return deepSeekChatModel.stream(prompt);
}
@Override
public ChatOptions getOptions() {
return deepSeekChatModel.getOptions();
}
@Override
@Deprecated(forRemoval = true)
@SuppressWarnings("removal")
public ChatOptions getDefaultOptions() {
return getOptions();
}
}

View File

@ -1,27 +1,25 @@
package cn.iocoder.yudao.module.ai.framework.security.config; package cn.iocoder.yudao.module.ai.framework.security.config;
import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer; import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
import cn.iocoder.yudao.module.infra.enums.ApiConstants; import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Value;
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties;
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties;
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.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import java.util.Optional;
/** /**
* AI Security * AI Security
*/ */
@Configuration(proxyBeanMethods = false, value = "aiSecurityConfiguration") @Configuration(proxyBeanMethods = false, value = "aiSecurityConfiguration")
public class SecurityConfiguration { public class SecurityConfiguration {
@Resource @Value("${spring.ai.mcp.server.sse-endpoint:/sse}")
private Optional<McpServerSseProperties> mcpServerSseProperties; private String mcpSseEndpoint;
@Resource @Value("${spring.ai.mcp.server.sse-message-endpoint:/mcp/message}")
private Optional<McpServerStreamableHttpProperties> mcpServerStreamableHttpProperties; private String mcpSseMessageEndpoint;
@Value("${spring.ai.mcp.server.streamable-http-endpoint:/mcp}")
private String mcpStreamableHttpEndpoint;
@Bean("aiAuthorizeRequestsCustomizer") @Bean("aiAuthorizeRequestsCustomizer")
public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
@ -29,28 +27,15 @@ public class SecurityConfiguration {
@Override @Override
public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) { public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
// Swagger 接口文档 if (StrUtil.isNotBlank(mcpSseEndpoint)) {
registry.requestMatchers("/v3/api-docs/**").permitAll() registry.requestMatchers(mcpSseEndpoint).permitAll();
.requestMatchers("/webjars/**").permitAll() }
.requestMatchers("/swagger-ui").permitAll() if (StrUtil.isNotBlank(mcpSseMessageEndpoint)) {
.requestMatchers("/swagger-ui/**").permitAll(); registry.requestMatchers(mcpSseMessageEndpoint).permitAll();
// Spring Boot Actuator 的安全配置 }
registry.requestMatchers("/actuator").permitAll() if (StrUtil.isNotBlank(mcpStreamableHttpEndpoint)) {
.requestMatchers("/actuator/**").permitAll(); registry.requestMatchers(mcpStreamableHttpEndpoint).permitAll();
// Druid 监控 }
registry.requestMatchers("/druid/**").permitAll();
// TODO 芋艿:这个每个项目都需要重复配置,得捉摸有没通用的方案
// RPC 服务的安全配置
registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll();
// MCP Server
mcpServerSseProperties.ifPresent(properties -> {
registry.requestMatchers(properties.getSseEndpoint()).permitAll();
registry.requestMatchers(properties.getSseMessageEndpoint()).permitAll();
});
mcpServerStreamableHttpProperties.ifPresent(properties ->
registry.requestMatchers(properties.getMcpEndpoint()).permitAll());
} }
}; };

View File

@ -49,10 +49,10 @@ import org.springframework.ai.chat.model.StreamingChatModel;
import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider; import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpClientCommonProperties;
import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.resolution.ToolCallbackResolver; import org.springframework.ai.tool.resolution.ToolCallbackResolver;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
@ -130,9 +130,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
@Autowired(required = false) // 由于 yudao.ai.mcp.client.enable 配置项,可以关闭 McpSyncClient 的功能,所以这里只能不强制注入 @Autowired(required = false) // 由于 yudao.ai.mcp.client.enable 配置项,可以关闭 McpSyncClient 的功能,所以这里只能不强制注入
private List<McpSyncClient> mcpClients; private List<McpSyncClient> mcpClients;
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") @Value("${spring.ai.mcp.client.name:mcp}")
@Autowired(required = false) // 由于 yudao.ai.mcp.client.enable 配置项,可以关闭 McpSyncClient 的功能,所以这里只能不强制注入 private String mcpClientName;
private McpClientCommonProperties mcpClientCommonProperties;
@Resource @Resource
private ToolCallbackResolver toolCallbackResolver; private ToolCallbackResolver toolCallbackResolver;
@ -410,13 +409,16 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
if (CollUtil.isNotEmpty(mcpClients) && CollUtil.isNotEmpty(chatRole.getMcpClientNames())) { if (CollUtil.isNotEmpty(mcpClients) && CollUtil.isNotEmpty(chatRole.getMcpClientNames())) {
chatRole.getMcpClientNames().forEach(mcpClientName -> { chatRole.getMcpClientNames().forEach(mcpClientName -> {
// 2.1 标准化名字,参考 McpClientAutoConfiguration 的 connectedClientName 方法 // 2.1 标准化名字,参考 McpClientAutoConfiguration 的 connectedClientName 方法
String finalMcpClientName = mcpClientCommonProperties.getName() + " - " + mcpClientName; String finalMcpClientName = this.mcpClientName + " - " + mcpClientName;
// 2.2 匹配对应的 McpSyncClient // 2.2 匹配对应的 McpSyncClient
mcpClients.forEach(mcpClient -> { mcpClients.forEach(mcpClient -> {
if (ObjUtil.notEqual(mcpClient.getClientInfo().name(), finalMcpClientName)) { if (ObjUtil.notEqual(mcpClient.getClientInfo().name(), finalMcpClientName)) {
return; return;
} }
ToolCallback[] mcpToolCallBacks = new SyncMcpToolCallbackProvider(mcpClient).getToolCallbacks(); ToolCallback[] mcpToolCallBacks = SyncMcpToolCallbackProvider.builder()
.mcpClients(mcpClient)
.build()
.getToolCallbacks();
CollUtil.addAll(toolCallbacks, mcpToolCallBacks); CollUtil.addAll(toolCallbacks, mcpToolCallBacks);
}); });
}); });
@ -539,7 +541,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
public void deleteChatMessageByConversationId(Long conversationId, Long userId) { public void deleteChatMessageByConversationId(Long conversationId, Long userId) {
// 1. 校验消息存在 // 1. 校验消息存在
List<AiChatMessageDO> messages = chatMessageMapper.selectListByConversationId(conversationId); List<AiChatMessageDO> messages = chatMessageMapper.selectListByConversationId(conversationId);
if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) { if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.getFirst().getUserId(), userId)) {
throw exception(CHAT_MESSAGE_NOT_EXIST); throw exception(CHAT_MESSAGE_NOT_EXIST);
} }
// 2. 执行删除 // 2. 执行删除

View File

@ -29,14 +29,12 @@ import cn.iocoder.yudao.module.infra.api.file.FileApi;
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions; import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springaicommunity.qianfan.QianFanImageOptions;
import org.springframework.ai.image.ImageModel; import org.springframework.ai.image.ImageModel;
import org.springframework.ai.image.ImageOptions; import org.springframework.ai.image.ImageOptions;
import org.springframework.ai.image.ImagePrompt; import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse; import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.openai.OpenAiImageOptions; import org.springframework.ai.openai.OpenAiImageOptions;
import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -168,15 +166,6 @@ public class AiImageServiceImpl implements AiImageService {
.model(model.getModel()).n(1) .model(model.getModel()).n(1)
.height(draw.getHeight()).width(draw.getWidth()) .height(draw.getHeight()).width(draw.getWidth())
.build(); .build();
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) {
return QianFanImageOptions.builder()
.model(model.getModel()).N(1)
.height(draw.getHeight()).width(draw.getWidth())
.build();
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.ZHI_PU.getPlatform())) {
return ZhiPuAiImageOptions.builder()
.model(model.getModel())
.build();
} }
throw new IllegalArgumentException("不支持的 AI 平台:" + model.getPlatform()); throw new IllegalArgumentException("不支持的 AI 平台:" + model.getPlatform());
} }

View File

@ -166,7 +166,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
segmentMapper.deleteByIds(convertList(segments, AiKnowledgeSegmentDO::getId)); segmentMapper.deleteByIds(convertList(segments, AiKnowledgeSegmentDO::getId));
// 3. 删除向量存储中的段落 // 3. 删除向量存储中的段落
VectorStore vectorStore = getVectorStoreById(segments.get(0).getKnowledgeId()); VectorStore vectorStore = getVectorStoreById(segments.getFirst().getKnowledgeId());
vectorStore.delete(convertList(segments, AiKnowledgeSegmentDO::getVectorId)); vectorStore.delete(convertList(segments, AiKnowledgeSegmentDO::getVectorId));
} }
@ -299,7 +299,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
// 2. Rerank 重排序 // 2. Rerank 重排序
if (rerankModel != null) { if (rerankModel != null) {
RerankResponse rerankResponse = rerankModel.call(new RerankRequest(reqBO.getContent(), documents, RerankResponse rerankResponse = rerankModel.call(new RerankRequest(reqBO.getContent(), documents,
DashScopeRerankOptions.builder().withTopN(topK).build())); DashScopeRerankOptions.builder().topN(topK).build()));
documents = convertList(rerankResponse.getResults(), documents = convertList(rerankResponse.getResults(),
documentWithScore -> documentWithScore.getScore() >= similarityThreshold documentWithScore -> documentWithScore.getScore() >= similarityThreshold
? documentWithScore.getOutput() : null); ? documentWithScore.getOutput() : null);

View File

@ -8,20 +8,15 @@ import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import org.springaicommunity.moonshot.MoonshotChatOptions;
import org.springaicommunity.qianfan.QianFanChatOptions;
import org.springframework.ai.anthropic.AnthropicChatOptions; import org.springframework.ai.anthropic.AnthropicChatOptions;
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
import org.springframework.ai.chat.messages.*; import org.springframework.ai.chat.messages.*;
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.deepseek.DeepSeekAssistantMessage; import org.springframework.ai.deepseek.DeepSeekAssistantMessage;
import org.springframework.ai.deepseek.DeepSeekChatOptions; import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.ai.minimax.MiniMaxChatOptions;
import org.springframework.ai.ollama.api.OllamaChatOptions; import org.springframework.ai.ollama.api.OllamaChatOptions;
import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
import java.util.*; import java.util.*;
@ -42,7 +37,8 @@ public class AiUtils {
* @see <a href="https://help.aliyun.com/zh/model-studio/error-code#error-url"> withMultiModel </a> * @see <a href="https://help.aliyun.com/zh/model-studio/error-code#error-url"> withMultiModel </a>
*/ */
public static final Set<String> TONG_YI_MULTI_MODELS = SetUtils.asSet( public static final Set<String> TONG_YI_MULTI_MODELS = SetUtils.asSet(
// qwen3.5 / 3.6 系列(统一多模态主干) // qwen3.5 / 3.6 / 3.7 系列(统一多模态主干)
"qwen3.7-max", "qwen3.7-plus", "qwen3.7-flash",
"qwen3.6-plus", "qwen3.6-flash", "qwen3.6-plus", "qwen3.6-flash",
"qwen3.5-plus", "qwen3.5-flash", "qwen3.5-plus", "qwen3.5-flash",
// qwen-vl 视觉理解 // qwen-vl 视觉理解
@ -72,24 +68,17 @@ public class AiUtils {
.enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置 .enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置
.multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型 .multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型
.toolCallbacks(toolCallbacks).toolContext(toolContext).build(); .toolCallbacks(toolCallbacks).toolContext(toolContext).build();
case YI_YAN:
return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
case DEEP_SEEK: case DEEP_SEEK:
case DOU_BAO: // 复用 DeepSeek 客户端 case DOU_BAO: // 复用 DeepSeek 客户端
case HUN_YUAN: // 复用 DeepSeek 客户端 case HUN_YUAN: // 复用 DeepSeek 客户端
case SILICON_FLOW: // 复用 DeepSeek 客户端 case SILICON_FLOW: // 复用 DeepSeek 客户端
case XING_HUO: // 复用 DeepSeek 客户端 case XING_HUO: // 复用 DeepSeek 客户端
case YI_YAN: // 复用 DeepSeek 客户端
case ZHI_PU: // 复用 DeepSeek 客户端
case MINI_MAX: // 复用 DeepSeek 客户端
case MOONSHOT: // 复用 DeepSeek 客户端
return DeepSeekChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) return DeepSeekChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
.toolCallbacks(toolCallbacks).toolContext(toolContext).build(); .toolCallbacks(toolCallbacks).toolContext(toolContext).build();
case ZHI_PU:
return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
case MINI_MAX:
return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
case MOONSHOT:
return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
case OPENAI: case OPENAI:
case GEMINI: // 复用 OpenAI 客户端 case GEMINI: // 复用 OpenAI 客户端
case BAI_CHUAN: // 复用 OpenAI 客户端 case BAI_CHUAN: // 复用 OpenAI 客户端
@ -97,7 +86,8 @@ public class AiUtils {
return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
.toolCallbacks(toolCallbacks).toolContext(toolContext).build(); .toolCallbacks(toolCallbacks).toolContext(toolContext).build();
case AZURE_OPENAI: case AZURE_OPENAI:
return AzureOpenAiChatOptions.builder().deploymentName(model).temperature(temperature).maxTokens(maxTokens) return OpenAiChatOptions.builder().model(model).deploymentName(model).azure(true)
.temperature(temperature).maxTokens(maxTokens)
.toolCallbacks(toolCallbacks).toolContext(toolContext).build(); .toolCallbacks(toolCallbacks).toolContext(toolContext).build();
case ANTHROPIC: case ANTHROPIC:
return AnthropicChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) return AnthropicChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
@ -159,4 +149,4 @@ public class AiUtils {
return MapUtil.getStr(output.getMetadata(), "reasoningContent"); return MapUtil.getStr(output.getMetadata(), "reasoningContent");
} }
} }

View File

@ -0,0 +1,29 @@
package org.springframework.ai.model.tool;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
import java.lang.reflect.Method;
/**
* TODO spring-ai-alibaba 2.0.0-M1.1 Spring AI ToolExecutionEligibilityPredicate
* Spring AI 2.0.0 spring-ai-alibaba shim
*/
public class DefaultToolExecutionEligibilityPredicate implements ToolExecutionEligibilityPredicate {
@Override
public boolean test(ChatOptions promptOptions, ChatResponse chatResponse) {
return isInternalToolExecutionEnabled(promptOptions) && chatResponse != null && chatResponse.hasToolCalls();
}
private static boolean isInternalToolExecutionEnabled(ChatOptions promptOptions) {
try {
Method method = promptOptions.getClass().getMethod("getInternalToolExecutionEnabled");
Object result = method.invoke(promptOptions);
return result == null || Boolean.TRUE.equals(result);
} catch (ReflectiveOperationException | SecurityException ignored) {
return true;
}
}
}

View File

@ -0,0 +1,21 @@
package org.springframework.ai.model.tool;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.util.Assert;
import java.util.function.BiPredicate;
/**
* TODO spring-ai-alibaba 2.0.0-M1.1 Spring AI ToolExecutionEligibilityPredicate
* Spring AI 2.0.0 spring-ai-alibaba shim
*/
public interface ToolExecutionEligibilityPredicate extends BiPredicate<ChatOptions, ChatResponse> {
default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse) {
Assert.notNull(promptOptions, "promptOptions cannot be null");
Assert.notNull(chatResponse, "chatResponse cannot be null");
return test(promptOptions, chatResponse);
}
}

View File

@ -17,11 +17,25 @@ spring:
--- #################### 数据库相关配置 #################### --- #################### 数据库相关配置 ####################
spring: spring:
# 数据源配置项
autoconfigure: autoconfigure:
# noinspection SpringBootApplicationYaml
exclude: exclude:
- org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant手动创建 - org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant手动创建
- org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration # 禁用 AI 模块的 Redis手动创建
- org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus手动创建 - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus手动创建
- org.dromara.trans.config.TransServiceConfig # TODO 芋艿easy-trans 兼容 Spring Boot 4 后移除TransServiceConfig 引用了已移除的 RestTemplateBuilder
# TODO 芋艿spring-ai-alibaba 2.0.0-M1.1 的 DashScope 自动配置暂不兼容 Spring AI 2.0.0
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAgentAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeVideoAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAudioSpeechAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAudioTranscriptionAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeRerankAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeMultimodalEmbeddingAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAsyncToolCallingManagerAutoConfiguration
# 数据源配置项
datasource: datasource:
druid: # Druid 【监控】相关的全局配置 druid: # Druid 【监控】相关的全局配置
web-stat-filter: web-stat-filter:

View File

@ -17,11 +17,25 @@ spring:
--- #################### 数据库相关配置 #################### --- #################### 数据库相关配置 ####################
spring: spring:
# 数据源配置项
autoconfigure: autoconfigure:
# noinspection SpringBootApplicationYaml
exclude: exclude:
- org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant手动创建 - org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant手动创建
- org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration # 禁用 AI 模块的 Redis手动创建
- org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus手动创建 - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus手动创建
- org.dromara.trans.config.TransServiceConfig # TODO 芋艿easy-trans 兼容 Spring Boot 4 后移除TransServiceConfig 引用了已移除的 RestTemplateBuilder
# TODO 芋艿spring-ai-alibaba 2.0.0-M1.1 的 DashScope 自动配置暂不兼容 Spring AI 2.0.0
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAgentAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeVideoAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAudioSpeechAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAudioTranscriptionAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeRerankAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeMultimodalEmbeddingAutoConfiguration
- com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAsyncToolCallingManagerAutoConfiguration
# 数据源配置项
datasource: datasource:
druid: # Druid 【监控】相关的全局配置 druid: # Druid 【监控】相关的全局配置
web-stat-filter: web-stat-filter:
@ -141,4 +155,4 @@ yudao:
security: security:
mock-enable: true mock-enable: true
access-log: # 访问日志的配置项 access-log: # 访问日志的配置项
enable: false enable: false

View File

@ -20,14 +20,10 @@ spring:
multipart: multipart:
max-file-size: 16MB # 单个文件大小 max-file-size: 16MB # 单个文件大小
max-request-size: 32MB # 设置总上传的文件大小 max-request-size: 32MB # 设置总上传的文件大小
encoding:
# Jackson 配置项 enabled: true
jackson: charset: UTF-8 # 必须设置 UTF-8避免 WebFlux 流式返回AI 场景)会乱码问题
serialization: force: true
write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
fail-on-empty-beans: false # 允许序列化无属性的 Bean
# Cache 配置项 # Cache 配置项
cache: cache:
@ -37,11 +33,6 @@ spring:
server: server:
port: 48090 port: 48090
servlet:
encoding:
enabled: true
charset: UTF-8 # 必须设置 UTF-8避免 WebFlux 流式返回AI 场景)会乱码问题
force: true
logging: logging:
file: file:

View File

@ -4,7 +4,6 @@ import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.ai.anthropic.AnthropicChatModel; import org.springframework.ai.anthropic.AnthropicChatModel;
import org.springframework.ai.anthropic.AnthropicChatOptions; import org.springframework.ai.anthropic.AnthropicChatOptions;
import org.springframework.ai.anthropic.api.AnthropicApi;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.UserMessage;
@ -23,12 +22,10 @@ import java.util.List;
public class AnthropicChatModelTest { public class AnthropicChatModelTest {
private final AnthropicChatModel chatModel = AnthropicChatModel.builder() private final AnthropicChatModel chatModel = AnthropicChatModel.builder()
.anthropicApi(AnthropicApi.builder() .options(AnthropicChatOptions.builder()
.apiKey("sk-muubv7cXeLw0Etgs743f365cD5Ea44429946Fa7e672d8942") .apiKey("sk-muubv7cXeLw0Etgs743f365cD5Ea44429946Fa7e672d8942")
.baseUrl("https://aihubmix.com") .baseUrl("https://aihubmix.com")
.build()) .model("claude-sonnet-4-5")
.defaultOptions(AnthropicChatOptions.builder()
.model(AnthropicApi.ChatModel.CLAUDE_SONNET_4_5)
.temperature(0.7) .temperature(0.7)
.maxTokens(4096) .maxTokens(4096)
.build()) .build())
@ -70,8 +67,7 @@ public class AnthropicChatModelTest {
List<Message> messages = new ArrayList<>(); List<Message> messages = new ArrayList<>();
messages.add(new UserMessage("thkinking 下1+1 为什么等于 2 ")); messages.add(new UserMessage("thkinking 下1+1 为什么等于 2 "));
AnthropicChatOptions options = AnthropicChatOptions.builder() AnthropicChatOptions options = AnthropicChatOptions.builder()
.model(AnthropicApi.ChatModel.CLAUDE_SONNET_4_5) .model("claude-sonnet-4-5")
.thinking(AnthropicApi.ThinkingType.ENABLED, 3096)
.temperature(1D) .temperature(1D)
.build(); .build();

View File

@ -1,71 +0,0 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatProperties.DEFAULT_DEPLOYMENT_NAME;
/**
* {@link AzureOpenAiChatModel}
*
* @author
*/
public class AzureOpenAIChatModelTests {
// TODO @芋艿:晚点在调整
private final OpenAIClientBuilder openAiApi = new OpenAIClientBuilder()
.endpoint("https://eastusprejade.openai.azure.com")
.credential(new AzureKeyCredential("xxx"));
private final AzureOpenAiChatModel chatModel = AzureOpenAiChatModel.builder()
.openAIClientBuilder(openAiApi)
.defaultOptions(AzureOpenAiChatOptions.builder()
.deploymentName(DEFAULT_DEPLOYMENT_NAME)
.build())
.build();
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -10,7 +10,6 @@ import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.ArrayList; import java.util.ArrayList;
@ -24,11 +23,9 @@ import java.util.List;
public class BaiChuanChatModelTests { public class BaiChuanChatModelTests {
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder() .options(OpenAiChatOptions.builder()
.baseUrl(BaiChuanChatModel.BASE_URL) .baseUrl(BaiChuanChatModel.BASE_URL)
.apiKey("sk-61b6766a94c70786ed02673f5e16af3c") // apiKey .apiKey("sk-61b6766a94c70786ed02673f5e16af3c") // apiKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model("Baichuan4-Turbo") // 模型https://platform.baichuan-ai.com/docs/api .model("Baichuan4-Turbo") // 模型https://platform.baichuan-ai.com/docs/api
.temperature(0.7) .temperature(0.7)
.build()) .build())

View File

@ -8,11 +8,12 @@ import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.openai.OpenAiChatOptions;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* {@link OpenAiChatModel} Coze * {@link OpenAiChatModel} Coze
@ -22,7 +23,7 @@ import java.util.List;
public class CozeChatModelTests { public class CozeChatModelTests {
private final OpenAiChatModel chatModel = OpenAiChatModel.builder() private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder() .options(OpenAiChatOptions.builder()
.baseUrl("http://127.0.0.1:3000") .baseUrl("http://127.0.0.1:3000")
.apiKey("app-4hy2d7fJauSbrKbzTKX1afuP") // apiKey .apiKey("app-4hy2d7fJauSbrKbzTKX1afuP") // apiKey
.build()) .build())
@ -40,7 +41,7 @@ public class CozeChatModelTests {
ChatResponse response = chatModel.call(new Prompt(messages)); ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果 // 打印结果
System.out.println(response); System.out.println(response);
System.out.println(response.getResult().getOutput()); System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
} }
@Test @Test
@ -56,7 +57,7 @@ public class CozeChatModelTests {
// 打印结果 // 打印结果
flux.doOnNext(response -> { flux.doOnNext(response -> {
// System.out.println(response); // System.out.println(response);
System.out.println(response.getResult().getOutput()); System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
}).then().block(); }).then().block();
} }

View File

@ -26,8 +26,9 @@ public class DeepSeekChatModelTests {
.deepSeekApi(DeepSeekApi.builder() .deepSeekApi(DeepSeekApi.builder()
.apiKey("sk-eaf4172a057344dd9bc64b1f806b6axx") // apiKey .apiKey("sk-eaf4172a057344dd9bc64b1f806b6axx") // apiKey
.build()) .build())
.defaultOptions(DeepSeekChatOptions.builder() .options(DeepSeekChatOptions.builder()
.model("deepseek-chat") // 模型 .model("deepseek-v4-flash") // 模型
// .model("deepseek-v4-pro") // 模型
.temperature(0.7) .temperature(0.7)
.build()) .build())
.build(); .build();

View File

@ -8,11 +8,12 @@ import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.openai.OpenAiChatOptions;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* {@link OpenAiChatModel} Dify * {@link OpenAiChatModel} Dify
@ -22,7 +23,7 @@ import java.util.List;
public class DifyChatModelTests { public class DifyChatModelTests {
private final OpenAiChatModel chatModel = OpenAiChatModel.builder() private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder() .options(OpenAiChatOptions.builder()
.baseUrl("http://127.0.0.1:3000") .baseUrl("http://127.0.0.1:3000")
.apiKey("app-4hy2d7fJauSbrKbzTKX1afuP") // apiKey .apiKey("app-4hy2d7fJauSbrKbzTKX1afuP") // apiKey
.build()) .build())
@ -40,7 +41,7 @@ public class DifyChatModelTests {
ChatResponse response = chatModel.call(new Prompt(messages)); ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果 // 打印结果
System.out.println(response); System.out.println(response);
System.out.println(response.getResult().getOutput()); System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
} }
@Test @Test
@ -56,7 +57,7 @@ public class DifyChatModelTests {
// 打印结果 // 打印结果
flux.doOnNext(response -> { flux.doOnNext(response -> {
// System.out.println(response); // System.out.println(response);
System.out.println(response.getResult().getOutput()); System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
}).then().block(); }).then().block();
} }

View File

@ -32,7 +32,7 @@ public class DouBaoChatModelTests {
.completionsPath(DouBaoChatModel.COMPLETE_PATH) .completionsPath(DouBaoChatModel.COMPLETE_PATH)
.apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey .apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey
.build()) .build())
.defaultOptions(DeepSeekChatOptions.builder() .options(DeepSeekChatOptions.builder()
.model("doubao-1-5-lite-32k-250115") // 模型doubao .model("doubao-1-5-lite-32k-250115") // 模型doubao
// .model("doubao-seed-1-6-thinking-250715") // 模型doubao // .model("doubao-seed-1-6-thinking-250715") // 模型doubao
// .model("deepseek-r1-250120") // 模型deepseek // .model("deepseek-r1-250120") // 模型deepseek

View File

@ -8,11 +8,12 @@ import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.openai.OpenAiChatOptions;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* {@link OpenAiChatModel} FastGPT * {@link OpenAiChatModel} FastGPT
@ -22,7 +23,7 @@ import java.util.List;
public class FastGPTChatModelTests { public class FastGPTChatModelTests {
private final OpenAiChatModel chatModel = OpenAiChatModel.builder() private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder() .options(OpenAiChatOptions.builder()
.baseUrl("https://cloud.fastgpt.cn/api") .baseUrl("https://cloud.fastgpt.cn/api")
.apiKey("fastgpt-aqcc61kFtF8CeaglnGAfQOCIDWwjGdJVJHv6hIlMo28otFlva2aZNK") // apiKey .apiKey("fastgpt-aqcc61kFtF8CeaglnGAfQOCIDWwjGdJVJHv6hIlMo28otFlva2aZNK") // apiKey
.build()) .build())
@ -40,7 +41,7 @@ public class FastGPTChatModelTests {
ChatResponse response = chatModel.call(new Prompt(messages)); ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果 // 打印结果
System.out.println(response); System.out.println(response);
System.out.println(response.getResult().getOutput()); System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
} }
@Test @Test
@ -56,7 +57,7 @@ public class FastGPTChatModelTests {
// 打印结果 // 打印结果
flux.doOnNext(response -> { flux.doOnNext(response -> {
// System.out.println(response); // System.out.println(response);
System.out.println(response.getResult().getOutput()); System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
}).then().block(); }).then().block();
} }

View File

@ -10,7 +10,6 @@ import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.ArrayList; import java.util.ArrayList;
@ -24,12 +23,9 @@ import java.util.List;
public class GeminiChatModelTests { public class GeminiChatModelTests {
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder() .options(OpenAiChatOptions.builder()
.baseUrl(GeminiChatModel.BASE_URL) .baseUrl(GeminiChatModel.BASE_URL)
.completionsPath(GeminiChatModel.COMPLETE_PATH)
.apiKey("AIzaSyAVoBxgoFvvte820vEQMma2LKBnC98bqMQ") .apiKey("AIzaSyAVoBxgoFvvte820vEQMma2LKBnC98bqMQ")
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(GeminiChatModel.MODEL_DEFAULT) // 模型 .model(GeminiChatModel.MODEL_DEFAULT) // 模型
.temperature(0.7) .temperature(0.7)
.build()) .build())

View File

@ -29,7 +29,7 @@ public class HunYuanChatModelTests {
.completionsPath(HunYuanChatModel.COMPLETE_PATH) .completionsPath(HunYuanChatModel.COMPLETE_PATH)
.apiKey("sk-abc") // apiKey .apiKey("sk-abc") // apiKey
.build()) .build())
.defaultOptions(DeepSeekChatOptions.builder() .options(DeepSeekChatOptions.builder()
.model(HunYuanChatModel.MODEL_DEFAULT) // 模型 .model(HunYuanChatModel.MODEL_DEFAULT) // 模型
.temperature(0.7) .temperature(0.7)
.build()) .build())
@ -91,7 +91,7 @@ public class HunYuanChatModelTests {
.completionsPath(HunYuanChatModel.COMPLETE_PATH) .completionsPath(HunYuanChatModel.COMPLETE_PATH)
.apiKey("sk-abc") // apiKey .apiKey("sk-abc") // apiKey
.build()) .build())
.defaultOptions(DeepSeekChatOptions.builder() .options(DeepSeekChatOptions.builder()
// .model(HunYuanChatModel.DEEP_SEEK_MODEL_DEFAULT) // 模型("deepseek-v3" // .model(HunYuanChatModel.DEEP_SEEK_MODEL_DEFAULT) // 模型("deepseek-v3"
.model("deepseek-r1") // 模型("deepseek-r1" .model("deepseek-r1") // 模型("deepseek-r1"
.temperature(0.7) .temperature(0.7)

View File

@ -27,7 +27,7 @@ public class LlamaChatModelTests {
.ollamaApi(OllamaApi.builder() .ollamaApi(OllamaApi.builder()
.baseUrl("http://127.0.0.1:11434") // Ollama 服务地址 .baseUrl("http://127.0.0.1:11434") // Ollama 服务地址
.build()) .build())
.defaultOptions(OllamaChatOptions.builder() .options(OllamaChatOptions.builder()
.model(OllamaModel.LLAMA3.getName()) // 模型 .model(OllamaModel.LLAMA3.getName()) // 模型
.build()) .build())
.build(); .build();

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
@ -7,9 +8,9 @@ import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.minimax.MiniMaxChatModel; import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.minimax.MiniMaxChatOptions; import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.ai.minimax.api.MiniMaxApi; import org.springframework.ai.deepseek.api.DeepSeekApi;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.ArrayList; import java.util.ArrayList;
@ -22,11 +23,15 @@ import java.util.List;
*/ */
public class MiniMaxChatModelTests { public class MiniMaxChatModelTests {
private final MiniMaxChatModel chatModel = new MiniMaxChatModel( private final MiniMaxChatModel chatModel = new MiniMaxChatModel(DeepSeekChatModel.builder()
new MiniMaxApi("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiLnjovmlofmlowiLCJVc2VyTmFtZSI6IueOi-aWh-aWjCIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxODk3Mjg3MjQ5NDU2ODA4MzQ2IiwiUGhvbmUiOiIxNTYwMTY5MTM5OSIsIkdyb3VwSUQiOiIxODk3Mjg3MjQ5NDQ4NDE5NzM4IiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjUtMDMtMTEgMTI6NTI6MDIiLCJUb2tlblR5cGUiOjEsImlzcyI6Im1pbmltYXgifQ.aAuB7gWW_oA4IYhh-CF7c9MfWWxKN49B_HK-DYjXaDwwffhiG-H1571z1WQhp9QytWG-DqgLejneeSxkiq1wQIe3FsEP2wz4BmGBct31LehbJu8ehLxg_vg75Uod1nFAHbm5mZz6JSVLNIlSo87Xr3UtSzJhAXlapEkcqlA4YOzOpKrZ8l5_OJPTORTCmHWZYgJcRS-faNiH62ZnUEHUozesTFhubJHo5GfJCw_edlnmfSUocERV1BjWvenhZ9My-aYXNktcW9WaSj9l6gayV7A0Ium_PL55T9ln1PcI8gayiVUKJGJDoqNyF1AF9_aF9NOKtTnQzwNqnZdlTYH6hw"), // 密钥 .deepSeekApi(DeepSeekApi.builder()
MiniMaxChatOptions.builder() .baseUrl(MiniMaxChatModel.BASE_URL)
.model(MiniMaxApi.ChatModel.ABAB_6_5_G_Chat.getValue()) // 模型 .apiKey("sk-api-xxx") // 密钥
.build()); .build())
.options(DeepSeekChatOptions.builder()
.model(MiniMaxChatModel.MODEL_DEFAULT) // 模型
.build())
.build());
@Test @Test
@Disabled @Disabled
public void testCall() { public void testCall() {
@ -59,14 +64,13 @@ public class MiniMaxChatModelTests {
}).then().block(); }).then().block();
} }
// TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复
@Test @Test
@Disabled @Disabled
public void testStream_thinking() { public void testStream_thinking() {
// 准备参数 // 准备参数
List<Message> messages = new ArrayList<>(); List<Message> messages = new ArrayList<>();
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
MiniMaxChatOptions options = MiniMaxChatOptions.builder() DeepSeekChatOptions options = DeepSeekChatOptions.builder()
.model("MiniMax-M1") .model("MiniMax-M1")
.build(); .build();

View File

@ -1,15 +1,16 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springaicommunity.moonshot.MoonshotChatModel;
import org.springaicommunity.moonshot.MoonshotChatOptions;
import org.springaicommunity.moonshot.api.MoonshotApi;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.ai.deepseek.api.DeepSeekApi;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.ArrayList; import java.util.ArrayList;
@ -22,14 +23,17 @@ import java.util.List;
*/ */
public class MoonshotChatModelTests { public class MoonshotChatModelTests {
private final MoonshotChatModel chatModel = MoonshotChatModel.builder() private final MoonshotChatModel chatModel = new MoonshotChatModel(DeepSeekChatModel.builder()
.moonshotApi(MoonshotApi.builder() .deepSeekApi(DeepSeekApi.builder()
.apiKey("sk-aHYYV1SARscItye5QQRRNbXij4fy65Ee7pNZlC9gsSQnUKXA") // 密钥 .baseUrl(MoonshotChatModel.BASE_URL)
.completionsPath(MoonshotChatModel.COMPLETE_PATH)
.apiKey("sk-xxx") // 密钥
.build()) .build())
.defaultOptions(MoonshotChatOptions.builder() .options(DeepSeekChatOptions.builder()
.model("kimi-k2-0711-preview") // 模型 .model(MoonshotChatModel.MODEL_DEFAULT) // 模型
.temperature(1D)
.build()) .build())
.build(); .build());
@Test @Test
@Disabled @Disabled
@ -63,16 +67,14 @@ public class MoonshotChatModelTests {
}).then().block(); }).then().block();
} }
// TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复
@Test @Test
@Disabled @Disabled
public void testStream_thinking() { public void testStream_thinking() {
// 准备参数 // 准备参数
List<Message> messages = new ArrayList<>(); List<Message> messages = new ArrayList<>();
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
MoonshotChatOptions options = MoonshotChatOptions.builder() DeepSeekChatOptions options = DeepSeekChatOptions.builder()
// .model("kimi-k2-0711-preview") .model(MoonshotChatModel.MODEL_DEFAULT)
.model("kimi-thinking-preview")
.build(); .build();
// 调用 // 调用

View File

@ -26,7 +26,7 @@ public class OllamaChatModelTests {
.ollamaApi(OllamaApi.builder() .ollamaApi(OllamaApi.builder()
.baseUrl("http://127.0.0.1:11434") // Ollama 服务地址 .baseUrl("http://127.0.0.1:11434") // Ollama 服务地址
.build()) .build())
.defaultOptions(OllamaChatOptions.builder() .options(OllamaChatOptions.builder()
// .model("qwen") // 模型https://ollama.com/library/qwen // .model("qwen") // 模型https://ollama.com/library/qwen
.model("deepseek-r1") // 模型https://ollama.com/library/deepseek-r1 .model("deepseek-r1") // 模型https://ollama.com/library/deepseek-r1
.build()) .build())

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import com.azure.ai.openai.models.ReasoningEffortValue;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
@ -10,11 +9,11 @@ import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* {@link OpenAiChatModel} * {@link OpenAiChatModel}
@ -24,13 +23,11 @@ import java.util.List;
public class OpenAIChatModelTests { public class OpenAIChatModelTests {
private final OpenAiChatModel chatModel = OpenAiChatModel.builder() private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder() .options(OpenAiChatOptions.builder()
.baseUrl("https://api.holdai.top") .baseUrl("https://api.holdai.top")
.apiKey("sk-z5joyRoV1iFEnh2SAi8QPNrIZTXyQSyxTmD5CoNDQbFixK2l") // apiKey .apiKey("sk-xxx") // apiKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model("gpt-5-nano-2025-08-07") // 模型 .model("gpt-5-nano-2025-08-07") // 模型
// .model(OpenAiApi.ChatModel.O1) // 模型 // .model("o1") // 模型
.temperature(0.7) .temperature(0.7)
.build()) .build())
.build(); .build();
@ -47,7 +44,7 @@ public class OpenAIChatModelTests {
ChatResponse response = chatModel.call(new Prompt(messages)); ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果 // 打印结果
System.out.println(response); System.out.println(response);
System.out.println(response.getResult().getOutput()); System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
} }
@Test @Test
@ -63,7 +60,7 @@ public class OpenAIChatModelTests {
// 打印结果 // 打印结果
flux.doOnNext(response -> { flux.doOnNext(response -> {
// System.out.println(response); // System.out.println(response);
System.out.println(response.getResult().getOutput()); System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
}).then().block(); }).then().block();
} }
@ -76,9 +73,9 @@ public class OpenAIChatModelTests {
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
OpenAiChatOptions options = OpenAiChatOptions.builder() OpenAiChatOptions options = OpenAiChatOptions.builder()
.model("gpt-5") .model("gpt-5")
// .model(OpenAiApi.ChatModel.O4_MINI) // .model("o4-mini")
// .model("o3-pro") // .model("o3-pro")
.reasoningEffort(ReasoningEffortValue.LOW.getValue()) .reasoningEffort("low")
.build(); .build();
// 调用 // 调用
@ -86,7 +83,7 @@ public class OpenAIChatModelTests {
// 打印结果 // 打印结果
flux.doOnNext(response -> { flux.doOnNext(response -> {
// System.out.println(response); // System.out.println(response);
System.out.println(response.getResult().getOutput()); System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
}).then().block(); }).then().block();
} }

View File

@ -29,7 +29,7 @@ public class SiliconFlowChatModelTests {
.baseUrl(SiliconFlowApiConstants.DEFAULT_BASE_URL) .baseUrl(SiliconFlowApiConstants.DEFAULT_BASE_URL)
.apiKey("sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz") // apiKey .apiKey("sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz") // apiKey
.build()) .build())
.defaultOptions(DeepSeekChatOptions.builder() .options(DeepSeekChatOptions.builder()
.model(SiliconFlowApiConstants.MODEL_DEFAULT) // 模型 .model(SiliconFlowApiConstants.MODEL_DEFAULT) // 模型
// .model("deepseek-ai/DeepSeek-R1") // 模型deepseek-ai/DeepSeek-R1可用赠费 // .model("deepseek-ai/DeepSeek-R1") // 模型deepseek-ai/DeepSeek-R1可用赠费
// .model("Pro/deepseek-ai/DeepSeek-R1") // 模型Pro/deepseek-ai/DeepSeek-R1需要付费 // .model("Pro/deepseek-ai/DeepSeek-R1") // 模型Pro/deepseek-ai/DeepSeek-R1需要付费

View File

@ -29,7 +29,7 @@ public class XingHuoChatModelTests {
.completionsPath(XingHuoChatModel.BASE_COMPLETIONS_PATH_V2) .completionsPath(XingHuoChatModel.BASE_COMPLETIONS_PATH_V2)
.apiKey("75b161ed2aef4719b275d6e7f2a4d4cd:YWYxYWI2MTA4ODI2NGZlYTQyNjAzZTcz") // appKey:secretKey .apiKey("75b161ed2aef4719b275d6e7f2a4d4cd:YWYxYWI2MTA4ODI2NGZlYTQyNjAzZTcz") // appKey:secretKey
.build()) .build())
.defaultOptions(DeepSeekChatOptions.builder() .options(DeepSeekChatOptions.builder()
// .model("generalv3.5") // 模型 // .model("generalv3.5") // 模型
.model("x1") // 模型 .model("x1") // 模型
.temperature(0.7) .temperature(0.7)

View File

@ -1,41 +1,42 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springaicommunity.qianfan.QianFanChatModel;
import org.springaicommunity.qianfan.QianFanChatOptions;
import org.springaicommunity.qianfan.api.QianFanApi;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.ai.deepseek.api.DeepSeekApi;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
// TODO @芋艿:百度千帆 API 提供了 V2 版本,目前 Spring AI 不兼容,可关键 <https://github.com/spring-projects/spring-ai/issues/2179> 进展
/** /**
* {@link QianFanChatModel} * {@link YiYanChatModel}
* *
* @author fansili * @author fansili
*/ */
public class YiYanChatModelTests { public class YiYanChatModelTests {
private final QianFanChatModel chatModel = new QianFanChatModel( private final YiYanChatModel chatModel = new YiYanChatModel(DeepSeekChatModel.builder()
new QianFanApi("DGnyzREuaY7av7c38bOM9Ji2", "9aR8myflEOPDrEeLhoXv0FdqANOAyIZW"), // 密钥 .deepSeekApi(DeepSeekApi.builder()
QianFanChatOptions.builder() .baseUrl(YiYanChatModel.BASE_URL)
.model("ERNIE-4.5-8K-Preview") .apiKey("bce-v3/xxx") // 密钥
.build() .build())
); .options(DeepSeekChatOptions.builder()
.model(YiYanChatModel.MODEL_DEFAULT)
.build())
.build());
@Test @Test
@Disabled @Disabled
public void testCall() { public void testCall() {
// 准备参数 // 准备参数
List<Message> messages = new ArrayList<>(); List<Message> messages = new ArrayList<>();
// TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名!
// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = ")); messages.add(new UserMessage("1 + 1 = "));
// 调用 // 调用
@ -49,8 +50,6 @@ public class YiYanChatModelTests {
public void testStream() { public void testStream() {
// 准备参数 // 准备参数
List<Message> messages = new ArrayList<>(); List<Message> messages = new ArrayList<>();
// TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名!
// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = ")); messages.add(new UserMessage("1 + 1 = "));
// 调用 // 调用

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
@ -7,27 +8,30 @@ import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.zhipuai.ZhiPuAiChatModel; import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi; import org.springframework.ai.deepseek.api.DeepSeekApi;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* {@link ZhiPuAiChatModel} * {@link ZhiPuChatModel}
* *
* @author * @author
*/ */
public class ZhiPuAiChatModelTests { public class ZhiPuAiChatModelTests {
private final ZhiPuAiChatModel chatModel = new ZhiPuAiChatModel( private final ZhiPuChatModel chatModel = new ZhiPuChatModel(DeepSeekChatModel.builder()
ZhiPuAiApi.builder().apiKey("2f35fb6ca4ea41fab898729b7fac086c.6ESSfPcCkxaKEUlR").build(), // 密钥 .deepSeekApi(DeepSeekApi.builder()
ZhiPuAiChatOptions.builder() .baseUrl(ZhiPuChatModel.BASE_URL)
.model(ZhiPuAiApi.ChatModel.GLM_4.getName()) // 模型 .apiKey("sk-xxx") // 密钥
.build())
.options(DeepSeekChatOptions.builder()
.model(ZhiPuChatModel.MODEL_DEFAULT) // 模型
.build() .build()
); ).build());
@Test @Test
@Disabled @Disabled
@ -61,15 +65,14 @@ public class ZhiPuAiChatModelTests {
}).then().block(); }).then().block();
} }
// TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复
@Test @Test
@Disabled @Disabled
public void testStream_thinking() { public void testStream_thinking() {
// 准备参数 // 准备参数
List<Message> messages = new ArrayList<>(); List<Message> messages = new ArrayList<>();
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder() DeepSeekChatOptions options = DeepSeekChatOptions.builder()
.model("GLM-4.5") .model(ZhiPuChatModel.MODEL_DEFAULT)
.build(); .build();
// 调用 // 调用

View File

@ -7,7 +7,6 @@ import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse; import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.openai.OpenAiImageModel; import org.springframework.ai.openai.OpenAiImageModel;
import org.springframework.ai.openai.OpenAiImageOptions; import org.springframework.ai.openai.OpenAiImageOptions;
import org.springframework.ai.openai.api.OpenAiImageApi;
/** /**
* {@link OpenAiImageModel} * {@link OpenAiImageModel}
@ -16,17 +15,19 @@ import org.springframework.ai.openai.api.OpenAiImageApi;
*/ */
public class OpenAiImageModelTests { public class OpenAiImageModelTests {
private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder() private final OpenAiImageModel imageModel = OpenAiImageModel.builder()
.baseUrl("https://api.holdai.top") // apiKey .options(OpenAiImageOptions.builder()
.apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD") .baseUrl("https://api.holdai.top") // apiKey
.build()); .apiKey("sk-xxx")
.build())
.build();
@Test @Test
@Disabled @Disabled
public void testCall() { public void testCall() {
// 准备参数 // 准备参数
ImageOptions options = OpenAiImageOptions.builder() ImageOptions options = OpenAiImageOptions.builder()
.model(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜 .model("dall-e-2") // 这个模型比较便宜
.height(256).width(256) .height(256).width(256)
.build(); .build();
ImagePrompt prompt = new ImagePrompt("中国长城!", options); ImagePrompt prompt = new ImagePrompt("中国长城!", options);

View File

@ -1,43 +0,0 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springaicommunity.qianfan.QianFanImageModel;
import org.springaicommunity.qianfan.QianFanImageOptions;
import org.springaicommunity.qianfan.api.QianFanImageApi;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import static cn.iocoder.yudao.module.ai.framework.ai.core.model.image.StabilityAiImageModelTests.viewImage;
// TODO @芋艿:百度千帆 API 提供了 V2 版本,目前 Spring AI 不兼容,可关键 <https://github.com/spring-projects/spring-ai/issues/2179> 进展
/**
* {@link QianFanImageModel}
*/
public class QianFanImageTests {
private final QianFanImageModel imageModel = new QianFanImageModel(
new QianFanImageApi("qS8k8dYr2nXunagK4SSU8Xjj", "pHGbx51ql2f0hOyabQvSZezahVC3hh3e")); // 密钥
@Test
@Disabled
public void testCall() {
// 准备参数
// 只支持 1024x1024、768x768、768x1024、1024x768、576x1024、1024x576
QianFanImageOptions imageOptions = QianFanImageOptions.builder()
.model(QianFanImageApi.ImageModel.Stable_Diffusion_XL.getValue())
.width(1024).height(1024)
.N(1)
.build();
ImagePrompt prompt = new ImagePrompt("good", imageOptions);
// 方法调用
ImageResponse response = imageModel.call(prompt);
// 打印结果
String b64Json = response.getResult().getOutput().getB64Json();
System.out.println(response);
viewImage(b64Json);
}
}

View File

@ -1,35 +0,0 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.zhipuai.ZhiPuAiImageModel;
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
/**
* {@link ZhiPuAiImageModel}
*/
public class ZhiPuAiImageModelTests {
private final ZhiPuAiImageModel imageModel = new ZhiPuAiImageModel(
new ZhiPuAiImageApi("78d3228c1d9e5e342a3e1ab349e2dd7b.VXLoq5vrwK2ofboy") // 密钥
);
@Test
@Disabled
public void testCall() {
// 准备参数
ZhiPuAiImageOptions imageOptions = ZhiPuAiImageOptions.builder()
.model(ZhiPuAiImageApi.ImageModel.CogView_3.getValue())
.build();
ImagePrompt prompt = new ImagePrompt("万里长城", imageOptions);
// 方法调用
ImageResponse response = imageModel.call(prompt);
// 打印结果
System.out.println(response);
}
}

View File

@ -6,7 +6,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.method.MethodToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider;
@ -14,11 +13,9 @@ import org.springframework.ai.tool.method.MethodToolCallbackProvider;
public class DouBaoMcpTests { public class DouBaoMcpTests {
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder() .options(OpenAiChatOptions.builder()
.baseUrl(DouBaoChatModel.BASE_URL) .baseUrl(DouBaoChatModel.BASE_URL)
.apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey .apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model("doubao-1-5-lite-32k-250115") // 模型doubao .model("doubao-1-5-lite-32k-250115") // 模型doubao
.temperature(0.7) .temperature(0.7)
.build()) .build())
@ -36,44 +33,47 @@ public class DouBaoMcpTests {
@Test @Test
public void testMcpGetUserInfo() { public void testMcpGetUserInfo() {
// 打印结果 // 打印结果
System.out.println(chatClient.prompt() System.out.println(chatClient.prompt()
.user("目前有哪些工具可以使用") .user("目前有哪些工具可以使用")
.call() .call()
.content()); .content());
System.out.println("===================================="); System.out.println("====================================");
// 打印结果 // 打印结果
System.out.println(chatClient.prompt() System.out.println(chatClient.prompt()
.user("小新的年龄是多少") .user("小新的年龄是多少")
.call() .call()
.content()); .content());
System.out.println("===================================="); System.out.println("====================================");
// 打印结果 // 打印结果
System.out.println(chatClient.prompt() System.out.println(chatClient.prompt()
.user("获取小新的基本信息") .user("获取小新的基本信息")
.call() .call()
.content()); .content());
System.out.println("===================================="); System.out.println("====================================");
// 打印结果 // 打印结果
System.out.println(chatClient.prompt() System.out.println(chatClient.prompt()
.user("小新是什么职业的") .user("小新是什么职业的")
.call() .call()
.content()); .content());
System.out.println("===================================="); System.out.println("====================================");
// 打印结果 // 打印结果
System.out.println(chatClient.prompt() System.out.println(chatClient.prompt()
.user("小新的教育背景") .user("小新的教育背景")
.call() .call()
.content()); .content());
System.out.println("===================================="); System.out.println("====================================");
// 打印结果 // 打印结果
System.out.println(chatClient.prompt() System.out.println(chatClient.prompt()
.user("小新的兴趣爱好是什么") .user("小新的兴趣爱好是什么")
.call() .call()
.content()); .content());
System.out.println("===================================="); System.out.println("====================================");
} }
@ -121,4 +121,4 @@ public class DouBaoMcpTests {
} }
} }

View File

@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import com.fasterxml.jackson.core.type.TypeReference; import tools.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.http.*; import org.springframework.http.*;
@ -45,7 +45,7 @@ public class BpmHttpRequestUtils {
BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class); BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class);
// 1.1 设置请求头 // 1.1 设置请求头
MultiValueMap<String, String> headers = buildHttpHeaders(processInstance, headerParams); HttpHeaders headers = buildHttpHeaders(processInstance, headerParams);
// 1.2 设置请求体 // 1.2 设置请求体
MultiValueMap<String, String> body = buildHttpBody(processInstance, bodyParams); MultiValueMap<String, String> body = buildHttpBody(processInstance, bodyParams);
@ -113,7 +113,7 @@ public class BpmHttpRequestUtils {
} }
public static ResponseEntity<String> sendHttpRequest(String url, public static ResponseEntity<String> sendHttpRequest(String url,
MultiValueMap<String, String> headers, HttpHeaders headers,
Object body, Object body,
RestTemplate restTemplate) { RestTemplate restTemplate) {
HttpEntity<Object> requestEntity = new HttpEntity<>(body, headers); HttpEntity<Object> requestEntity = new HttpEntity<>(body, headers);
@ -128,12 +128,12 @@ public class BpmHttpRequestUtils {
return responseEntity; return responseEntity;
} }
public static MultiValueMap<String, String> buildHttpHeaders(ProcessInstance processInstance, public static HttpHeaders buildHttpHeaders(ProcessInstance processInstance,
List<BpmSimpleModelNodeVO.HttpRequestParam> headerSettings) { List<BpmSimpleModelNodeVO.HttpRequestParam> headerSettings) {
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>(); HttpHeaders headers = new HttpHeaders();
headers.add(HEADER_TENANT_ID, processInstance.getTenantId()); headers.add(HEADER_TENANT_ID, processInstance.getTenantId());
Map<String, Object> processVariables = processInstance.getProcessVariables(); Map<String, Object> processVariables = processInstance.getProcessVariables();
addHttpRequestParam(headers, headerSettings, processVariables); addHttpRequestHeader(headers, headerSettings, processVariables);
return headers; return headers;
} }
@ -172,10 +172,25 @@ public class BpmHttpRequestUtils {
/** /**
* HTTP * HTTP
* *
* @param params HTTP * @param headers HTTP
* @param paramSettings HTTP * @param paramSettings HTTP
* @param processVariables * @param processVariables
*/ */
public static void addHttpRequestHeader(HttpHeaders headers,
List<BpmSimpleModelNodeVO.HttpRequestParam> paramSettings,
Map<String, Object> processVariables) {
if (CollUtil.isEmpty(paramSettings)) {
return;
}
paramSettings.forEach(item -> {
if (item.getType().equals(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())) {
headers.add(item.getKey(), item.getValue());
} else if (item.getType().equals(BpmHttpRequestParamTypeEnum.FROM_FORM.getType())) {
headers.add(item.getKey(), processVariables.get(item.getValue()).toString());
}
});
}
public static void addHttpRequestParam(MultiValueMap<String, String> params, public static void addHttpRequestParam(MultiValueMap<String, String> params,
List<BpmSimpleModelNodeVO.HttpRequestParam> paramSettings, List<BpmSimpleModelNodeVO.HttpRequestParam> paramSettings,
Map<String, Object> processVariables) { Map<String, Object> processVariables) {

View File

@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormFi
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import com.fasterxml.jackson.databind.JsonNode; import tools.jackson.databind.JsonNode;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.common.engine.api.variable.VariableContainer;

View File

@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.bpm.service.task.trigger.BpmTrigger; import cn.iocoder.yudao.module.bpm.service.task.trigger.BpmTrigger;
import com.fasterxml.jackson.core.type.TypeReference; import tools.jackson.core.type.TypeReference;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.bpm.service.task.trigger.BpmTrigger; import cn.iocoder.yudao.module.bpm.service.task.trigger.BpmTrigger;
import com.fasterxml.jackson.core.type.TypeReference; import tools.jackson.core.type.TypeReference;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@ -19,7 +19,9 @@ spring:
spring: spring:
# 数据源配置项 # 数据源配置项
autoconfigure: autoconfigure:
# noinspection SpringBootApplicationYaml
exclude: exclude:
- org.dromara.trans.config.TransServiceConfig # TODO 芋艿easy-trans 兼容 Spring Boot 4 后移除TransServiceConfig 引用了已移除的 RestTemplateBuilder
datasource: datasource:
druid: # Druid 【监控】相关的全局配置 druid: # Druid 【监控】相关的全局配置
web-stat-filter: web-stat-filter:

Some files were not shown because too many files have changed in this diff Show More