初始代码

This commit is contained in:
wangmingwei
2026-04-21 17:41:09 +08:00
parent 186ec6683a
commit b686ecac5f
493 changed files with 52349 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.yunzhupaas</groupId>
<artifactId>yunzhupaas-boot-common</artifactId>
<version>5.2.0-RELEASE</version>
</parent>
<artifactId>yunzhupaas-common-ai</artifactId>
<dependencies>
<dependency>
<groupId>com.yunzhupaas</groupId>
<artifactId>yunzhupaas-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.unfbx</groupId>
<artifactId>chatgpt-java</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,203 @@
package com.yunzhupaas.config;
import com.unfbx.chatgpt.OpenAiClient;
import com.unfbx.chatgpt.function.KeyRandomStrategy;
import com.unfbx.chatgpt.function.KeyStrategyFunction;
import com.unfbx.chatgpt.interceptor.DefaultOpenAiAuthInterceptor;
import com.unfbx.chatgpt.interceptor.OpenAiAuthInterceptor;
import com.yunzhupaas.constants.AiConstants;
import com.yunzhupaas.service.OpenAiService;
import com.yunzhupaas.service.impl.DefaultOpenAiServiceImpl;
import com.yunzhupaas.service.impl.DisabledOpenAiServiceImpl;
import com.yunzhupaas.util.StringUtil;
import okhttp3.Authenticator;
import okhttp3.OkHttpClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* AI客户端 自动配置
*
* @author 云筑产品开发平台组
* @copyright 深圳市乐程软件有限公司
* @date 2024/10/9 14:03
*/
@Configuration(proxyBeanMethods = false)
public class AiAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = AiConstants.CONFIGURATION_PREFIX, name = "enabled", havingValue = "false", matchIfMissing = true)
public OpenAiService getDisabledOpenAiService() {
return new DisabledOpenAiServiceImpl();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = AiConstants.CONFIGURATION_PREFIX, name = "enabled", havingValue = "true")
public static class AiEnabledConfiguration {
@Bean
@ConfigurationProperties(prefix = AiConstants.CONFIGURATION_PREFIX)
public AiProperties getAiProperties() {
return new AiProperties();
}
@Bean
public OpenAiService getDefaultOpenAiService(OpenAiClient openAiClient, AiProperties aiProperties) {
return new DefaultOpenAiServiceImpl(openAiClient, aiProperties);
}
@Bean
@ConditionalOnMissingBean
public OpenAiClient getOpenAiClient(
@Qualifier(AiConstants.DEFAULT_HTTP_CLIENT_BEAN_NAME) OkHttpClient okHttpClient,
KeyStrategyFunction<List<String>, String> keyStrategyFunction, OpenAiAuthInterceptor authInterceptor,
AiProperties aiProperties) {
String apiHost = aiProperties.getApiHost();
// 需要以 / 结尾
if (!apiHost.isEmpty() && !apiHost.endsWith("/")) {
apiHost += "/";
}
// 构造openAiClient
return OpenAiClient.builder()
.apiHost(apiHost)
.apiKey(aiProperties.getApiKey())
.keyStrategy(keyStrategyFunction)
.authInterceptor(authInterceptor)
.okHttpClient(okHttpClient)
.build();
}
/*
* @Bean
*
* @ConditionalOnMissingBean
* public OpenAiStreamClient
* getOpenAiStreamClient(@Qualifier(AiConstants.DEFAULT_HTTP_CLIENT_BEAN_NAME)
* OkHttpClient okHttpClient
* , KeyStrategyFunction<List<String>, String> keyStrategyFunction,
* OpenAiAuthInterceptor authInterceptor, AiProperties aiProperties) {
* String apiHost = aiProperties.getApiHost();
* // 需要以 / 结尾
* if(!apiHost.isEmpty() && !apiHost.endsWith("/")){
* apiHost +="/";
* }
* // 构造openAiClient
* return OpenAiStreamClient.builder()
* .apiHost(apiHost)
* .apiKey(aiProperties.getApiKey())
* .keyStrategy(keyStrategyFunction)
* .authInterceptor(authInterceptor)
* .okHttpClient(okHttpClient)
* .build();
* }
*/
/**
* 多 Key 选择策略
*/
@Bean
@ConditionalOnMissingBean
public KeyStrategyFunction<List<String>, String> getDefaultOpenAiKeyStrategyFunction() {
return new KeyRandomStrategy();
}
/**
* 认证拦截器
*
* @see com.unfbx.chatgpt.interceptor.DynamicKeyOpenAiAuthInterceptor 动态移除无效Key
*/
@Bean
@ConditionalOnMissingBean
public OpenAiAuthInterceptor getDefaultOpenAiAuthInterceptor() {
return new DefaultOpenAiAuthInterceptor();
}
/**
* 默认okhttpclient
*/
@Bean(name = AiConstants.DEFAULT_HTTP_CLIENT_BEAN_NAME)
@ConditionalOnMissingBean(name = AiConstants.DEFAULT_HTTP_CLIENT_BEAN_NAME)
public OkHttpClient getDefaultOpenAiOkHttpClient(AiProperties aiProperties) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (aiProperties.getProxy() != null
&& StringUtil.isNotEmpty(aiProperties.getProxy().getHost())
&& aiProperties.getProxy().getPort() != null) {
// 设置代理
builder.proxy(new Proxy(aiProperties.getProxy().getType(),
new InetSocketAddress(aiProperties.getProxy().getHost(), aiProperties.getProxy().getPort())));
// 设置代理认证
if (StringUtil.isNotEmpty(aiProperties.getProxy().getUsername())
&& StringUtil.isNotEmpty(aiProperties.getProxy().getPassword())) {
builder.proxyAuthenticator(Authenticator.JAVA_NET_AUTHENTICATOR);
java.net.Authenticator.setDefault(new java.net.Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
// 返回代理的用户名和密码
return new PasswordAuthentication(aiProperties.getProxy().getUsername(),
aiProperties.getProxy().getPassword().toCharArray());
}
});
}
}
/*
* try {
* // 创建一个信任所有证书的 TrustManager
* final TrustManager[] trustAllCerts = new TrustManager[]{
* new X509TrustManager() {
*
* @Override
* public void checkClientTrusted(X509Certificate[] chain, String authType)
* throws CertificateException {
* }
*
* @Override
* public void checkServerTrusted(X509Certificate[] chain, String authType)
* throws CertificateException {
* }
*
* @Override
* public X509Certificate[] getAcceptedIssuers() {
* return new X509Certificate[]{};
* }
* }
* };
*
* // 安装所有信任管理器
* final SSLContext sslContext = SSLContext.getInstance("SSL");
* sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
*
* // 创建 OkHttpClient 并配置 SSL socket factory 和 hostname verifier
* final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
*
* // 信任所有证书
* builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)
* trustAllCerts[0]);
* builder.hostnameVerifier((hostname, session) -> true);
* } catch (Exception e) {
* throw new RuntimeException(e);
* }
*/
return builder
.callTimeout(aiProperties.getTimeout(), TimeUnit.SECONDS)
.connectTimeout(aiProperties.getTimeout(), TimeUnit.SECONDS)
.writeTimeout(aiProperties.getTimeout(), TimeUnit.SECONDS)
.readTimeout(aiProperties.getTimeout(), TimeUnit.SECONDS)
.build();
}
}
}

