Part05 中州养老项目实训 ☀️
大约 6 分钟
Part05 中州养老项目实训 ☀️
额外拓展
1 拓展-SpringAI+DeepSeek前言
开发一个和Deepseek聊天的页面,使用SpringAI+Deepseek
代码操作

官网:https://spring.io/projects/spring-ai
1. 创建一个Maven项目,并且导入以下依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.yangeit</groupId>
<artifactId>ai-deepseek</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.2</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- lombok的依赖,能免除get和set方法 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
</dependencies>
</project>
2. 创建配置文件application.yml,并添加以下内容(含有API Key)并创建启动类
#服务端口
server:
port: 8089
# 服务名
spring:
ai:
openai:
base-url: https://api.deepseek.com
# DeepSeek的OpenAI式端点
api-key: sk-7用自己的
chat.options:
model: deepseek-chat # 指定DeepSeek的模型名称 或者chat reasoner
@SpringBootApplication
public class DeepSeekApplication {
public static void main(String[] args) {
SpringApplication.run(DeepSeekApplication.class,args);
System.out.println("启动成功");
}
}
3. 创建一个AIConfig配置配类,给AI设定角色
@Configuration
public class AiConfig {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("你现在不是 deepseek 了," +
"你是一名学识渊博的诗人,擅长唐诗宋词")
.build();
}
}
4. 创建一个Controller类,并添加一个GET请求处理方法,用于接收用户输入并调用DeepSeek API
@RestController
@RequestMapping("/ai")
@Slf4j
public class DeepSeekController {
@Autowired
ChatClient chatClient;
@GetMapping("/chat")
public String generate(@RequestParam(value = "message") String message) {
log.info("Generating response");
// 调用 ChatClient 的 prompt 方法生成响应
// 1. prompt(message): 创建一个包含用户输入消息的 Prompt 对象
// 2. call(): 调用 ChatClient 与 AI 模型交互以获取响应
// 3. content(): 提取响应的内容部分
return chatClient.prompt().user(message).call().content();
}
/**
* @description: 流式响应
**/
@GetMapping(value = "/chat2",
produces = "application/json;charset=utf-8")
public Flux<String> generation02(@RequestParam String message){
Flux<String> output = chatClient.prompt()
.user(message)
.stream()
.content();
return output;
}
}

