1.直接上代码-后端:
@RestController
@CrossOrigin(origins = "*")
public class DeepSeekController {
private static final String API_URL = "https://api.deepseek.com/v1/chat/completions";
private final ObjectMapper objectMapper = new ObjectMapper();
@GetMapping(value = "/stream", produces = "text/event-stream")
public SseEmitter streamChat(@RequestParam String message) {
SseEmitter emitter = new SseEmitter(60_000L);
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(200, TimeUnit.SECONDS)
.readTimeout(200, TimeUnit.SECONDS)
.build();
String jsonBody = buildRequestJson(message);
Request request = new Request.Builder()
.url(API_URL)
.header("Authorization", "XXXXXXX")
.post(RequestBody.create(jsonBody, MediaType.get("application/json")))
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(response.body().byteStream()))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("data: ")) {
String content = parseContent(line.substring(6));
emitter.send(SseEmitter.event().data(content));
}
}
emitter.complete();
}
}
@Override
public void onFailure(Call call, IOException e) {
emitter.completeWithError(e);
}
});
return emitter;
}
private String parseContent(String jsonLine) {
try {
System.out.println(jsonLine);
JsonNode node = objectMapper.readTree(jsonLine);
return node.path("choices").get(0).path("delta").path("content").asText();
} catch (Exception e) {
return "[解析错误]";
}
}
private String buildRequestJson(String message) {
return String.format("{\"model\":\"deepseek-chat\",\"stream\":true,\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}]}",
message.replace("\"", "\\\""));
}
}
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.3</version>
</dependency>
2.前端
<!DOCTYPE html>
<meta charset="UTF-8"/>
<html>
<head>
<title>DeepSeek流式对话</title>
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css" rel="stylesheet">
<style>
.chat-container {
max-width: 800px;
margin: 20px auto;
background: #F9FAFB;
border-radius: 12px;
box-shadow: 0 8px 30px rgba(0,0,0,0.1);
}
.chat-messages {
height: 70vh;
padding: 20px;
overflow-y: auto;
}
.message {
margin: 12px 0;
padding: 14px 20px;
border-radius: 12px;
max-width: 80%;
animation: fadeIn 0.3s ease;
}
.user-message {
background: #E3F2FD;
margin-left: auto;
border-bottom-right-radius: 4px;
}
.ai-message {
background: #FFFFFF;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
border-bottom-left-radius: 4px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-messages" id="chatMessages">
<!-- 消息动态插入 -->
</div>
<div class="input-area">
<input type="text" id="inputField" placeholder="输入您的问题...">
<button id="sendBtn">
<i class="mdi mdi-send"></i>
</button>
</div>
</div>
<script>
document.getElementById('sendBtn').addEventListener('click', () => {
const input = document.getElementById('inputField').value.trim();
if (!input) return;
document.getElementById('inputField').value = '';
appendMessage(input, 'user');
const aiMessageDiv = appendMessage('', 'ai');
const eventSource = new EventSource(`/stream?message=${encodeURIComponent(input)}`);
eventSource.onmessage = (e) => {
aiMessageDiv.textContent += e.data;
aiMessageDiv.scrollIntoView({ behavior: 'smooth' });
};
eventSource.onerror = () => {
eventSource.close();
aiMessageDiv.textContent += '(对话结束)';
};
});
function appendMessage(content, type) {
const container = document.getElementById('chatMessages');
const div = document.createElement('div');
div.className = `message ${type}-message`;
div.textContent = content;
container.appendChild(div);
return div;
}
</script>
</body>
</html>