View File

@@ -0,0 +1,134 @@
package com.yunzhupaas.config;
import com.yunzhupaas.constants.AiConstants;
import lombok.Data;
import java.time.Duration;
import java.util.List;
/**
* AI 配置
*
* @author 云筑产品开发平台组
* @copyright 深圳市乐程软件有限公司
* @date 2024/10/9 14:03
*/
@Data
public class AiProperties {
/**
* 是否启用
*/
private boolean enabled;
/**
* openai服务器
*/
private String apiHost = AiConstants.OPENAI_HOST;
/**
* openai key
*/
private List<String> apiKey;
/**
* 超时时间
*/
private Long timeout = 30L;
/**
* 每个用户限制时间内的请求次数
*/
private Integer userLimitCount = 1;
/**
* 每个用户限制时间频率
*/
private Duration userLimitTime = Duration.ofSeconds(3);
/**
* 全部请求限制时间内的请求次数
*/
private Integer totalLimitCount = 500;
/**
* 全部请求限制时间频率
*/
private Duration totalLimitTime = Duration.ofMinutes(1L);
/**
* 代理配置
*/
private Proxy proxy = new Proxy();
/**
* 对话配置
*/
private ChatOption chat = new ChatOption();
@Data
public class ChatOption{
/**
* @see AiConstants.Model
*/
private String mode = AiConstants.Model.QWEN_25_3;
/**
* 设置seed参数会使文本生成过程更具有确定性通常用于使模型每次运行的结果一致。
* 在每次模型调用时传入相同的seed值由您指定并保持其他参数不变模型将很可能返回相同的结果。
*/
private Integer seed = 1234;
/**
* 允许模型生成的最大Token数。
*/
private Integer maxTokens = 1500;
/**
* 核采样的概率阈值,用于控制模型生成文本的多样性。
* top_p越高生成的文本更多样。反之生成的文本更确定。
* 由于temperature与top_p均可以控制生成文本的多样性因此建议您只设置其中一个值。
*/
private Double topP = 0.8;
/**
* 采样温度,用于控制模型生成文本的多样性。
* temperature越高生成的文本更多样反之生成的文本更确定。
* 由于temperature与top_p均可以控制生成文本的多样性因此建议您只设置其中一个值。
*/
private Double temperature = 0.85;
private boolean enableSearch = true;
}
@Data
public class Proxy{
/**
* HTTP, SOCKS
*/
private java.net.Proxy.Type type = java.net.Proxy.Type.HTTP;
/**
* 代理域名
*/
private String host;
/**
* 代理端口
*/
private Integer port;
/**
* 代理用户名
*/
private String username;
/**
* 代理密码
*/
private String password;
}
}

