This commit is contained in:
晴月 2025-04-11 22:03:56 +08:00
commit 12aa452792
12 changed files with 360 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@ -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/

91
pom.xml Normal file
View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hxy</groupId>
<artifactId>demoDeepSeek</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demoDeepSeek</name>
<description>demoDeepSeek</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>3.0.2</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.hxy.demodeepseek.DemoDeepSeekApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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("/"));
}
}

View File

@ -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<String> sendChatRequest(String question) {
Flux<String> response = deepSeekService.sendChatRequest(question);
// Flux 中的元素收集到一个列表中
Mono<List<String>> listMono = response.collectList();
// 将列表中的元素拼接成一个字符串
Mono<String> 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;
}
}

View File

@ -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<Message> 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");
}

View File

@ -0,0 +1,7 @@
package com.hxy.demodeepseek.model;
import lombok.Data;
@Data
public class Message {
private String role;
private String content;
}

View File

@ -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;
}
}

View File

@ -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<String> 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());
}
}

View File

@ -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

View File

@ -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() {
}
}