feat: 重构模块

This commit is contained in:
ageerle
2025-04-10 17:25:23 +08:00
parent 3be9005f95
commit 2509099146
653 changed files with 1000 additions and 165766 deletions

14
pom.xml
View File

@@ -18,6 +18,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<mysql.version>8.0.33</mysql.version>
<spring-boot.mybatis>3.0.1</spring-boot.mybatis>
<springdoc.version>2.1.0</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
@@ -60,6 +61,7 @@
<weixin-java-miniapp.version>4.5.0</weixin-java-miniapp.version>
<weixin-java-pay.version>4.6.0</weixin-java-pay.version>
<weixin-java-cp.version>4.6.0</weixin-java-cp.version>
<weixin-java-cp.version>4.6.0</weixin-java-cp.version>
</properties>
<profiles>
@@ -96,6 +98,12 @@
<dependencyManagement>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- SpringBoot的依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -315,6 +323,12 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-chat-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-system-api</artifactId>

View File

@@ -2,13 +2,20 @@
<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>
<artifactId>ruoyi-ai</artifactId>
<groupId>org.ruoyi</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common</artifactId>
<packaging>pom</packaging>
<description>
common 通用模块
</description>
<modules>
<module>ruoyi-common-bom</module>
@@ -32,16 +39,6 @@
<module>ruoyi-common-encrypt</module>
<module>ruoyi-common-tenant</module>
<module>ruoyi-common-chat</module>
<module>ruoyi-common-pay</module>
<module>ruoyi-common-wechat</module>
<module>ruoyi-common-live</module>
</modules>
<artifactId>ruoyi-common</artifactId>
<packaging>pom</packaging>
<description>
common 通用模块
</description>
</project>

View File