View File

@@ -0,0 +1,58 @@
package com.yunzhupaas.constants;
/**
* AI 常量
*
* @author 云筑产品开发平台组
* @copyright 深圳市乐程软件有限公司
* @date 2024/10/9 14:05
*/
public class AiConstants {
public static final String OPENAI_HOST = "https://dashscope.aliyuncs.com/compatible-mode/";
public static final String CONFIGURATION_PREFIX = "spring.cloud.ai.openai";
public static final String DEFAULT_HTTP_CLIENT_BEAN_NAME = "defaultOpenAiHttpClient";
public static final String GEN_MODEL_COMPNENT = "- input - textarea - inputNumber - switch - radio - checkbox - select - datePicker - timePicker - uploadFile - uploadImg - colorPicker - rate - slider - editor - depSelect - posSelect - userSelect - roleSelect - areaSelect - signature - sign - location";
public static final String GEN_MODEL_QUETION = "根据当前业务需求设计相应的表单结构。请仅返回JSON数据不包含其他任何形式的内容。预期结果是一个JSON数组因涉及不同表单需求故可能包含多个表单对象。请确保命名规避数据库与编程保留字。\n"
+
"所需表单应充分利用以下组件列表进行设计: " + GEN_MODEL_COMPNENT + "\n" +
"参考给定的JSON格式属性包含中文名tableTitle)、英文名(tableName)、字段列表(fields)字段列表是一个json数组包含字段英文名(fieldName)、字段中文名(fieldTitle)等;"
+
"创建表单结构,示例如下: [ { \"tableTitle\": \"商城订单\", \"tableName\": \"online_order_form\", \"fields\": [ {\"fieldTitle\": \"订单编号\", \"fieldName\": \"order_id\", \"fieldDbType\": \"varchar\", \"fieldComponent\": \"input\"}, {\"fieldTitle\": \"订单状态\", \"fieldName\": \"order_status\", \"fieldDbType\": \"int\", \"fieldComponent\": \"radio\", \"fieldOptions\":[{\"id\":\"1\", \"fullName\":\"未付款\"},{\"id\":\"2\", \"fullName\":\"已付款\"}]}] }, { \"tableTitle\": \"订单商品明细\", \"tableName\": \"order_item_details\", \"fields\": [ {\"fieldTitle\": \"订单ID外键\", \"fieldName\": \"order_id_fk\", \"fieldDbType\": \"varchar\", \"fieldComponent\": \"input\"}, {\"fieldTitle\": \"商品名称\", \"fieldName\": \"product_name\", \"fieldDbType\": \"varchar\", \"fieldComponent\": \"input\"}, {\"fieldTitle\": \"商品数量\", \"fieldName\": \"quantity\", \"fieldDbType\": \"int\", \"fieldComponent\": \"inputNumber\"}] } ]\n"
+
"请依据实际业务逻辑,合理选择组件与字段类型,确保设计的表单既能满足数据收集需求,又便于用户操作。";
public static final String CHAT_PRE_QUETION = "深圳市乐程软件有限公司下的低代码产品YUNZHUPAAS介绍";
/**
* 模型名称
*
* @see com.unfbx.chatgpt.entity.chat.ChatCompletion.Model
* <a href=
* "https://help.aliyun.com/zh/model-studio/getting-started/models">阿里官方稳定模型列表</a>
*/
public static class Model {
/**
* 通义千问系列效果最好的模型,适合复杂、多步骤的任务。
*/
public static final String QWEN_MAX = "qwen-max";
/**
* 通义千问系列速度最快、成本很低的模型,适合简单任务。
*/
public static final String QWEN_TURBO = "qwen-turbo";
/**
* 通义千问开源版, 可部署参数最高的版本, 云版本收费
*/
public static final String QWEN_25_72 = "qwen2.5-72b-instruct";
/**
* 通义千问开源版, 官方提供接口免费版本, 云版本限时免费
*/
public static final String QWEN_25_3 = "qwen2.5-3b-instruct";
}
}

