commit 12aa452792f0e97ff521636027ea770a8a9909f4 Author: Sunny-Moom Date: Fri Apr 11 22:03:56 2025 +0800 x diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..836ab16 --- /dev/null +++ b/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + com.hxy + demoDeepSeek + 0.0.1-SNAPSHOT + demoDeepSeek + demoDeepSeek + + 17 + UTF-8 + UTF-8 + 3.0.2 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.2.0 + + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.boot + spring-boot-starter-webflux + + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + com.hxy.demodeepseek.DemoDeepSeekApplication + true + + + + repackage + + repackage + + + + + + + + diff --git a/src/main/java/com/hxy/demodeepseek/DemoDeepSeekApplication.java b/src/main/java/com/hxy/demodeepseek/DemoDeepSeekApplication.java new file mode 100644 index 0000000..cfc0dfa --- /dev/null +++ b/src/main/java/com/hxy/demodeepseek/DemoDeepSeekApplication.java @@ -0,0 +1,12 @@ +package com.hxy.demodeepseek; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +public class DemoDeepSeekApplication { + public static void main(String[] args) { + SpringApplication.run(DemoDeepSeekApplication.class, args); + } +} diff --git a/src/main/java/com/hxy/demodeepseek/config/DeepSeekConfig.java b/src/main/java/com/hxy/demodeepseek/config/DeepSeekConfig.java new file mode 100644 index 0000000..a312e0a --- /dev/null +++ b/src/main/java/com/hxy/demodeepseek/config/DeepSeekConfig.java @@ -0,0 +1,13 @@ +package com.hxy.demodeepseek.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "deepseek") +@Data +public class DeepSeekConfig { + private String apiUrl; + private String apiKey; +} diff --git a/src/main/java/com/hxy/demodeepseek/config/OpenAPIConfig.java b/src/main/java/com/hxy/demodeepseek/config/OpenAPIConfig.java new file mode 100644 index 0000000..3175a53 --- /dev/null +++ b/src/main/java/com/hxy/demodeepseek/config/OpenAPIConfig.java @@ -0,0 +1,25 @@ +package com.hxy.demodeepseek.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import io.swagger.v3.oas.models.ExternalDocumentation; +import org.springframework.context.annotation.Configuration; + +/** + * http://localhost:8080/swagger-ui/index.html + */ +@Configuration +public class OpenAPIConfig { + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .info(new Info() + .title("接口文档标题") + .description("SpringBoot3 集成 Swagger3接口文档") + .version("v1")) + .externalDocs(new ExternalDocumentation() + .description("项目API文档") + .url("/")); + } +} diff --git a/src/main/java/com/hxy/demodeepseek/controller/DeepSeekController.java b/src/main/java/com/hxy/demodeepseek/controller/DeepSeekController.java new file mode 100644 index 0000000..5a21581 --- /dev/null +++ b/src/main/java/com/hxy/demodeepseek/controller/DeepSeekController.java @@ -0,0 +1,35 @@ +package com.hxy.demodeepseek.controller; +import com.hxy.demodeepseek.service.DeepSeekService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; + +@RestController +public class DeepSeekController { + @Autowired + private DeepSeekService deepSeekService; + + @GetMapping("/chat") + public Mono sendChatRequest(String question) { + Flux response = deepSeekService.sendChatRequest(question); + // 将 Flux 中的元素收集到一个列表中 + Mono> listMono = response.collectList(); + + // 将列表中的元素拼接成一个字符串 + Mono resultMono = listMono.map(list -> { + StringBuilder sb = new StringBuilder(); + for (String str : list) { + sb.append(str); + } + System.out.println("resultMono = " + sb); + return sb.toString(); + }); + + return resultMono; + } +} + diff --git a/src/main/java/com/hxy/demodeepseek/model/ChatRequest.java b/src/main/java/com/hxy/demodeepseek/model/ChatRequest.java new file mode 100644 index 0000000..b0e61a9 --- /dev/null +++ b/src/main/java/com/hxy/demodeepseek/model/ChatRequest.java @@ -0,0 +1,17 @@ +package com.hxy.demodeepseek.model; + +import lombok.Data; +import java.util.List; +@Data +public class ChatRequest { + private String model = "deepseek-ai/DeepSeek-V3"; + private List messages; + private boolean stream = true; + private int max_tokens = 2048; + private double temperature = 0.7; + private double top_p = 0.7; + private int top_k = 50; + private double frequency_penalty = 0.5; + private int n = 1; + private ResponseFormat response_format = new ResponseFormat("text"); +} diff --git a/src/main/java/com/hxy/demodeepseek/model/Message.java b/src/main/java/com/hxy/demodeepseek/model/Message.java new file mode 100644 index 0000000..b1f2d6e --- /dev/null +++ b/src/main/java/com/hxy/demodeepseek/model/Message.java @@ -0,0 +1,7 @@ +package com.hxy.demodeepseek.model; +import lombok.Data; +@Data +public class Message { + private String role; + private String content; +} diff --git a/src/main/java/com/hxy/demodeepseek/model/ResponseFormat.java b/src/main/java/com/hxy/demodeepseek/model/ResponseFormat.java new file mode 100644 index 0000000..3669923 --- /dev/null +++ b/src/main/java/com/hxy/demodeepseek/model/ResponseFormat.java @@ -0,0 +1,17 @@ +package com.hxy.demodeepseek.model; + +public class ResponseFormat { + private String type; + + public ResponseFormat(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/src/main/java/com/hxy/demodeepseek/service/DeepSeekService.java b/src/main/java/com/hxy/demodeepseek/service/DeepSeekService.java new file mode 100644 index 0000000..b869239 --- /dev/null +++ b/src/main/java/com/hxy/demodeepseek/service/DeepSeekService.java @@ -0,0 +1,87 @@ +package com.hxy.demodeepseek.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.JsonNode; +import com.hxy.demodeepseek.config.DeepSeekConfig; +import com.hxy.demodeepseek.model.ChatRequest; +import com.hxy.demodeepseek.model.Message; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; +import reactor.netty.http.client.PrematureCloseException; +import reactor.util.retry.Retry; + +import java.time.Duration; +import java.util.Collections; +import java.util.concurrent.TimeoutException; + + +@Service +public class DeepSeekService { + @Autowired + private DeepSeekConfig config; + @Autowired + private WebClient.Builder webClientBuilder; + private final ObjectMapper objectMapper = new ObjectMapper(); + + + public Flux sendChatRequest(String question) { + ChatRequest request = new ChatRequest(); + Message userMessage = new Message(); + userMessage.setRole("user"); + userMessage.setContent(question); + request.setMessages(Collections.singletonList(userMessage)); + + System.out.println("发送请求到: "+ config.getApiUrl()); + + return webClientBuilder.build() + .post() + .uri(config.getApiUrl()) + .header("Authorization", "Bearer " + config.getApiKey()) + .header("Content-Type", "application/json") + .bodyValue(request) + .retrieve() + .bodyToFlux(String.class) + .timeout(Duration.ofSeconds(300)) + .retryWhen(Retry.backoff(3, Duration.ofSeconds(2)) + .filter(throwable -> + throwable instanceof PrematureCloseException + || throwable instanceof TimeoutException + || throwable instanceof RuntimeException) + .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> { + System.out.println("重试次数已用完,最后一次错误: "+ retrySignal.failure().getMessage()); + return new RuntimeException("服务暂时不可用,请稍后重试"); + })) + .onErrorResume(e -> { + System.out.println("请求处理错误: "+ e.getMessage()); + return Flux.just("抱歉,服务器处理请求时发生错误: " + e.getMessage()); + }) + .map(response -> { + try { + if ("[DONE]".equals(response)) { + return "\n"; + } + + JsonNode jsonNode = objectMapper.readTree(response); + JsonNode choices = jsonNode.get("choices"); + if (choices != null && choices.isArray() && choices.size() > 0) { + JsonNode choice = choices.get(0); + JsonNode delta = choice.get("delta"); + if (delta != null && delta.has("content")) { + String content = delta.get("content").asText(); + return content != null ? content : ""; + } + } + return ""; + } catch (Exception e) { + System.out.println("解析响应时出错: "+ e.getMessage()); + System.out.println("原始响应: "+ response); + return ""; + } + }) + .filter(content -> !content.isEmpty()); + } + +} + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..58bf2ae --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,10 @@ +spring: + application: + name: demoDeepSeek + mvc: + async: + request-timeout: 360000 +deepseek: + api-url: https://api.siliconflow.cn/v1/chat/completions + api-key: sk-ldfhfwuwgskxzrskydasvjvqemctwonwuvqqfljaoomcjoro + diff --git a/src/test/java/com/hxy/demodeepseek/DemoDeepSeekApplicationTests.java b/src/test/java/com/hxy/demodeepseek/DemoDeepSeekApplicationTests.java new file mode 100644 index 0000000..8b1e313 --- /dev/null +++ b/src/test/java/com/hxy/demodeepseek/DemoDeepSeekApplicationTests.java @@ -0,0 +1,13 @@ +package com.hxy.demodeepseek; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DemoDeepSeekApplicationTests { + + @Test + void contextLoads() { + } + +}