@@ -159,25 +159,12 @@
<version>${revision}</version>
</dependency>
<!-- 微信模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-wechat</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-chat</artifactId>
<version>${revision}</version>
</dependency>
<!-- 支付模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-pay</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -18,6 +18,10 @@
<properties>
<retrofit2.version>2.9.0</retrofit2.version>
<azure.version>1.0.0-beta.12</azure.version>
<chatglm.version>release-V4-2.3.0</chatglm.version>
<okhttp.version>2.7.5</okhttp.version>
<jtokkit.version>0.5.0</jtokkit.version>
</properties>
<dependencies>
@@ -26,38 +30,22 @@
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<!-- 序列化模块 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-json</artifactId>
</dependency>
<!-- redis模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-ai-openai</artifactId>
<version>1.0.0-beta.12</version>
</dependency>
<dependency>
<groupId>io.github.ollama4j</groupId>
<artifactId>ollama4j</artifactId>
<version>1.0.79</version>
</dependency>
<!-- 序列化模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-json</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-satoken</artifactId>
<version>${azure.version}</version>
</dependency>
<dependency>
@@ -79,13 +67,7 @@
<dependency>
<groupId>com.knuddels</groupId>
<artifactId>jtokkit</artifactId>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.12</version>
<version>${jtokkit.version}</version>
</dependency>
<dependency>
@@ -102,13 +84,14 @@
<dependency>
<groupId>cn.bigmodel.openapi</groupId>
<artifactId>oapi-java-sdk</artifactId>
<version>release-V4-2.3.0</version>
<version>${chatglm.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.7.5</version>
<scope>compile</scope>
<version>${okhttp.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -12,6 +12,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
public class WebSocketProperties {
/**
* 是否开启
*/
private Boolean enabled;
/**

View File

@@ -1,73 +0,0 @@
package org.ruoyi.common.chat.demo;
import cn.hutool.json.JSONUtil;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
/**
* 描述: sse
*
* @author https:www.unfbx.com
* 2023-06-15
*/
@Slf4j
public class ConsoleEventSourceListenerV2 extends EventSourceListener {
@Getter
String args = "";
final CountDownLatch countDownLatch;
public ConsoleEventSourceListenerV2(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void onOpen(EventSource eventSource, Response response) {
log.info("OpenAI建立sse连接...");
}
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
log.info("OpenAI返回数据{}", data);
if (data.equals("[DONE]")) {
log.info("OpenAI返回数据结束了");
countDownLatch.countDown();
return;
}
ChatCompletionResponse chatCompletionResponse = JSONUtil.toBean(data, ChatCompletionResponse.class);
if(Objects.nonNull(chatCompletionResponse.getChoices().get(0).getDelta().getFunctionCall())){
args += chatCompletionResponse.getChoices().get(0).getDelta().getFunctionCall().getArguments();
}
}
@Override
public void onClosed(EventSource eventSource) {
log.info("OpenAI关闭sse连接...");
}
@SneakyThrows
@Override
public void onFailure(EventSource eventSource, Throwable t, Response response) {
if(Objects.isNull(response)){
log.error("OpenAI sse连接异常:{}", t);
eventSource.cancel();
return;
}
ResponseBody body = response.body();
if (Objects.nonNull(body)) {
log.error("OpenAI sse连接异常data{},异常:{}", body.string(), t);
} else {
log.error("OpenAI sse连接异常data{},异常:{}", response, t);
}
eventSource.cancel();
}
}

View File

@@ -1,92 +0,0 @@
package org.ruoyi.common.chat.demo;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
import org.ruoyi.common.chat.entity.chat.Message;
import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction;
import org.ruoyi.common.chat.entity.chat.tool.ToolCalls;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
/**
* 描述: demo测试实现类仅供思路参考
*
* @author https:www.unfbx.com
* 2023-11-12
*/
@Slf4j
public class ConsoleEventSourceListenerV3 extends EventSourceListener {
@Getter
List<ToolCalls> choices = new ArrayList<>();
@Getter
ToolCalls toolCalls = new ToolCalls();
@Getter
ToolCallFunction toolCallFunction = ToolCallFunction.builder().name("").arguments("").build();
final CountDownLatch countDownLatch;
public ConsoleEventSourceListenerV3(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void onOpen(EventSource eventSource, Response response) {
log.info("OpenAI建立sse连接...");
}
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
log.info("OpenAI返回数据{}", data);
if (data.equals("[DONE]")) {
log.info("OpenAI返回数据结束了");
return;
}
ChatCompletionResponse chatCompletionResponse = JSONUtil.toBean(data, ChatCompletionResponse.class);
Message delta = chatCompletionResponse.getChoices().get(0).getDelta();
if (CollectionUtil.isNotEmpty(delta.getToolCalls())) {
choices.addAll(delta.getToolCalls());
}
}
@Override
public void onClosed(EventSource eventSource) {
if(CollectionUtil.isNotEmpty(choices)){
toolCalls.setId(choices.get(0).getId());
toolCalls.setType(choices.get(0).getType());
choices.forEach(e -> {
toolCallFunction.setName(e.getFunction().getName());
toolCallFunction.setArguments(toolCallFunction.getArguments() + e.getFunction().getArguments());
toolCalls.setFunction(toolCallFunction);
});
}
log.info("OpenAI关闭sse连接...");
countDownLatch.countDown();
}
@SneakyThrows
@Override
public void onFailure(EventSource eventSource, Throwable t, Response response) {
if(Objects.isNull(response)){
log.error("OpenAI sse连接异常:{}", t);
eventSource.cancel();
return;
}
ResponseBody body = response.body();
if (Objects.nonNull(body)) {
log.error("OpenAI sse连接异常data{},异常:{}", body.string(), t);
} else {
log.error("OpenAI sse连接异常data{},异常:{}", response, t);
}
eventSource.cancel();
}
}

View File

@@ -1,417 +0,0 @@
package org.ruoyi.common.chat.demo;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.junit.Before;
import org.junit.Test;
import org.ruoyi.common.chat.entity.chat.*;
import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction;
import org.ruoyi.common.chat.entity.chat.tool.ToolCalls;
import org.ruoyi.common.chat.entity.chat.tool.Tools;
import org.ruoyi.common.chat.entity.chat.tool.ToolsFunction;
import org.ruoyi.common.chat.openai.OpenAiClient;
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
import org.ruoyi.common.chat.openai.function.KeyRandomStrategy;
import org.ruoyi.common.chat.openai.interceptor.DynamicKeyOpenAiAuthInterceptor;
import org.ruoyi.common.chat.openai.interceptor.OpenAILogger;
import org.ruoyi.common.chat.openai.interceptor.OpenAiResponseInterceptor;
import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
import org.ruoyi.common.chat.plugin.CmdPlugin;
import org.ruoyi.common.chat.plugin.CmdReq;
import org.ruoyi.common.chat.sse.ConsoleEventSourceListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 描述:
*
* @author ageerle@163.com
* date 2025/3/8
*/
@Slf4j
public class PluginTest {
private OpenAiClient openAiClient;
private OpenAiStreamClient openAiStreamClient;
@Before
public void before() {
//可以为null
// Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890));
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
//千万别再生产或者测试环境打开BODY级别日志
//生产或者测试环境建议设置为这三种级别NONE,BASIC,HEADERS,
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
// .proxy(proxy)
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(new OpenAiResponseInterceptor())
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
openAiClient = OpenAiClient.builder()
//支持多key传入请求时候随机选择
.apiKey(Arrays.asList("sk-xx"))
//自定义key的获取策略默认KeyRandomStrategy
//.keyStrategy(new KeyRandomStrategy())
.keyStrategy(new KeyRandomStrategy())
.okHttpClient(okHttpClient)
//自己做了代理就传代理地址,没有可不不传,(关注公众号回复openai ,获取免费的测试代理地址)
.apiHost("https://api.pandarobot.chat/")
.build();
openAiStreamClient = OpenAiStreamClient.builder()
//支持多key传入请求时候随机选择
.apiKey(Arrays.asList("sk-xx"))
//自定义key的获取策略默认KeyRandomStrategy
.keyStrategy(new KeyRandomStrategy())
.authInterceptor(new DynamicKeyOpenAiAuthInterceptor())
.okHttpClient(okHttpClient)
//自己做了代理就传代理地址,没有可不不传,(关注公众号回复openai ,获取免费的测试代理地址)
.apiHost("https://api.pandarobot.chat/")
.build();
}
@Test
public void chatFunction() {
//模型GPT_3_5_TURBO_16K_0613
Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语并解释下词语对应物品的用途").build();
//属性一
JSONObject wordLength = new JSONObject();
wordLength.put("type", "number");
wordLength.put("description", "词语的长度");
//属性二
JSONObject language = new JSONObject();
language.put("type", "string");
language.put("enum", Arrays.asList("zh", "en"));
language.put("description", "语言类型例如zh代表中文、en代表英语");
//参数
JSONObject properties = new JSONObject();
properties.put("wordLength", wordLength);
properties.put("language", language);
Parameters parameters = Parameters.builder()
.type("object")
.properties(properties)
.required(Collections.singletonList("wordLength")).build();
Functions functions = Functions.builder()
.name("getOneWord")
.description("获取一个指定长度和语言类型的词语")
.parameters(parameters)
.build();
ChatCompletion chatCompletion = ChatCompletion
.builder()
.messages(Collections.singletonList(message))
.functions(Collections.singletonList(functions))
.functionCall("auto")
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
ChatChoice chatChoice = chatCompletionResponse.getChoices().get(0);
log.info("构造的方法值:{}", chatChoice.getMessage().getFunctionCall());
log.info("构造的方法名称:{}", chatChoice.getMessage().getFunctionCall().getName());
log.info("构造的方法参数:{}", chatChoice.getMessage().getFunctionCall().getArguments());
WordParam wordParam = JSONUtil.toBean(chatChoice.getMessage().getFunctionCall().getArguments(), WordParam.class);
String oneWord = getOneWord(wordParam);
FunctionCall functionCall = FunctionCall.builder()
.arguments(chatChoice.getMessage().getFunctionCall().getArguments())
.name("getOneWord")
.build();
Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").functionCall(functionCall).build();
String content
= "{ " +
"\"wordLength\": \"3\", " +
"\"language\": \"zh\", " +
"\"word\": \"" + oneWord + "\"," +
"\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" +
"}";
Message message3 = Message.builder().role(Message.Role.FUNCTION).name("getOneWord").content(content).build();
List<Message> messageList = Arrays.asList(message, message2, message3);
ChatCompletion chatCompletionV2 = ChatCompletion
.builder()
.messages(messageList)
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponseV2 = openAiClient.chatCompletion(chatCompletionV2);
log.info("自定义的方法返回值:{}",chatCompletionResponseV2.getChoices().get(0).getMessage().getContent());
}
@Test
public void plugin() {
CmdPlugin plugin = new CmdPlugin(CmdReq.class);
// 插件名称
plugin.setName("命令行工具");
// 方法名称
plugin.setFunction("openCmd");
// 方法说明
plugin.setDescription("提供一个命令行指令,比如<记事本>,指令使用中文,以function返回结果为准");
PluginAbstract.Arg arg = new PluginAbstract.Arg();
// 参数名称
arg.setName("cmd");
// 参数说明
arg.setDescription("命令行指令");
// 参数类型
arg.setType("string");
arg.setRequired(true);
plugin.setArgs(Collections.singletonList(arg));
Message message2 = Message.builder().role(Message.Role.USER).content("帮我打开计算器,结合上下文判断指令是否执行成功,只用回复成功或者失败").build();
List<Message> messages = new ArrayList<>();
messages.add(message2);
//有四个重载方法,都可以使用
ChatCompletionResponse response = openAiClient.chatCompletionWithPlugin(messages,"gpt-4o-mini",plugin);
log.info("自定义的方法返回值:{}", response.getChoices().get(0).getMessage().getContent());
}
/**
* 自定义返回数据格式
*/
@Test
public void diyReturnDataModelChat() {
Message message = Message.builder().role(Message.Role.USER).content("随机输出10个单词使用json输出").build();
ChatCompletion chatCompletion = ChatCompletion
.builder()
.messages(Collections.singletonList(message))
.responseFormat(ResponseFormat.builder().type(ResponseFormat.Type.JSON_OBJECT.getName()).build())
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
chatCompletionResponse.getChoices().forEach(e -> System.out.println(e.getMessage()));
}
@Test
public void streamPlugin() {
WeatherPlugin plugin = new WeatherPlugin(WeatherReq.class);
plugin.setName("知心天气");
plugin.setFunction("getLocationWeather");
plugin.setDescription("提供一个地址,方法将会获取该地址的天气的实时温度信息。");
PluginAbstract.Arg arg = new PluginAbstract.Arg();
arg.setName("location");
arg.setDescription("地名");
arg.setType("string");
arg.setRequired(true);
plugin.setArgs(Collections.singletonList(arg));
// Message message1 = Message.builder().role(Message.Role.USER).content("秦始皇统一了哪六国。").build();
Message message2 = Message.builder().role(Message.Role.USER).content("获取上海市的天气现在多少度然后再给出3个推荐的户外运动。").build();
List<Message> messages = new ArrayList<>();
// messages.add(message1);
messages.add(message2);
//默认模型GPT_3_5_TURBO_16K_0613
//有四个重载方法,都可以使用
openAiStreamClient.streamChatCompletionWithPlugin(messages, ChatCompletion.Model.GPT_4_1106_PREVIEW.getName(), new ConsoleEventSourceListener(), plugin);
CountDownLatch countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* tools使用示例
*/
@Test
public void toolsChat() {
Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语并解释下词语对应物品的用途").build();
//属性一
JSONObject wordLength = new JSONObject();
wordLength.put("type", "number");
wordLength.put("description", "词语的长度");
//属性二
JSONObject language = new JSONObject();
language.put("type", "string");
language.put("enum", Arrays.asList("zh", "en"));
language.put("description", "语言类型例如zh代表中文、en代表英语");
//参数
JSONObject properties = new JSONObject();
properties.put("wordLength", wordLength);
properties.put("language", language);
Parameters parameters = Parameters.builder()
.type("object")
.properties(properties)
.required(Collections.singletonList("wordLength")).build();
Tools tools = Tools.builder()
.type(Tools.Type.FUNCTION.getName())
.function(ToolsFunction.builder().name("getOneWord").description("获取一个指定长度和语言类型的词语").parameters(parameters).build())
.build();
ChatCompletion chatCompletion = ChatCompletion
.builder()
.messages(Collections.singletonList(message))
.tools(Collections.singletonList(tools))
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
ChatChoice chatChoice = chatCompletionResponse.getChoices().get(0);
log.info("构造的方法值:{}", chatChoice.getMessage().getToolCalls());
ToolCalls openAiReturnToolCalls = chatChoice.getMessage().getToolCalls().get(0);
WordParam wordParam = JSONUtil.toBean(openAiReturnToolCalls.getFunction().getArguments(), WordParam.class);
String oneWord = getOneWord(wordParam);
ToolCallFunction tcf = ToolCallFunction.builder().name("getOneWord").arguments(openAiReturnToolCalls.getFunction().getArguments()).build();
ToolCalls tc = ToolCalls.builder().id(openAiReturnToolCalls.getId()).type(ToolCalls.Type.FUNCTION.getName()).function(tcf).build();
//构造tool call
Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").toolCalls(Collections.singletonList(tc)).build();
String content
= "{ " +
"\"wordLength\": \"3\", " +
"\"language\": \"zh\", " +
"\"word\": \"" + oneWord + "\"," +
"\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" +
"}";
Message message3 = Message.builder().toolCallId(openAiReturnToolCalls.getId()).role(Message.Role.TOOL).name("getOneWord").content(content).build();
List<Message> messageList = Arrays.asList(message, message2, message3);
ChatCompletion chatCompletionV2 = ChatCompletion
.builder()
.messages(messageList)
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponseV2 = openAiClient.chatCompletion(chatCompletionV2);
log.info("自定义的方法返回值:{}", chatCompletionResponseV2.getChoices().get(0).getMessage().getContent());
}
/**
* tools流式输出使用示例
*/
@Test
public void streamToolsChat() {
CountDownLatch countDownLatch = new CountDownLatch(1);
ConsoleEventSourceListenerV3 eventSourceListener = new ConsoleEventSourceListenerV3(countDownLatch);
Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语并解释下词语对应物品的用途").build();
//属性一
JSONObject wordLength = new JSONObject();
wordLength.put("type", "number");
wordLength.put("description", "词语的长度");
//属性二
JSONObject language = new JSONObject();
language.put("type", "string");
language.put("enum", Arrays.asList("zh", "en"));
language.put("description", "语言类型例如zh代表中文、en代表英语");
//参数
JSONObject properties = new JSONObject();
properties.put("wordLength", wordLength);
properties.put("language", language);
Parameters parameters = Parameters.builder()
.type("object")
.properties(properties)
.required(Collections.singletonList("wordLength")).build();
Tools tools = Tools.builder()
.type(Tools.Type.FUNCTION.getName())
.function(ToolsFunction.builder().name("getOneWord").description("获取一个指定长度和语言类型的词语").parameters(parameters).build())
.build();
ChatCompletion chatCompletion = ChatCompletion
.builder()
.messages(Collections.singletonList(message))
.tools(Collections.singletonList(tools))
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
openAiStreamClient.streamChatCompletion(chatCompletion, eventSourceListener);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
ToolCalls openAiReturnToolCalls = eventSourceListener.getToolCalls();
WordParam wordParam = JSONUtil.toBean(openAiReturnToolCalls.getFunction().getArguments(), WordParam.class);
String oneWord = getOneWord(wordParam);
ToolCallFunction tcf = ToolCallFunction.builder().name("getOneWord").arguments(openAiReturnToolCalls.getFunction().getArguments()).build();
ToolCalls tc = ToolCalls.builder().id(openAiReturnToolCalls.getId()).type(ToolCalls.Type.FUNCTION.getName()).function(tcf).build();
//构造tool call
Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").toolCalls(Collections.singletonList(tc)).build();
String content
= "{ " +
"\"wordLength\": \"3\", " +
"\"language\": \"zh\", " +
"\"word\": \"" + oneWord + "\"," +
"\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" +
"}";
Message message3 = Message.builder().toolCallId(openAiReturnToolCalls.getId()).role(Message.Role.TOOL).name("getOneWord").content(content).build();
List<Message> messageList = Arrays.asList(message, message2, message3);
ChatCompletion chatCompletionV2 = ChatCompletion
.builder()
.messages(messageList)
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
CountDownLatch countDownLatch1 = new CountDownLatch(1);
openAiStreamClient.streamChatCompletion(chatCompletionV2, new ConsoleEventSourceListenerV3(countDownLatch));
try {
countDownLatch1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
countDownLatch1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Data
@Builder
static class WordParam {
private int wordLength;
@Builder.Default
private String language = "zh";
}
/**
* 获取一个词语(根据语言和字符长度查询)
* @param wordParam
* @return
*/
public String getOneWord(WordParam wordParam) {
List<String> zh = Arrays.asList("大香蕉", "哈密瓜", "苹果");
List<String> en = Arrays.asList("apple", "banana", "cantaloupe");
if (wordParam.getLanguage().equals("zh")) {
for (String e : zh) {
if (e.length() == wordParam.getWordLength()) {
return e;
}
}
}
if (wordParam.getLanguage().equals("en")) {
for (String e : en) {
if (e.length() == wordParam.getWordLength()) {
return e;
}
}
}
return "西瓜";
}
}

View File

@@ -1,24 +0,0 @@
package org.ruoyi.common.chat.demo;
import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
public class WeatherPlugin extends PluginAbstract<WeatherReq, WeatherResp> {
public WeatherPlugin(Class<?> r) {
super(r);
}
@Override
public WeatherResp func(WeatherReq args) {
WeatherResp weatherResp = new WeatherResp();
weatherResp.setTemp("25到28摄氏度");
weatherResp.setLevel(3);
return weatherResp;
}
@Override
public String content(WeatherResp weatherResp) {
return "当前天气温度:" + weatherResp.getTemp() + ",风力等级:" + weatherResp.getLevel();
}
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.common.chat.demo;
import lombok.Data;
import org.ruoyi.common.chat.openai.plugin.PluginParam;
@Data
public class WeatherReq extends PluginParam {
/**
* 城市
*/
private String location;
}

View File

@@ -1,15 +0,0 @@
package org.ruoyi.common.chat.demo;
import lombok.Data;
@Data
public class WeatherResp {
/**
* 温度
*/
private String temp;
/**
* 风力等级
*/
private Integer level;
}

View File

@@ -1,223 +0,0 @@
package org.ruoyi.common.chat.demo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhipu.oapi.ClientV4;
import com.zhipu.oapi.Constants;
import com.zhipu.oapi.service.v4.tools.*;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import com.zhipu.oapi.service.v4.model.*;
import io.reactivex.Flowable;
import java.util.HashMap;
import java.util.Map;
public class WebSearchToolsTest {
private final static Logger logger = LoggerFactory.getLogger(WebSearchToolsTest.class);
private static final String API_SECRET_KEY = "xx";
private static final ClientV4 client = new ClientV4.Builder(API_SECRET_KEY)
.networkConfig(300, 100, 100, 100, TimeUnit.SECONDS)
.connectionPool(new okhttp3.ConnectionPool(8, 1, TimeUnit.SECONDS))
.build();
private static final ObjectMapper mapper = new ObjectMapper();
// 请自定义自己的业务id
private static final String requestIdTemplate = "mycompany-%d";
@Test
public void test1() throws JsonProcessingException {
// json 转换 ArrayList<SearchChatMessage>
String jsonString = "[\n" +
" {\n" +
" \"content\": \"今天武汉天气怎么样\",\n" +
" \"role\": \"user\"\n" +
" }\n" +
" ]";
ArrayList<SearchChatMessage> messages = new ObjectMapper().readValue(jsonString, new TypeReference<ArrayList<SearchChatMessage>>() {
});
String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
WebSearchParamsRequest chatCompletionRequest = WebSearchParamsRequest.builder()
.model("web-search-pro")
.stream(Boolean.TRUE)
.messages(messages)
.requestId(requestId)
.build();
WebSearchApiResponse webSearchApiResponse = client.webSearchProStreamingInvoke(chatCompletionRequest);
if (webSearchApiResponse.isSuccess()) {
AtomicBoolean isFirst = new AtomicBoolean(true);
List<ChoiceDelta> choices = new ArrayList<>();
AtomicReference<WebSearchPro> lastAccumulator = new AtomicReference<>();
webSearchApiResponse.getFlowable().map(result -> result)
.doOnNext(accumulator -> {
{
if (isFirst.getAndSet(false)) {
logger.info("Response: ");
}
ChoiceDelta delta = accumulator.getChoices().get(0).getDelta();
if (delta != null && delta.getToolCalls() != null) {
logger.info("tool_calls: {}", mapper.writeValueAsString(delta.getToolCalls()));
}
choices.add(delta);
lastAccumulator.set(accumulator);
}
})
.doOnComplete(() -> System.out.println("Stream completed."))
.doOnError(throwable -> System.err.println("Error: " + throwable)) // Handle errors
.blockingSubscribe();// Use blockingSubscribe instead of blockingGet()
WebSearchPro chatMessageAccumulator = lastAccumulator.get();
webSearchApiResponse.setFlowable(null);// 打印前置空
webSearchApiResponse.setData(chatMessageAccumulator);
}
logger.info("model output: {}", mapper.writeValueAsString(webSearchApiResponse));
client.getConfig().getHttpClient().dispatcher().executorService().shutdown();
client.getConfig().getHttpClient().connectionPool().evictAll();
// List all active threads
for (Thread t : Thread.getAllStackTraces().keySet()) {
logger.info("Thread: " + t.getName() + " State: " + t.getState());
}
}
@Test
public void test2() throws JsonProcessingException {
// json 转换 ArrayList<SearchChatMessage>
String jsonString = "[\n" +
" {\n" +
" \"content\": \"今天天气怎么样\",\n" +
" \"role\": \"user\"\n" +
" }\n" +
" ]";
ArrayList<SearchChatMessage> messages = new ObjectMapper().readValue(jsonString, new TypeReference<ArrayList<SearchChatMessage>>() {
});
String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
WebSearchParamsRequest chatCompletionRequest = WebSearchParamsRequest.builder()
.model("web-search-pro")
.stream(Boolean.FALSE)
.messages(messages)
.requestId(requestId)
.build();
WebSearchApiResponse webSearchApiResponse = client.invokeWebSearchPro(chatCompletionRequest);
logger.info("model output: {}", mapper.writeValueAsString(webSearchApiResponse));
}
@Test
public void testFunctionSSE() throws JsonProcessingException {
List<ChatMessage> messages = new ArrayList<>();
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "成都到北京要多久,天气如何");
messages.add(chatMessage);
String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
// 函数调用参数构建部分
List<ChatTool> chatToolList = new ArrayList<>();
ChatTool chatTool = new ChatTool();
chatTool.setType(ChatToolType.FUNCTION.value());
ChatFunctionParameters chatFunctionParameters = new ChatFunctionParameters();
chatFunctionParameters.setType("object");
Map<String, Object> properties = new HashMap<>();
properties.put("location", new HashMap<String, Object>() {{
put("type", "string");
put("description", "城市,如:北京");
}});
properties.put("unit", new HashMap<String, Object>() {{
put("type", "string");
put("enum", new ArrayList<String>() {{
add("celsius");
add("fahrenheit");
}});
}});
chatFunctionParameters.setProperties(properties);
ChatFunction chatFunction = ChatFunction.builder()
.name("get_weather")
.description("Get the current weather of a location")
.parameters(chatFunctionParameters)
.build();
chatTool.setFunction(chatFunction);
chatToolList.add(chatTool);
HashMap<String, Object> extraJson = new HashMap<>();
extraJson.put("temperature", 0.5);
extraJson.put("max_tokens", 50);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model(Constants.ModelChatGLM4)
.stream(Boolean.TRUE)
.messages(messages)
.requestId(requestId)
.tools(chatToolList)
.toolChoice("auto")
.extraJson(extraJson)
.build();
ModelApiResponse sseModelApiResp = client.invokeModelApi(chatCompletionRequest);
if (sseModelApiResp.isSuccess()) {
AtomicBoolean isFirst = new AtomicBoolean(true);
List<Choice> choices = new ArrayList<>();
ChatMessageAccumulator chatMessageAccumulator = mapStreamToAccumulator(sseModelApiResp.getFlowable())
.doOnNext(accumulator -> {
{
if (isFirst.getAndSet(false)) {
logger.info("Response: ");
}
if (accumulator.getDelta() != null && accumulator.getDelta().getTool_calls() != null) {
String jsonString = mapper.writeValueAsString(accumulator.getDelta().getTool_calls());
logger.info("tool_calls: {}", jsonString);
}
if (accumulator.getDelta() != null && accumulator.getDelta().getContent() != null) {
logger.info(accumulator.getDelta().getContent());
}
choices.add(accumulator.getChoice());
}
})
.doOnComplete(System.out::println)
.lastElement()
.blockingGet();
ModelData data = new ModelData();
data.setChoices(choices);
data.setUsage(chatMessageAccumulator.getUsage());
data.setId(chatMessageAccumulator.getId());
data.setCreated(chatMessageAccumulator.getCreated());
data.setRequestId(chatCompletionRequest.getRequestId());
sseModelApiResp.setFlowable(null);// 打印前置空
sseModelApiResp.setData(data);
}
logger.info("model output: {}", mapper.writeValueAsString(sseModelApiResp));
}
public static Flowable<ChatMessageAccumulator> mapStreamToAccumulator(Flowable<ModelData> flowable) {
return flowable.map(chunk -> {
return new ChatMessageAccumulator(chunk.getChoices().get(0).getDelta(), null, chunk.getChoices().get(0), chunk.getUsage(), chunk.getCreated(), chunk.getId());
});
}
}

View File

@@ -1,75 +0,0 @@
package org.ruoyi.common.chat.domain.request;
import org.ruoyi.common.chat.entity.chat.Message;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
/**
* 描述:
*
* @author https:www.unfbx.com
* @sine 2023-04-08
*/
@Data
public class ChatRequest {
private String frequency_penalty;
private String max_tokens;
@NotEmpty(message = "对话消息不能为空")
List<Message> messages;
@NotEmpty(message = "传入的模型不能为空")
private String model;
private String presence_penalty;
private String stream;
private double temperature;
private double top_p = 1;
/**
* 知识库id
*/
private String kid;
private String userId;
/**
* 1 联网搜索
*/
private int chat_type;
/**
* 应用ID
*/
private String appId;
//
//
// /**
// * gpt的默认设置
// */
// private String systemMessage = "";
//
//
//
// private double temperature = 0.2;
//
// /**
// * 上下文的条数
// */
// private Integer contentNumber = 10;
//
// /**
// * 是否携带上下文
// */
// private Boolean usingContext = Boolean.TRUE;
}

View File

@@ -1,33 +0,0 @@
package org.ruoyi.common.chat.domain.request;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
/**
* 描述:
*
* @author https:www.unfbx.com
* @sine 2023-04-08
*/
@Data
public class Dall3Request {
@NotEmpty(message = "传入的模型不能为空")
private String model;
@NotEmpty(message = "提示词不能为空")
private String prompt;
/** 图片大小 */
@NotEmpty(message = "图片大小不能为空")
private String size ;
/** 图片质量 */
@NotEmpty(message = "图片质量不能为空")
private String quality;
/** 图片风格 */
@NotEmpty(message = "图片风格不能为空")
private String style;
}

View File

@@ -2,7 +2,6 @@ package org.ruoyi.common.chat.handler;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.config.LocalCache;
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
@@ -12,7 +11,6 @@ import org.ruoyi.common.chat.listener.WebSocketEventListener;
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
import org.ruoyi.common.chat.utils.WebSocketUtils;
import org.ruoyi.common.core.utils.SpringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;

View File

@@ -1,198 +0,0 @@
package org.ruoyi.common.chat.localModels;
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import org.ruoyi.common.chat.entity.models.LocalModelsSearchRequest;
import org.ruoyi.common.chat.entity.models.LocalModelsSearchResponse;
import org.springframework.stereotype.Service;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@Slf4j
@Service
public class LocalModelsofitClient {
private static final String BASE_URL = "http://127.0.0.1:5000"; // Flask 服务的 URL
private static Retrofit retrofit = null;
// 获取 Retrofit 实例
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
OkHttpClient client = new OkHttpClient.Builder()
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(JacksonConverterFactory.create()) // 使用 Jackson 处理 JSON 转换
.build();
}
return retrofit;
}
/**
* 向 Flask 服务发送文本向量化请求
*
* @param queries 查询文本列表
* @param modelName 模型名称
* @param delimiter 文本分隔符
* @param topK 返回的结果数
* @param blockSize 文本块大小
* @param overlapChars 重叠字符数
* @return 返回计算得到的 Top K 嵌入向量列表
*/
public static List<List<Double>> getTopKEmbeddings(
List<String> queries,
String modelName,
String delimiter,
int topK,
int blockSize,
int overlapChars) {
modelName = (!StringUtils.isEmpty(modelName)) ? modelName : "msmarco-distilbert-base-tas-b"; // 默认模型名称
delimiter = (!StringUtils.isEmpty(delimiter) ) ? delimiter : "."; // 默认分隔符
topK = (topK > 0) ? topK : 3; // 默认返回 3 个结果
blockSize = (blockSize > 0) ? blockSize : 500; // 默认文本块大小为 500
overlapChars = (overlapChars > 0) ? overlapChars : 50; // 默认重叠字符数为 50
// 创建 Retrofit 实例
Retrofit retrofit = getRetrofitInstance();
// 创建 SearchService 接口
SearchService service = retrofit.create(SearchService.class);
// 创建请求对象 LocalModelsSearchRequest
LocalModelsSearchRequest request = new LocalModelsSearchRequest(
queries, // 查询文本列表
modelName, // 模型名称
delimiter, // 文本分隔符
topK, // 返回的结果数
blockSize, // 文本块大小
overlapChars // 重叠字符数
);
final CountDownLatch latch = new CountDownLatch(1); // 创建一个 CountDownLatch
final List<List<Double>>[] topKEmbeddings = new List[]{null}; // 使用数组来存储结果(因为 Java 不支持直接修改 List
// 发起异步请求
service.vectorize(request).enqueue(new Callback<LocalModelsSearchResponse>() {
@Override
public void onResponse(Call<LocalModelsSearchResponse> call, Response<LocalModelsSearchResponse> response) {
if (response.isSuccessful()) {
LocalModelsSearchResponse searchResponse = response.body();
if (searchResponse != null) {
topKEmbeddings[0] = searchResponse.getTopKEmbeddings().get(0); // 获取结果
log.info("Successfully retrieved embeddings");
} else {
log.error("Response body is null");
}
} else {
log.error("Request failed. HTTP error code: " + response.code());
}
latch.countDown(); // 请求完成,减少计数
}
@Override
public void onFailure(Call<LocalModelsSearchResponse> call, Throwable t) {
t.printStackTrace();
log.error("Request failed: ", t);
latch.countDown(); // 请求失败,减少计数
}
});
try {
latch.await(); // 等待请求完成
} catch (InterruptedException e) {
e.printStackTrace();
}
return topKEmbeddings[0]; // 返回结果
}
// public static void main(String[] args) {
// // 示例调用
// List<String> queries = Arrays.asList("What is artificial intelligence?", "AI is transforming industries.");
// String modelName = "msmarco-distilbert-base-tas-b";
// String delimiter = ".";
// int topK = 3;
// int blockSize = 500;
// int overlapChars = 50;
//
// List<List<Double>> topKEmbeddings = getTopKEmbeddings(queries, modelName, delimiter, topK, blockSize, overlapChars);
//
// // 打印结果
// if (topKEmbeddings != null) {
// System.out.println("Top K embeddings: ");
// for (List<Double> embedding : topKEmbeddings) {
// System.out.println(embedding);
// }
// } else {
// System.out.println("No embeddings returned.");
// }
// }
// public static void main(String[] args) {
// // 创建 Retrofit 实例
// Retrofit retrofit = LocalModelsofitClient.getRetrofitInstance();
//
// // 创建 SearchService 接口
// SearchService service = retrofit.create(SearchService.class);
//
// // 创建请求对象 LocalModelsSearchRequest
// LocalModelsSearchRequest request = new LocalModelsSearchRequest(
// Arrays.asList("What is artificial intelligence?", "AI is transforming industries."), // 查询文本列表
// "msmarco-distilbert-base-tas-b", // 模型名称
// ".", // 分隔符
// 3, // 返回的结果数
// 500, // 文本块大小
// 50 // 重叠字符数
// );
//
// // 发起请求
// service.vectorize(request).enqueue(new Callback<LocalModelsSearchResponse>() {
// @Override
// public void onResponse(Call<LocalModelsSearchResponse> call, Response<LocalModelsSearchResponse> response) {
// if (response.isSuccessful()) {
// LocalModelsSearchResponse searchResponse = response.body();
// System.out.println("Response Body: " + response.body()); // Print the whole response body for debugging
//
// if (searchResponse != null) {
// // If the response is not null, process it.
// // Example: Extract the embeddings and print them
// List<List<List<Double>>> topKEmbeddings = searchResponse.getTopKEmbeddings();
// if (topKEmbeddings != null) {
// // Print the Top K embeddings
//
// } else {
// System.err.println("Top K embeddings are null");
// }
//
// // If there is more information you want to process, handle it here
//
// } else {
// System.err.println("Response body is null");
// }
// } else {
// System.err.println("Request failed. HTTP error code: " + response.code());
// log.error("Failed to retrieve data. HTTP error code: " + response.code());
// }
// }
//
// @Override
// public void onFailure(Call<LocalModelsSearchResponse> call, Throwable t) {
// // 请求失败,打印错误
// t.printStackTrace();
// log.error("Request failed: ", t);
// }
// });
// }
}

View File

@@ -1,25 +0,0 @@
package org.ruoyi.common.chat.localModels;
import org.ruoyi.common.chat.entity.models.LocalModelsSearchRequest;
import org.ruoyi.common.chat.entity.models.LocalModelsSearchResponse;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
/**
* @program: RUOYIAI
* @ClassName SearchService
* @description: 请求模型
* @author: hejh
* @create: 2025-03-15 17:27
* @Version 1.0
**/
public interface SearchService {
@POST("/vectorize") // 与 Flask 服务中的路由匹配
Call<LocalModelsSearchResponse> vectorize(@Body LocalModelsSearchRequest request);
}

View File

@@ -1,36 +0,0 @@
package org.ruoyi.common.chat.plugin;
import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
import java.io.IOException;
public class CmdPlugin extends PluginAbstract<CmdReq, CmdResp> {
public CmdPlugin(Class<?> r) {
super(r);
}
@Override
public CmdResp func(CmdReq args) {
try {
if("计算器".equals(args.getCmd())){
Runtime.getRuntime().exec("calc");
}else if("记事本".equals(args.getCmd())){
Runtime.getRuntime().exec("notepad");
}else if("命令行".equals(args.getCmd())){
String [] cmd={"cmd","/C","start copy exel exe2"};
Runtime.getRuntime().exec(cmd);
}
} catch (IOException e) {
throw new RuntimeException("指令执行失败");
}
CmdResp resp = new CmdResp();
resp.setResult(args.getCmd()+"指令执行成功!");
return resp;
}
@Override
public String content(CmdResp resp) {
return resp.getResult();
}
}

View File

@@ -2,31 +2,39 @@ package org.ruoyi.common.chat.request;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import org.ruoyi.common.chat.entity.chat.Content;
import org.ruoyi.common.chat.entity.chat.Message;
import java.util.List;
/**
* 描述:
* 描述:对话请求对象
*
* @author https:www.unfbx.com
* @author ageerle
* @sine 2023-04-08
*/
@Data
public class ChatRequest {
@NotEmpty(message = "传入的模型不能为空")
private String model;
@NotEmpty(message = "对话消息不能为空")
List<Message> messages;
List<Content> imageContent;
@NotEmpty(message = "传入的模型不能为空")
private String model;
/**
* 提示词
*/
private String prompt;
private String userId;
/**
* 是否开启流式对话
*/
private Boolean stream = Boolean.TRUE;
/**
* 是否开启联网搜索(0关闭 1开启)
*/
private Boolean search = Boolean.FALSE;
/**
* 知识库id
@@ -34,13 +42,14 @@ public class ChatRequest {
private String kid;
/**
* gpt的默认设置
* 用户id
*/
private String systemMessage = "";
private String userId;
private double top_p = 1;
private double temperature = 0.2;
/**
* 应用ID
*/
private String appId;
/**
* 上下文的条数
@@ -52,4 +61,5 @@ public class ChatRequest {
*/
private Boolean usingContext = Boolean.TRUE;
}

View File

@@ -28,7 +28,6 @@ public class ConsoleEventSourceListener extends EventSourceListener {
log.info("OpenAI返回数据{}", data);
if ("[DONE]".equals(data)) {
log.info("OpenAI返回数据结束了");
return;
}
}

View File

@@ -8,7 +8,6 @@ import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.jetbrains.annotations.NotNull;
import org.ruoyi.common.chat.constant.OpenAIConst;
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
import org.ruoyi.common.chat.entity.chat.FunctionCall;

View File

@@ -51,7 +51,7 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.24</version>
<version>${hutool.version}</version>
</dependency>
<dependency>
@@ -59,18 +59,6 @@
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>${weixin-java-miniapp.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>${weixin-java-cp.version}</version>
</dependency>
<!-- 自动生成YML配置关联JSON文件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -94,6 +82,11 @@
<artifactId>ip2region</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>${weixin-java-cp.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>

View File

@@ -1,49 +0,0 @@
<!--
~ MIT License
~
~ Copyright (c) 2023 OrdinaryRoad
~
~ Permission is hereby granted, free of charge, to any person obtaining a copy
~ of this software and associated documentation files (the "Software"), to deal
~ in the Software without restriction, including without limitation the rights
~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
~ copies of the Software, and to permit persons to whom the Software is
~ furnished to do so, subject to the following conditions:
~
~ The above copyright notice and this permission notice shall be included in all
~ copies or substantial portions of the Software.
~
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
~ SOFTWARE.
-->
<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>org.ruoyi</groupId>
<artifactId>live-chat-client-commons</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>live-chat-client-commons-base</artifactId>
<name>ordinaryroad-live-chat-client-commons-base</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,32 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.constant;
/**
* @author mjz
* @date 2023/8/26
*/
public class Constants {
}

View File

@@ -1,51 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.exception;
/**
* @author mjz
* @date 2023/9/5
*/
public class BaseException extends RuntimeException {
public BaseException() {
}
public BaseException(String message) {
super(message);
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(Throwable cause) {
super(cause);
}
public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,60 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
/**
* 连接回调
*
* @author mjz
* @date 2023/8/26
*/
public interface IBaseConnectionListener<T> {
/**
* 连接建立成功
*/
default void onConnected(T t) {
// ignore
}
/**
* 连接建立失败
*
* @param t
*/
default void onConnectFailed(T t) {
// ignore
}
/**
* 连接断开
*
* @param t
*/
default void onDisconnected(T t) {
// ignore
}
}

View File

@@ -1,149 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
import tech.ordinaryroad.live.chat.client.commons.base.msg.BaseCmdMsg;
import tech.ordinaryroad.live.chat.client.commons.base.msg.BaseMsg;
import tech.ordinaryroad.live.chat.client.commons.base.msg.ICmdMsg;
import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
/**
* Base消息回调
*
* @author mjz
* @date 2023/8/26
*/
public interface IBaseMsgListener<T, CmdEnum extends Enum<CmdEnum>> {
/**
* 收到消息(所有消息)
*
* @param msg IMsg
*/
default void onMsg(T t, IMsg msg) {
this.onMsg(msg);
}
default void onMsg(IMsg msg) {
// ignore
}
/**
* 收到cmd消息所有cmd
*
* @param cmd CmdEnum
* @param cmdMsg BaseCmdMsg
*/
default void onCmdMsg(T t, CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
this.onCmdMsg(cmd, cmdMsg);
}
default void onCmdMsg(CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
// ignore
}
/**
* 收到其他cmd消息存在Enum但Listener没有对应的回调
*
* @param cmd CmdEnum
* @param cmdMsg BaseCmdMsg
*/
default void onOtherCmdMsg(T t, CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
this.onOtherCmdMsg(cmd, cmdMsg);
}
default void onOtherCmdMsg(CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
// ignore
}
/**
* 收到未知cmd消息
*
* @param cmdString 实际收到的cmd字符串
* @param msg BaseMsg
*/
default void onUnknownCmd(T t, String cmdString, IMsg msg) {
this.onUnknownCmd(cmdString, msg);
}
default void onUnknownCmd(String cmdString, IMsg msg) {
// ignore
}
/**
* 收到cmd消息所有cmd
*
* @param cmd CmdEnum
* @param cmdMsg BaseCmdMsg
* @deprecated use {@link #onCmdMsg(T, Enum, ICmdMsg)}
*/
default void onCmdMsg(T t, CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
this.onCmdMsg(cmd, cmdMsg);
}
/**
* @deprecated use {@link #onCmdMsg(Enum, ICmdMsg)}
*/
default void onCmdMsg(CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
// ignore
}
/**
* 收到其他cmd消息存在Enum但Listener没有对应的回调
*
* @param cmd CmdEnum
* @param cmdMsg BaseCmdMsg
* @deprecated use {@link #onOtherCmdMsg(T, Enum, ICmdMsg)}
*/
default void onOtherCmdMsg(T t, CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
this.onOtherCmdMsg(cmd, cmdMsg);
}
/**
* @deprecated use {@link #onOtherCmdMsg(Enum, ICmdMsg)}
*/
default void onOtherCmdMsg(CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
// ignore
}
/**
* 收到未知cmd消息
*
* @param cmdString 实际收到的cmd字符串
* @param msg BaseMsg
* @deprecated use {@link #onUnknownCmd(T, String, IMsg)}
*/
default void onUnknownCmd(T t, String cmdString, BaseMsg msg) {
this.onUnknownCmd(cmdString, msg);
}
/**
* @deprecated use {@link #onUnknownCmd(String, IMsg)}
*/
default void onUnknownCmd(String cmdString, BaseMsg msg) {
// ignore
}
}

View File

@@ -1,46 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
/**
* 弹幕消息回调
*
* @author mjz
* @since 0.0.6
*/
public interface IDanmuMsgListener<T, DanmuMsg> {
/**
* 收到弹幕
*/
default void onDanmuMsg(T t, DanmuMsg msg) {
this.onDanmuMsg(msg);
}
default void onDanmuMsg(DanmuMsg msg) {
// ignore
}
}

View File

@@ -1,47 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
/**
* 进入房间消息回调
*
* @author mjz
* @date 2023/12/14
* @since 0.0.16
*/
public interface IEnterRoomMsgListener<T, EnterRoomMsg> {
/**
* 用户进入房间
*/
default void onEnterRoomMsg(T t, EnterRoomMsg msg) {
this.onEnterRoomMsg(msg);
}
default void onEnterRoomMsg(EnterRoomMsg msg) {
// ignore
}
}

View File

@@ -1,47 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
/**
* 礼物消息回调
*
* @author mjz
* @since 0.0.8
*/
public interface IGiftMsgListener<T, GiftMsg> {
/**
* 收到礼物
*/
default void onGiftMsg(T t, GiftMsg msg) {
this.onGiftMsg(msg);
}
default void onGiftMsg(GiftMsg msg) {
// ignore
}
}

View File

@@ -1,46 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
/**
* 点赞消息回调
*
* @author mjz
* @since 0.2.0
*/
public interface ILikeMsgListener<T, LikeMsg> {
/**
* 收到点赞
*/
default void onLikeMsg(T t, LikeMsg msg) {
this.onLikeMsg(msg);
}
default void onLikeMsg(LikeMsg msg) {
// ignore
}
}

View File

@@ -1,47 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
/**
* 醒目留言消息回调
*
* @author mjz
* @date 2023/9/24
* @since 0.0.11
*/
public interface ISuperChatMsgListener<T, SuperChatMsg> {
/**
* 收到醒目留言
*/
default void onSuperChatMsg(T t, SuperChatMsg msg) {
this.onSuperChatMsg(msg);
}
default void onSuperChatMsg(SuperChatMsg msg) {
// ignore
}
}

View File

@@ -1,33 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* @author mjz
* @date 2023/8/26
*/
public abstract class BaseCmdMsg<CmdEnum extends Enum<CmdEnum>> extends BaseMsg
implements ICmdMsg<CmdEnum> {
}

View File

@@ -1,74 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
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 tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
import java.util.HashMap;
import java.util.Map;
/**
* @author mjz
* @date 2023/8/26
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public abstract class BaseMsg implements IMsg {
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
/**
* 未知属性都放在这
*/
private final Map<String, JsonNode> unknownProperties = new HashMap<>();
@JsonAnyGetter
public Map<String, JsonNode> getUnknownProperties() {
return unknownProperties;
}
@JsonAnySetter
public void setOther(String key, JsonNode value) {
this.unknownProperties.put(key, value);
}
@Override
public String toString() {
try {
return OBJECT_MAPPER.writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new BaseException(e);
}
}
}

View File

@@ -1,38 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* @author mjz
* @date 2023/10/2
*/
public interface ICmdMsg<CmdEnum extends Enum<CmdEnum>> extends IMsg {
String getCmd();
void setCmd(String cmd);
CmdEnum getCmdEnum();
}

View File

@@ -1,67 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* @author mjz
* @date 2023/9/8
*/
public interface IDanmuMsg extends IMsg {
/**
* 粉丝牌名称
*/
String getBadgeName();
/**
* 粉丝牌等级
*/
byte getBadgeLevel();
/**
* 弹幕发送者id
*/
String getUid();
/**
* 弹幕发送者用户名
*/
String getUsername();
/**
* 弹幕发送者头像地址
*
* @since 0.0.11
*/
default String getUserAvatar() {
return null;
}
/**
* 弹幕内容
*/
String getContent();
}

View File

@@ -1,62 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* 入房消息
*
* @author mjz
* @date 2023/12/26
* @since 0.0.16
*/
public interface IEnterRoomMsg extends IMsg {
/**
* 粉丝牌名称
*/
String getBadgeName();
/**
* 粉丝牌等级
*/
byte getBadgeLevel();
/**
* 用户id
*/
String getUid();
/**
* 用户名
*/
String getUsername();
/**
* 头像地址
*/
default String getUserAvatar() {
return null;
}
}

View File

@@ -1,100 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* @author mjz
* @date 2023/9/8
*/
public interface IGiftMsg extends IMsg {
/**
* 粉丝牌名称
*/
default String getBadgeName() {
return "";
}
/**
* 粉丝牌等级
*/
default byte getBadgeLevel() {
return 0;
}
/**
* 发送方id
*/
String getUid();
/**
* 发送方用户名
*/
String getUsername();
/**
* 发送方头像地址
*
* @since 0.0.11
*/
default String getUserAvatar() {
return null;
}
/**
* 礼物名称
*/
String getGiftName();
/**
* 礼物图像地址
*/
String getGiftImg();
/**
* 礼物id
*/
String getGiftId();
/**
* 礼物数量
*/
int getGiftCount();
/**
* 单个礼物价格
*/
int getGiftPrice();
/**
* 接收方id
*/
String getReceiveUid();
/**
* 接收方用户名
*/
String getReceiveUsername();
}

View File

@@ -1,71 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* @author mjz
* @date 2024/1/31
* @since 0.2.0
*/
public interface ILikeMsg extends IMsg {
/**
* 粉丝牌名称
*/
default String getBadgeName(){
return "";
}
/**
* 粉丝牌等级
*/
default byte getBadgeLevel(){
return 0;
}
/**
* 点赞者id
*/
String getUid();
/**
* 点赞者用户名
*/
String getUsername();
/**
* 点赞者头像地址
*/
default String getUserAvatar() {
return null;
}
/**
* 点赞数
*/
default int getClickCount() {
return 1;
}
}

View File

@@ -1,34 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
import java.io.Serializable;
/**
* @author mjz
* @date 2023/8/26
*/
public interface IMsg extends Serializable {
}

View File

@@ -1,49 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* 醒目留言
*
* @author mjz
* @date 2023/9/22
*/
public interface ISuperChatMsg extends IDanmuMsg {
/**
* 醒目留言持续时间,单位秒
*/
int getDuration();
@Override
default String getBadgeName() {
return "";
}
@Override
default byte getBadgeLevel() {
return 0;
}
}

View File

@@ -1,55 +0,0 @@
<!--
~ MIT License
~
~ Copyright (c) 2023 OrdinaryRoad
~
~ Permission is hereby granted, free of charge, to any person obtaining a copy
~ of this software and associated documentation files (the "Software"), to deal
~ in the Software without restriction, including without limitation the rights
~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
~ copies of the Software, and to permit persons to whom the Software is
~ furnished to do so, subject to the following conditions:
~
~ The above copyright notice and this permission notice shall be included in all
~ copies or substantial portions of the Software.
~
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
~ SOFTWARE.
-->
<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>org.ruoyi</groupId>
<artifactId>live-chat-client-commons</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<artifactId>live-chat-client-commons-client</artifactId>
<name>live-chat-client-commons-client</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-commons-base</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-commons-util</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,200 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.client;
import lombok.Getter;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
import tech.ordinaryroad.live.chat.client.commons.client.config.BaseLiveChatClientConfig;
import tech.ordinaryroad.live.chat.client.commons.client.enums.ClientStatusEnums;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
/**
* @author mjz
* @date 2023/8/26
*/
public abstract class BaseLiveChatClient<
Config extends BaseLiveChatClientConfig,
MsgListener extends IBaseMsgListener<?, ?>
> implements IBaseLiveChatClient<MsgListener> {
private final Config config;
@Getter
private volatile ClientStatusEnums status = ClientStatusEnums.NEW;
protected PropertyChangeSupport statusChangeSupport = new PropertyChangeSupport(status);
protected volatile boolean cancelReconnect = false;
protected final List<MsgListener> msgListeners = Collections.synchronizedList(new ArrayList<>());
protected BaseLiveChatClient(Config config) {
this.config = config;
}
public Config getConfig() {
return config;
}
@Override
public void connect(Runnable success) {
this.connect(success, null);
}
@Override
public void connect() {
this.connect(null, null);
}
@Override
public void disconnect(boolean cancelReconnect) {
this.cancelReconnect = cancelReconnect;
this.disconnect();
}
@Override
public void send(Object msg) {
this.send(msg, null, null);
}
@Override
public void send(Object msg, Runnable success) {
this.send(msg, success, null);
}
@Override
public void send(Object msg, Consumer<Throwable> failed) {
this.send(msg, null, failed);
}
@Override
public void sendDanmu(Object danmu) {
this.sendDanmu(danmu, null, null);
}
@Override
public void sendDanmu(Object danmu, Runnable success) {
this.sendDanmu(danmu, success, null);
}
@Override
public void sendDanmu(Object danmu, Consumer<Throwable> failed) {
this.sendDanmu(danmu, null, failed);
}
@Override
public void clickLike(int count) {
this.clickLike(count, null, null);
}
@Override
public void clickLike(int count, Runnable success) {
this.clickLike(count, success, null);
}
@Override
public void clickLike(int count, Consumer<Throwable> failed) {
this.clickLike(count, null, failed);
}
protected abstract void tryReconnect();
protected abstract String getWebSocketUriString();
/**
* 判断是否处于某个状态,或者处于后续状态
*
* @param status {@link ClientStatusEnums}
* @return false: 还没有到达该状态
*/
protected boolean checkStatus(ClientStatusEnums status) {
return this.status.getCode() >= Objects.requireNonNull(status).getCode();
}
protected void setStatus(ClientStatusEnums status) {
ClientStatusEnums oldStatus = this.status;
if (oldStatus != status) {
this.status = status;
this.statusChangeSupport.firePropertyChange("status", oldStatus, status);
}
}
public void addStatusChangeListener(PropertyChangeListener listener) {
this.statusChangeSupport.addPropertyChangeListener(listener);
}
public void removeStatusChangeListener(PropertyChangeListener listener) {
this.statusChangeSupport.removePropertyChangeListener(listener);
}
@Override
public void destroy() {
for (PropertyChangeListener propertyChangeListener : this.statusChangeSupport.getPropertyChangeListeners()) {
this.statusChangeSupport.removePropertyChangeListener(propertyChangeListener);
}
this.msgListeners.clear();
}
@Override
public boolean addMsgListener(MsgListener msgListener) {
if (msgListener == null) {
return false;
}
return this.msgListeners.add(msgListener);
}
@Override
public boolean addMsgListeners(List<MsgListener> msgListeners) {
if (msgListeners == null || msgListeners.isEmpty()) {
return false;
}
return this.msgListeners.addAll(msgListeners);
}
@Override
public boolean removeMsgListener(MsgListener msgListener) {
if (msgListener == null) {
return false;
}
return this.msgListeners.remove(msgListener);
}
@Override
public boolean removeMsgListeners(List<MsgListener> msgListeners) {
if (msgListeners == null || msgListeners.isEmpty()) {
return false;
}
return this.msgListeners.removeAll(msgListeners);
}
@Override
public void removeAllMsgListeners() {
this.msgListeners.clear();
}
}

View File

@@ -1,135 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.client;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
import java.util.List;
import java.util.function.Consumer;
/**
* @author mjz
* @date 2023/9/5
*/
public interface IBaseLiveChatClient<MsgListener extends IBaseMsgListener<?, ?>> {
void init();
boolean addMsgListener(MsgListener msgListener);
boolean addMsgListeners(List<MsgListener> msgListeners);
boolean removeMsgListener(MsgListener msgListener);
boolean removeMsgListeners(List<MsgListener> msgListeners);
void removeAllMsgListeners();
void connect(Runnable success, Consumer<Throwable> failed);
void connect(Runnable success);
void connect();
/**
* 手动断开连接
*
* @param cancelReconnect 取消本次的自动重连(如果启用自动重连)
*/
void disconnect(boolean cancelReconnect);
void disconnect();
void destroy();
void send(Object msg);
void send(Object msg, Runnable success, Consumer<Throwable> failed);
void send(Object msg, Runnable success);
void send(Object msg, Consumer<Throwable> failed);
/**
* 发送弹幕
*
* @param danmu 弹幕内容
* @since 0.0.6
*/
void sendDanmu(Object danmu);
/**
* 发送弹幕
*
* @param danmu 弹幕内容
* @since 0.0.6
*/
void sendDanmu(Object danmu, Runnable success, Consumer<Throwable> failed);
/**
* 发送弹幕
*
* @param danmu 弹幕内容
* @since 0.0.6
*/
void sendDanmu(Object danmu, Runnable success);
/**
* 发送弹幕
*
* @param danmu 弹幕内容
* @since 0.0.6
*/
void sendDanmu(Object danmu, Consumer<Throwable> failed);
/**
* 为直播间点赞
*
* @since 0.2.0
*/
void clickLike(int count);
/**
* 为直播间点赞
*
* @since 0.2.0
*/
void clickLike(int count, Runnable success, Consumer<Throwable> failed);
/**
* 为直播间点赞
*
* @since 0.2.0
*/
void clickLike(int count, Runnable success);
/**
* 为直播间点赞
*
* @since 0.2.0
*/
void clickLike(int count, Consumer<Throwable> failed);
}

View File

@@ -1,139 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.client.config;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
/**
* 直播间弹幕客户端配置
*
* @author mjz
* @date 2023/8/26
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder(toBuilder = true)
public abstract class BaseLiveChatClientConfig {
protected PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
public static final long DEFAULT_HEARTBEAT_INITIAL_DELAY = 15;
public static final long DEFAULT_HEARTBEAT_PERIOD = 25;
public static final long DEFAULT_MIN_SEND_DANMU_PERIOD = 3000L;
private String websocketUri;
/**
* 浏览器中的Cookie
*/
private String cookie;
/**
* 直播间id
*/
private Object roomId;
/**
* 是否启用自动重连
*/
@Builder.Default
private boolean autoReconnect = Boolean.TRUE;
/**
* 重试延迟时间默认5s后重试
*/
@Builder.Default
private int reconnectDelay = 5;
/**
* 首次发送心跳包的延迟时间(秒)
*/
@Builder.Default
private long heartbeatInitialDelay = DEFAULT_HEARTBEAT_INITIAL_DELAY;
/**
* 心跳包发送周期(秒)
*/
@Builder.Default
private long heartbeatPeriod = DEFAULT_HEARTBEAT_PERIOD;
/**
* 最小发送弹幕时间间隔(毫秒)
*/
@Builder.Default
private long minSendDanmuPeriod = DEFAULT_MIN_SEND_DANMU_PERIOD;
public void setCookie(String cookie) {
String oldValue = this.cookie;
this.cookie = cookie;
this.propertyChangeSupport.firePropertyChange("cookie", oldValue, cookie);
}
public void setRoomId(Object roomId) {
if (!(roomId instanceof Number || roomId instanceof String)) {
throw new BaseException("房间ID仅支持数字或字符串所传参数类型" + roomId.getClass() + "值:" + roomId);
}
Object oldValue = this.roomId;
this.roomId = roomId;
this.propertyChangeSupport.firePropertyChange("roomId", oldValue, roomId);
}
public void setWebsocketUri(String websocketUri) {
String oldValue = this.websocketUri;
this.websocketUri = websocketUri;
this.propertyChangeSupport.firePropertyChange("websocketUri", oldValue, websocketUri);
}
public void setMinSendDanmuPeriod(long minSendDanmuPeriod) {
long oldValue = this.minSendDanmuPeriod;
this.minSendDanmuPeriod = minSendDanmuPeriod;
this.propertyChangeSupport.firePropertyChange("minSendDanmuPeriod", oldValue, minSendDanmuPeriod);
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
this.propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
this.propertyChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
this.propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
this.propertyChangeSupport.removePropertyChangeListener(listener);
}
}

View File

@@ -1,83 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.client.enums;
/**
* @author mjz
* @date 2023/8/26
*/
public enum ClientStatusEnums {
/**
* 新创建
*/
NEW(0),
/**
* 已初始化
*/
INITIALIZED(1),
/**
* 连接中
*/
CONNECTING(100),
/**
* 重新连接中
*/
RECONNECTING(101),
/**
* 已连接
*/
CONNECTED(200),
/**
* 连接失败
*/
CONNECT_FAILED(401),
/**
* 已断开连接
*/
DISCONNECTED(400),
/**
* 已销毁
*/
DESTROYED(-1),
;
public int getCode() {
return code;
}
ClientStatusEnums(int order) {
this.code = order;
}
private final int code;
}

View File

@@ -1,25 +0,0 @@
<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>org.ruoyi</groupId>
<artifactId>live-chat-client-commons</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<artifactId>live-chat-client-commons-util</artifactId>
<name>live-chat-client-commons-util</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,80 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import java.net.HttpCookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* @author mjz
* @date 2023/8/27
*/
public class OrLiveChatCookieUtil {
public static String toString(List<HttpCookie> cookies) {
if (CollUtil.isEmpty(cookies)) {
return StrUtil.EMPTY;
}
return cookies.stream().map(httpCookie -> {
httpCookie.setVersion(0);
return httpCookie.toString();
}).collect(Collectors.joining("; "));
}
public static Map<String, String> parseCookieString(String cookies) {
Map<String, String> map = new HashMap<>();
if (StrUtil.isNotBlank(cookies) && !StrUtil.isNullOrUndefined(cookies)) {
try {
String[] split = cookies.split("; ");
for (String s : split) {
String[] split1 = s.split("=");
map.put(split1[0], split1[1]);
}
} catch (Exception e) {
throw new RuntimeException("cookie解析失败 " + cookies, e);
}
}
return map;
}
public static String getCookieByName(Map<String, String> cookieMap, String name, Supplier<String> supplier) {
String str = MapUtil.getStr(cookieMap, name);
return str == null ? supplier.get() : str;
}
public static String getCookieByName(String cookie, String name, Supplier<String> supplier) {
String str = MapUtil.getStr(parseCookieString(cookie), name);
return str == null ? supplier.get() : str;
}
}

View File

@@ -1,40 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.util;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
/**
* @author mjz
* @date 2023/12/2
*/
public class OrLiveChatNumberUtil extends NumberUtil {
public static long parseLong(Object object){
return NumberUtil.parseLong(StrUtil.toStringOrNull(object));
}
}

View File

@@ -1,50 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package tech.ordinaryroad.live.chat.client.commons.util;
import cn.hutool.core.util.ReflectUtil;
import java.lang.reflect.Method;
/**
* @author mjz
* @date 2023/8/28
*/
public class OrLiveChatReflectUtil extends ReflectUtil {
public static Method getGetterMethod(Class<?> objectClass, String key) {
Method method;
if (key.startsWith("is")) {
method = ReflectUtil.getMethodByNameIgnoreCase(objectClass, key);
if (method == null) {
ReflectUtil.getMethodByNameIgnoreCase(objectClass, "get" + key);
}
} else {
method = ReflectUtil.getMethodByNameIgnoreCase(objectClass, "get" + key);
}
return method;
}
}

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