View File

@@ -0,0 +1,42 @@
package com.yunzhupaas.service;
import com.unfbx.chatgpt.entity.chat.Message;
import com.yunzhupaas.model.ai.AiFormModel;
import java.util.List;
/**
* AI服务工具
* @author 云筑产品开发平台组
* @copyright 深圳市乐程软件有限公司
* @date 2024/10/9 14:38
*/
public interface OpenAiService {
/**
* 简单对话
* @param prompt
*/
String completion(String prompt);
/**
* 连续对话
* @param messages 历史对话内容
*/
String completion(Message... messages);
/**
* 生成表单
* @param businessName 业务名称
* @return
*/
String generatorModelStr(String businessName);
/**
* 生成表单
* @param prompt
* @return
*/
List<AiFormModel> generatorModelVO(String prompt);
}

View File

@@ -0,0 +1,101 @@
package com.yunzhupaas.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.unfbx.chatgpt.OpenAiClient;
import com.unfbx.chatgpt.entity.chat.ChatCompletion;
import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse;
import com.unfbx.chatgpt.entity.chat.Message;
import com.yunzhupaas.config.AiProperties;
import com.yunzhupaas.constant.MsgCode;
import com.yunzhupaas.constants.AiConstants;
import com.yunzhupaas.exception.DataException;
import com.yunzhupaas.model.ai.AiFormModel;
import com.yunzhupaas.service.OpenAiService;
import com.yunzhupaas.util.StringUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* OpenAi 实现类
*
* @author 云筑产品开发平台组
* @copyright 深圳市乐程软件有限公司
* @date 2024/10/9 14:39
*/
@Slf4j
@AllArgsConstructor
public class DefaultOpenAiServiceImpl implements OpenAiService {
private OpenAiClient openAiClient;
private AiProperties aiProperties;
@Override
public String completion(String prompt) {
Message sendMessage = Message.builder().role(Message.Role.USER).content(prompt).build();
ChatCompletion chatCompletion;
if (StringUtil.isNotEmpty(prompt) && prompt.toLowerCase().contains("com.yunzhupaas")) {
Message sysMessage = Message.builder().role(Message.Role.SYSTEM).content(AiConstants.CHAT_PRE_QUETION).build();
chatCompletion = getDefaultChatComletion(sysMessage, sendMessage);
} else {
chatCompletion = getDefaultChatComletion(sendMessage);
}
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
return chatCompletionResponse.getChoices().stream().map(chatChoice -> chatChoice.getMessage().getContent()).collect(Collectors.joining());
}
@Override
public String generatorModelStr(String businessName) {
Message sysMessage = Message.builder().role(Message.Role.SYSTEM).content(AiConstants.GEN_MODEL_QUETION).build();
String userTemplate = "当前业务需求是:";
Message sendMessage = Message.builder().role(Message.Role.USER).content(userTemplate + businessName).build();
ChatCompletion chatCompletion = getDefaultChatComletion(sysMessage, sendMessage);
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
return chatCompletionResponse.getChoices().stream().map(chatChoice -> chatChoice.getMessage().getContent()).collect(Collectors.joining());
}
@Override
public List<AiFormModel> generatorModelVO(String prompt) {
String result = "";
List<AiFormModel> aiFormModels;
try {
result = generatorModelStr(prompt);
int startIndex = result.indexOf("[");
int endIndex = result.lastIndexOf("]");
if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) {
result = result.substring(startIndex, endIndex + 1).trim();
}
aiFormModels = JSON.parseObject(result, new TypeReference<List<AiFormModel>>() {
});
} catch (Exception e) {
log.error("AI表单生成转换失败: {}, {}", result, e.getMessage());
throw new DataException(MsgCode.SYS181.get());
}
return aiFormModels;
}
@Override
public String completion(Message... messages) {
ChatCompletion chatCompletion = getDefaultChatComletion(messages);
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
return chatCompletionResponse.getChoices().stream().map(chatChoice -> chatChoice.getMessage().getContent()).collect(Collectors.joining());
}
private ChatCompletion getDefaultChatComletion(Message... messages) {
AiProperties.ChatOption chatOption = aiProperties.getChat();
return ChatCompletion.builder()
.model(chatOption.getMode())
.temperature(chatOption.getTemperature())
.topP(chatOption.getTopP())
.seed(chatOption.getSeed())
.maxTokens(chatOption.getMaxTokens())
.messages(Arrays.asList(messages)).build();
}
}

