mirror of
https://gitee.com/yudaocode/yudao-boot-mini.git
synced 2026-10-06 14:13:14 +08:00
Merge branch 'master-jdk25' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk25
# Conflicts: # yudao-module-ai/pom.xml # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/baichuan/BaiChuanChatModel.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/doubao/DouBaoChatModel.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/grok/GrokChatModel.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/hunyuan/HunYuanChatModel.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowApiConstants.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/xinghuo/XingHuoChatModel.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/yiyan/YiYanChatModel.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/AnthropicChatModelTest.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/BaiChuanChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DeepSeekChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DouBaoChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/GeminiChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/HunYuanChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/MiniMaxChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/MoonshotChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/OpenAIChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/SiliconFlowChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/TongYiChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/XingHuoChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/YiYanChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/ZhiPuAiChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/SiliconFlowImageModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/TongYiImagesModelTest.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/mcp/DouBaoMcpTests.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
This commit is contained in:
@@ -36,7 +36,7 @@
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>2026.05-jdk25-SNAPSHOT</revision>
|
||||
<revision>2026.06-jdk25-SNAPSHOT</revision>
|
||||
<!-- Maven 相关 -->
|
||||
<java.version>25</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
|
||||
+414
-379
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>2026.05-jdk25-SNAPSHOT</revision>
|
||||
<revision>2026.06-jdk25-SNAPSHOT</revision>
|
||||
<flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version>
|
||||
<!-- 统一依赖管理 -->
|
||||
<spring.boot.version>4.1.0</spring.boot.version>
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 阶跃星辰 {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class StepFunChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.stepfun.com";
|
||||
|
||||
public static final String COMPLETE_PATH = "/v1/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "step-3.7-flash";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getOptions() {
|
||||
return deepSeekChatModel.getOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return getOptions();
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* Azure OpenAI 集成测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AzureOpenAIChatModelTests {
|
||||
|
||||
private static final String BASE_URL = SystemUtil.get("AZURE_OPENAI_BASE_URL",
|
||||
"https://xxx.openai.azure.com");
|
||||
private static final String API_KEY = SystemUtil.get("AZURE_OPENAI_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的 Azure OpenAI API Key
|
||||
private static final String DEPLOYMENT_NAME = SystemUtil.get("AZURE_OPENAI_DEPLOYMENT_NAME",
|
||||
"gpt-5.4"); // Azure 上创建的模型部署名称
|
||||
|
||||
private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
|
||||
.options(OpenAiChatOptions.builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.apiKey(API_KEY)
|
||||
.model(DEPLOYMENT_NAME)
|
||||
.microsoftFoundry(true)
|
||||
.deploymentName(DEPLOYMENT_NAME)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link GrokChatModel} 集成测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class GrokChatModelTests {
|
||||
|
||||
private static final String API_KEY = SystemUtil.get("GROK_API_KEY",
|
||||
"sk-xxxx");
|
||||
private static final String MODEL = SystemUtil.get("GROK_MODEL",
|
||||
GrokChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final GrokChatModel chatModel = new GrokChatModel(OpenAiChatModel.builder()
|
||||
.options(OpenAiChatOptions.builder()
|
||||
.baseUrl(GrokChatModel.BASE_URL)
|
||||
.apiKey(API_KEY)
|
||||
.model(MODEL)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build());
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun.StepFunChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link StepFunChatModel} 集成测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class StepFunChatModelTests {
|
||||
|
||||
private static final String API_KEY = SystemUtil.get("STEPFUN_API_KEY",
|
||||
"sk-xxxx");
|
||||
private static final String MODEL = SystemUtil.get("STEPFUN_MODEL",
|
||||
StepFunChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final StepFunChatModel chatModel = new StepFunChatModel(DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(StepFunChatModel.BASE_URL)
|
||||
.completionsPath(StepFunChatModel.COMPLETE_PATH)
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build());
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
// System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
System.out.println(response.getResult() != null ? response.getResult().getOutput() : null);
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -154,13 +154,11 @@ spring:
|
||||
host: 127.0.0.1
|
||||
port: 19530
|
||||
openai: # OpenAI 官方
|
||||
api-key: sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17
|
||||
base-url: https://api.gptsapi.net
|
||||
azure: # OpenAI 微软
|
||||
openai:
|
||||
endpoint: https://eastusprejade.openai.azure.com
|
||||
base-url: ${OPENAI_BASE_URL}
|
||||
api-key: ${OPENAI_API_KEY}
|
||||
anthropic: # Anthropic Claude
|
||||
api-key: sk-muubv7cXeLw0Etgs743f365cD5Ea44429946Fa7e672d8942
|
||||
base-url: ${ANTHROPIC_BASE_URL}
|
||||
api-key: ${ANTHROPIC_API_KEY}
|
||||
ollama:
|
||||
base-url: http://127.0.0.1:11434
|
||||
chat:
|
||||
@@ -168,12 +166,17 @@ spring:
|
||||
stabilityai:
|
||||
api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx
|
||||
dashscope: # 通义千问
|
||||
api-key: sk-47aa124781be4bfb95244cc62f6xxxx
|
||||
api-key: ${DASHSCOPE_API_KEY}
|
||||
deepseek: # DeepSeek
|
||||
api-key: sk-e94db327cc7d457d99a8de8810fc6b12
|
||||
api-key: ${DEEPSEEK_API_KEY}
|
||||
chat:
|
||||
options:
|
||||
model: deepseek-chat
|
||||
model: deepseek-v4-flash
|
||||
google:
|
||||
genai: # 谷歌 Gemini
|
||||
api-key: ${GEMINI_API_KEY}
|
||||
chat:
|
||||
model: gemini-2.5-flash
|
||||
model:
|
||||
rerank: false # 是否开启“通义千问”的 Rerank 模型,填写 dashscope 开启
|
||||
mcp:
|
||||
@@ -196,47 +199,50 @@ spring:
|
||||
|
||||
yudao:
|
||||
ai:
|
||||
gemini: # 谷歌 Gemini
|
||||
enable: true
|
||||
api-key: AIzaSyAVoBxgoFvvte820vEQMma2LKBnC98bqMQ
|
||||
model: gemini-2.5-flash
|
||||
doubao: # 字节豆包
|
||||
enable: true
|
||||
api-key: 5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272
|
||||
model: doubao-1-5-lite-32k-250115
|
||||
api-key: ${DOUBAO_API_KEY}
|
||||
model: doubao-seed-2-1-turbo-260628
|
||||
hunyuan: # 腾讯混元
|
||||
enable: true
|
||||
api-key: sk-abc
|
||||
model: hunyuan-turbo
|
||||
api-key: ${HUNYUAN_API_KEY}
|
||||
model: hy3-preview
|
||||
siliconflow: # 硅基流动
|
||||
enable: true
|
||||
api-key: sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz
|
||||
model: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
|
||||
api-key: ${SILICONFLOW_API_KEY}
|
||||
model: deepseek-ai/DeepSeek-V4-Pro
|
||||
xinghuo: # 讯飞星火
|
||||
enable: true
|
||||
appKey: 75b161ed2aef4719b275d6e7f2a4d4cd
|
||||
secretKey: YWYxYWI2MTA4ODI2NGZlYTQyNjAzZTcz
|
||||
model: x1
|
||||
api-key: ${XINGHUO_API_KEY}
|
||||
model: x2-flash
|
||||
baichuan: # 百川智能
|
||||
enable: true
|
||||
api-key: sk-abc
|
||||
model: Baichuan4-Turbo
|
||||
api-key: ${BAICHUAN_API_KEY}
|
||||
model: Baichuan-M3
|
||||
yiyan: # 文心一言
|
||||
enable: false
|
||||
api-key: bce-v3/xxxx
|
||||
model: ernie-4.0-8k-latest
|
||||
api-key: ${YIYAN_API_KEY}
|
||||
model: ernie-5.1
|
||||
zhipu: # 智谱 AI(GLM)
|
||||
enable: false
|
||||
api-key: sk-abc
|
||||
api-key: ${ZHIPU_API_KEY}
|
||||
model: glm-5.2
|
||||
minimax: # MiniMax
|
||||
enable: false
|
||||
api-key: xxxx
|
||||
api-key: ${MINIMAX_API_KEY}
|
||||
model: MiniMax-M3
|
||||
moonshot: # 月之暗面(KIMI)
|
||||
enable: false
|
||||
api-key: sk-abc
|
||||
api-key: ${MOONSHOT_API_KEY}
|
||||
model: kimi-k2.6
|
||||
stepfun: # 阶跃星辰
|
||||
enable: false
|
||||
api-key: ${STEPFUN_API_KEY}
|
||||
model: step-3.7-flash
|
||||
grok: # Grok
|
||||
enable: false
|
||||
api-key: ${GROK_API_KEY}
|
||||
model: grok-4.3
|
||||
midjourney:
|
||||
enable: true
|
||||
# base-url: https://api.holdai.top/mj-relax/mj
|
||||
|
||||
Reference in New Issue
Block a user