1. 在resources下创建static文件夹,并创建一个index.html文件,用于展示聊天界面
完成代码输入后,启动项目,访问:localhost:8089 进入对话页面
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Deepseek 聊天</title>
<style>
:root {
--primary-color: #4f46e5;
--bg-color: #121212; /* 深色背景 */
--card-color: #1e1e1e; /* 深色卡片 */
--text-color: #ffffff; /* 白色文字 */
--border-color: #333333; /* 较深的边框 */
}
/* 明暗模式切换 */
[data-theme="light"] {
--bg-color: #f9fafb;
--card-color: #ffffff;
--text-color: #111827;
--border-color: #e5e7eb;
}
body {
font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
height: 100vh;
transition: background-color 0.3s, color 0.3s;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
flex: 1;
display: flex;
flex-direction: column;
width: 100%;
}
.header {
text-align: center;
margin-bottom: 20px;
}
.header h1 {
color: var(--primary-color);
margin-bottom: 8px;
}
.header p {
color: #6b7280;
margin-top: 0;
}
.chat-container {
flex: 1;
display: flex;
flex-direction: column;
background-color: var(--card-color);
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.messages {
flex: 1;
padding: 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 16px;
transition: opacity 0.3s;
}
.message {
max-width: 80%;
padding: 12px 16px;
border-radius: 12px;
line-height: 1.5;
opacity: 0;
animation: fadeIn 0.5s forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.user-message {
align-self: flex-end;
background-color: var(--primary-color);
color: white;
border-bottom-right-radius: 4px;
}
.ai-message {
align-self: flex-start;
background-color: #333333; /* 更深的背景色 */
color: var(--text-color);
border-bottom-left-radius: 4px;
}
.input-area {
display: flex;
padding: 16px;
border-top: 1px solid var(--border-color);
background-color: var(--card-color);
}
.input-area input {
flex: 1;
padding: 12px 16px;
border: 1px solid var(--border-color);
border-radius: 8px;
font-size: 16px;
outline: none;
transition: border-color 0.2s;
}
.input-area input:focus {
border-color: var(--primary-color);
}
.input-area button {
margin-left: 12px;
padding: 12px 24px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: background-color 0.2s;
}
.input-area button:hover {
background-color: #4338ca;
}
.input-area button:disabled {
background-color: #9ca3af;
cursor: not-allowed;
}
.typing-indicator {
display: inline-block;
padding: 8px 16px;
background-color: #333333;
border-radius: 12px;
color: #6b7280;
font-size: 14px;
}
.typing-dots {
display: inline-flex;
align-items: center;
}
.typing-dots span {
width: 6px;
height: 6px;
margin: 0 2px;
background-color: #9ca3af;
border-radius: 50%;
display: inline-block;
animation: typing 1.4s infinite both;
}
.typing-dots span:nth-child(2) {
animation-delay: 0.2s;
}
.typing-dots span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0% {
opacity: 0.4;
transform: translateY(0);
}
50% {
opacity: 1;
transform: translateY(-4px);
}
100% {
opacity: 0.4;
transform: translateY(0);
}
}
/* 明暗模式切换按钮 */
.theme-toggle {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
font-size: 18px;
color: var(--primary-color);
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Deepseek Chat</h1>
<p>与AI进行流畅的对话体验</p>
</div>
<div class="chat-container">
<div class="messages" id="messages">
<!-- 消息将在这里动态添加 -->
</div>
<div class="input-area">
<input type="text" id="userInput" placeholder="输入消息..." autocomplete="off">
<button id="sendButton">发送</button>
</div>
</div>
<!-- 明暗模式切换按钮 -->
<div class="theme-toggle" onclick="toggleTheme()">🌙</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const messagesContainer = document.getElementById('messages');
const userInput = document.getElementById('userInput');
const sendButton = document.getElementById('sendButton');
// 切换明暗模式
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
}
// 加载用户偏好主题
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-theme', savedTheme);
// 添加用户消息到聊天界面
function addUserMessage(text) {
const messageDiv = document.createElement('div');
messageDiv.className = 'message user-message';
messageDiv.textContent = text;
messagesContainer.appendChild(messageDiv);
scrollToBottom();
}
// 添加AI消息到聊天界面
function addAiMessage(text) {
const messageDiv = document.createElement('div');
messageDiv.className = 'message ai-message';
messageDiv.textContent = text;
messagesContainer.appendChild(messageDiv);
scrollToBottom();
}
// 显示AI正在输入的指示器
function showTypingIndicator() {
const typingDiv = document.createElement('div');
typingDiv.id = 'typingIndicator';
typingDiv.className = 'typing-indicator';
typingDiv.innerHTML = `
<div class="typing-dots">
<span></span>
<span></span>
<span></span>
</div>
Deepseek 正在输入...
`;
messagesContainer.appendChild(typingDiv);
scrollToBottom();
}
// 隐藏AI正在输入的指示器
function hideTypingIndicator() {
const typingIndicator = document.getElementById('typingIndicator');
if (typingIndicator) {
typingIndicator.remove();
}
}
// 滚动到聊天底部
function scrollToBottom() {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// 发送消息到后端并处理响应
async function sendMessage() {
const message = userInput.value.trim();
if (!message) return;
// 添加用户消息并清空输入框
addUserMessage(message);
userInput.value = '';
// 显示AI正在输入
showTypingIndicator();
try {
// 使用Fetch API发送请求
const url = `http://localhost:8089/ai/chat?message=${encodeURIComponent(message)}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json, text/plain'
}
});
if (!response.ok) {
throw new Error('网络响应错误');
}
// 克隆响应对象以避免重复读取
const responseClone = response.clone();
// 尝试解析JSON,如果失败则直接读取文本
let data;
try {
data = await response.json();
} catch (e) {
// 如果解析JSON失败,尝试读取纯文本
data = { response: await responseClone.text() };
}
hideTypingIndicator();
addAiMessage(data.response); // 假设后端返回的JSON中有response字段
} catch (error) {
console.error('Error:', error);
hideTypingIndicator();
addAiMessage('抱歉,发生了一些错误。请稍后再试。');
}
}
// 发送按钮点击事件
sendButton.addEventListener('click', sendMessage);
// 输入框回车事件
userInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
// 初始焦点在输入框
userInput.focus();
// 从本地存储加载聊天历史
const chatHistory = JSON.parse(localStorage.getItem('chatHistory') || '[]');
chatHistory.forEach(msg => {
if (msg.type === 'user') {
addUserMessage(msg.text);
} else {
addAiMessage(msg.text);
}
});
// 保存聊天历史
function saveChatHistory() {
const messages = Array.from(messagesContainer.children).map(msg => ({
type: msg.classList.contains('user-message') ? 'user' : 'ai',
text: msg.textContent
}));
localStorage.setItem('chatHistory', JSON.stringify(messages));
}
// 监听消息添加事件以保存历史
messagesContainer.addEventListener('DOMNodeInserted', saveChatHistory);
});
</script>
</body>
</html>
总结
课堂作业
- SpringAI至少需要JDK版本是多少? 🎤
- SpringAI是大模型吗? 他的作用是什么?