View File

@@ -0,0 +1,41 @@
package com.yunzhupaas.service.impl;
import com.unfbx.chatgpt.entity.chat.Message;
import com.yunzhupaas.constant.MsgCode;
import com.yunzhupaas.exception.DataException;
import com.yunzhupaas.model.ai.AiFormModel;
import com.yunzhupaas.service.OpenAiService;
import java.util.List;
/**
* 未启用是空实现
* @author 云筑产品开发平台组
* @copyright 深圳市乐程软件有限公司
* @date 2024/10/15 17:00
*/
public class DisabledOpenAiServiceImpl implements OpenAiService {
@Override
public String completion(String prompt) {
return throwError();
}
@Override
public String completion(Message... messages) {
return throwError();
}
@Override
public String generatorModelStr(String businessName) {
return throwError();
}
@Override
public List<AiFormModel> generatorModelVO(String prompt) {
return throwError();
}
public <T> T throwError(){
throw new DataException(MsgCode.SYS180.get());
}
}

View File

@@ -0,0 +1,51 @@
package com.yunzhupaas.util;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import com.yunzhupaas.config.AiProperties;
import java.util.concurrent.atomic.AtomicInteger;
/**
* AI接口限流
*
* @author 云筑产品开发平台组
* @copyright 深圳市乐程软件有限公司
* @date 2025/3/7 10:00
*/
public class AiLimitUtil {
private static AiProperties aiProperties;
private static TimedCache<String, AtomicInteger> limit = null;
private static final String TOTAL_KEY = "ai_limit_total";
public AiLimitUtil(AiProperties aiProperties) {
AiLimitUtil.aiProperties = aiProperties;
if (limit == null) {
limit = CacheUtil.newTimedCache(aiProperties.getUserLimitTime().toMillis());
// 一分钟清理一次无用数据
limit.schedulePrune(1000L * 60);
}
}
/**
* 是否可以请求
* @param userId
* @return
*/
public static boolean tryAcquire(String userId) {
if (StringUtil.isNotEmpty(userId)) {
// 按用户限制
AtomicInteger userCount = limit.get(userId, false, AtomicInteger::new);
if (userCount.incrementAndGet() > aiProperties.getUserLimitCount()) {
return false;
}
}
// 所有请求限制
AtomicInteger totalCount = limit.get(TOTAL_KEY, false, aiProperties.getTotalLimitTime().toMillis(), AtomicInteger::new);
return totalCount.incrementAndGet() <= aiProperties.getTotalLimitCount();
}
}