先到阿里云控制台注册token
后端代码
python Django
from django.http import JsonResponse
from openai import OpenAI
import json
from django.http import StreamingHttpResponse
def admin_only_api(request):
if request.method == 'GET':
content = request.GET.get('content')
client = OpenAI(
api_key="申请的token",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
def event_stream():
reasoning_content = ""
answer_content = ""
is_answering = False
completion = client.chat.completions.create(
model="deepseek-r1",
messages=[
{"role": "user", "content": content}
],
stream=True
)
for chunk in completion:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
if hasattr(delta, 'reasoning_content') and delta.reasoning_content is not None:
reasoning_content += delta.reasoning_content
data = {'code': 200, 'msg': '', 'msg1': delta.reasoning_content}
yield f"data: {json.dumps(data)}\n\n"
else:
if delta.content != "" and not is_answering:
is_answering = True
if delta.content:
answer_content += delta.content
print(delta.content)
data = {'code': 200, 'msg': delta.content, 'msg1': ''}
yield f"data: {json.dumps(data)}\n\n"
response = StreamingHttpResponse(event_stream(), content_type='text/event-stream')
response['Cache-Control'] = 'no-cache'
# 跨域
response['Access-Control-Allow-Origin'] = '设置成你的前端地址' # 设置允许的来源
return response
else:
response = JsonResponse({"error": "Method not allowed"}, status=405)
# 跨域
response['Access-Control-Allow-Origin'] = '设置成你的前端地址' # 设置允许的来源
return response
java Springboot
- Maven依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
<version>2.18.2</version>
</dependency>
- 代码
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Author reisen7
* Date 2025/4/9 1:12
* Description
*/
@RestController
@RequestMapping("/deepSeek")
public class DeepSeekController {
private static final Logger logger = LoggerFactory.getLogger(DeepSeekController.class);
private final ExecutorService executor = Executors.newCachedThreadPool();
private static GenerationParam buildGenerationParam(Message userMsg) {
return GenerationParam.builder()
.apiKey("sk-xxxxxx")
.model("deepseek-r1")
.messages(Arrays.asList(userMsg))
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
.incrementalOutput(true)
.build();
}
@GetMapping(value = "/query", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter getBotContent(@RequestParam String question) {
SseEmitter emitter = new SseEmitter(60_000L); // 60秒超时
executor.execute(() -> {
try {
// 先发送思考中状态
Map<String, Object> thinkingResponse = new HashMap<>();
thinkingResponse.put("code", 200);
thinkingResponse.put("msg1", "正在分析您的问题...");
emitter.send(SseEmitter.event().data(thinkingResponse));
Generation gen = new Generation();
Message userMsg = Message.builder()
.role(Role.USER.getValue())
.content(question)
.build();
GenerationParam param = buildGenerationParam(userMsg);
// 处理流式响应
gen.streamCall(param).blockingForEach(message -> {
Map<String, Object> response = new HashMap<>();
response.put("code", 200);
String reasoning = message.getOutput().getChoices().get(0).getMessage().getReasoningContent();
String content = message.getOutput().getChoices().get(0).getMessage().getContent();
if (reasoning != null && !reasoning.isEmpty()) {
response.put("msg1", reasoning);
emitter.send(SseEmitter.event().data(response));
}
if (content != null && !content.isEmpty()) {
response.remove("msg1");
response.put("msg", content);
emitter.send(SseEmitter.event().data(response));
}
});
emitter.complete();
} catch (Exception e) {
logger.error("Error in streaming response", e);
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("code", 500);
errorResponse.put("msg", "处理请求时发生错误: " + e.getMessage());
try {
emitter.send(SseEmitter.event().data(errorResponse));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
emitter.completeWithError(e);
}
});
return emitter;
}
}
前端仿Deepseek页面
这只是实现了弹窗的内容,需要引入到使用的页面中,这里不再补充可以参考之前的代码
https://blog.youkuaiyun.com/qq_46150074/article/details/144575355?spm=1001.2014.3001.5502
<template>
<div class="model-bg" v-show="show" @mousemove="modelMove" @mouseup="cancelMove">
<div class="model-container">
<div class="model-header" @mousedown="setStartingPoint">
{{ title }}
</div>
<div class="model-main" ref="box">
<div v-for="(item, i) in list" :key="i" :class="item.id == 2 ? 'atalk' : 'btalk'">
<span>{{ item.content }}</span>
</div>
</div>
<div style="width: 100%; display: flex;"><input type="text" v-model="wordone" class="inputword" @keyup.enter="sendmsg">
<el-button type="primary" :disabled="isButtonDisabled" round @click="sendmsg"
class="btnsend">发送</el-button>
</div>
<div class="model-footer">
<el-button round @click="cancel">关闭</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false
},
title: {
type: String,
default: 'deepseek'
},
},
data() {
return {
x: 0,
y: 0,
node: null,
isCanMove: false,
isButtonDisabled: false,
list: [],
wordone: '',
wordtow: '',
eventSource: null,
currentIndex: 0, // 当前显示的字符索引
currentMessage: null, // 当前正在显示的消息对象
typingTimer: null // 逐字显示定时器
}
},
components: {
},
mounted() {
this.node = document.querySelector('.model-container')
},
methods: {
sendmsg() {
if (this.isButtonDisabled == false) {
// 重置相关状态变量
if (this.typingTimer) {
clearInterval(this.typingTimer);
}
this.currentIndex = 0;
this.currentMessage = null;
this.typingTimer = null;
this.list.push({ id: 1, name: 'sigtuna', content: this.wordone });
this.scrollToBottom();
this.getBotContent();
} else {
return;
}
this.isButtonDisabled = true;
this.wordone = '';
},
getBotContent() {
// 添加思考中的提示
this.list.push({ id: 2, name: 'kanade', content: '正在思考中...', isThinking: true });
// 改成后端的接口
this.eventSource = new EventSource(`/api/ai?content=${this.wordone}`);
this.eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.code === 200) {
if (data.msg1) {
// 处理思考问题消息
if (!this.currentMessage || this.currentMessage.id !== 2) {
// 如果当前没有正在显示的思考问题消息,或者当前消息不是思考问题消息,或者当前思考问题消息已结束
this.currentMessage = { id: 2, name: 'kanade', content: '思考问题:' + data.msg1, isThinking: true };
this.list.push(this.currentMessage);
this.currentIndex = 0;
} else {
// 如果当前正在显示思考问题消息,追加内容
this.currentMessage.content += data.msg1;
}
}
if (data.msg) {
// 处理回答消息
if (!this.currentMessage || this.currentMessage.id !== 2 || this.currentMessage.isThinking) {
// 如果当前没有正在显示的回答消息,或者当前消息不是回答消息,或者当前是思考问题消息
this.currentMessage = { id: 2, name: 'kanade', content: '回答问题:' + data.msg, isThinking: false };
this.list.push(this.currentMessage);
this.currentIndex = 0;
} else {
// 如果当前正在显示回答消息,追加内容
this.currentMessage.content += data.msg;
}
}
this.scrollToBottom();
}
};
this.eventSource.onerror = (error) => {
this.isButtonDisabled = false;
this.eventSource.close();
};
},
// 添加新的方法用于处理内容更新时的滚动
scrollToBottom() {
this.$nextTick(() => {
const div = this.$refs.box;
div.scrollTop = div.scrollHeight;
});
},
startTyping() {
if (this.typingTimer) {
clearInterval(this.typingTimer);
}
this.typingTimer = setInterval(() => {
if (this.currentIndex < this.currentMessage.content.length) {
this.currentMessage.content = this.currentMessage.content.slice(0, this.currentIndex + 1);
this.currentIndex++;
const div = this.$refs.box;
div.scrollTop = div.scrollHeight;
} else {
clearInterval(this.typingTimer);
}
}, 50); // 控制逐字显示速度,可调整
},
cancel() {
if (this.eventSource) {
this.eventSource.close();
}
if (this.typingTimer) {
clearInterval(this.typingTimer);
}
this.$emit('cancel');
},
submit() {
this.$emit('submit');
},
setStartingPoint(e) {
this.x = e.clientX - this.node.offsetLeft;
this.y = e.clientY - this.node.offsetTop;
this.isCanMove = true;
},
modelMove(e) {
if (this.isCanMove) {
this.node.style.left = e.clientX - this.x + 'px';
this.node.style.top = e.clientY - this.y + 'px';
}
},
cancelMove() {
this.isCanMove = false;
},
}
}
</script>
<style scoped>
.model-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}
.model-container {
background: #1a1a1a;
border-radius: 12px;
width: 80%;
max-width: 900px;
height: 60vh;
max-height: 800px;
display: flex;
flex-direction: column;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
overflow: hidden;
border: 1px solid #333;
}
.model-header {
height: 60px;
background: linear-gradient(90deg, #2a2a2a, #1a1a1a);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: move;
font-size: 18px;
font-weight: 600;
border-bottom: 1px solid #333;
position: relative;
}
.model-header::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
background: linear-gradient(90deg, rgba(0, 0, 0, 0), #444, rgba(0, 0, 0, 0));
}
.model-main {
flex: 1;
padding: 20px;
overflow-y: auto;
background: #1a1a1a;
scrollbar-width: thin;
scrollbar-color: #444 #1a1a1a;
}
.model-main::-webkit-scrollbar {
width: 6px;
}
.model-main::-webkit-scrollbar-track {
background: #1a1a1a;
}
.model-main::-webkit-scrollbar-thumb {
background-color: #444;
border-radius: 3px;
}
.model-footer {
padding: 15px;
background: #1a1a1a;
border-top: 1px solid #333;
display: flex;
align-items: center;
justify-content: center;
}
.model-footer button {
width: 100px;
background: #333;
color: #fff;
border: none;
}
.model-footer button:hover {
background: #444;
}
.atalk {
margin: 15px 0;
display: flex;
align-items: flex-start;
}
.atalk::before {
content: "🤖";
margin-right: 10px;
font-size: 20px;
}
.atalk span {
background: #2a2a2a;
color: #e0e0e0;
border-radius: 12px;
padding: 12px 16px;
max-width: 80%;
line-height: 1.5;
border: 1px solid #333;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.btalk {
margin: 15px 0;
display: flex;
justify-content: flex-end;
}
.btalk::after {
content: "👤";
margin-left: 10px;
font-size: 20px;
}
.btalk span {
background: #0078d4;
color: white;
border-radius: 12px;
padding: 12px 16px;
max-width: 80%;
line-height: 1.5;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.input-container {
display: flex;
padding: 15px;
background: #1a1a1a;
border-top: 1px solid #333;
}
.inputword {
flex: 1;
height: 40px;
border-radius: 20px;
border: 1px solid #444;
padding: 0 15px;
background: #2a2a2a;
color: #fff;
outline: none;
font-size: 14px;
}
.inputword:focus {
border-color: #0078d4;
}
.btnsend {
width: 100px;
height: 40px;
margin-left: 10px;
background: #0078d4;
color: white;
border: none;
border-radius: 20px;
font-weight: 500;
transition: all 0.2s;
}
.btnsend:hover {
background: #0063b1;
}
.btnsend:disabled {
background: #555;
cursor: not-allowed;
}
/* 思考中样式 */
.atalk.thinking span {
color: #aaa;
font-style: italic;
background: #2a2a2a;
}
/* 问题样式 */
.atalk.question span {
color: #4fc3f7;
font-weight: 500;
}
/* 打字效果 */
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.typing-cursor::after {
content: "|";
animation: blink 1s infinite;
color: #4fc3f7;
}
</style>