蹭个大模型热度,创建一个简单的通义聊天机器人,前端页面通义千问自己写的。
仅供参考,参考了一部分通义api和CSDN里面大部分文章。在这里简单介绍一下。
每个ip每天允许对话20次,在20次结束后禁止继续。
效果演示:
通义千问api调用
注册啥的就不多说了,简单介绍一下代码。
依赖包如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!--阿里巴巴大模型-->
<!-- https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.16.0</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
<version>0.0.20131108.vaadin1</version>
</dependency>
</dependencies>
前端页面很简单,一个chat按钮,弹出聊天框,然后有个聊天框。发送post请求。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat Assistant</title>
</head>
<body>
<div id="chat-button" class="chat-button">Chat</div>
<div id="chat-container" class="chat-container hidden">
<div class="chat-header">Chat Assistant</div>
<div id="chat-messages" class="chat-messages"></div>
<div class="chat-input">
<input id="message-input" type="text" placeholder="Type your message...">
<button id="send">Send</button>
</div>
</div>
<script type="text/javascript">
document.getElementById('chat-button').addEventListener('click', function() {
const chatContainer = document.getElementById('chat-container');
if (chatContainer.classList.contains('hidden')) {
chatContainer.classList.remove('hidden');
send.removeEventListener('click', sendMessage); // 移除旧的事件监听器
send.addEventListener('click', sendMessage); // 添加新的事件监听器
} else {
chatContainer.classList.add('hidden');
}
});
const send = document.getElementById("send")
send.addEventListener('click', sendMessage);
async function sendMessage() {
const input = document.getElementById('message-input');
const message = input.value.trim();
if (!message) return;
// Display the user's message first
displayMessage(message, 'user');
// Send the message to the server
try {
const response = await fetch('http://localhost:8080/ai/send1', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ "question":message })
});
if (!response.ok) {
throw new Error('Failed to send message');
}
const data =await response.json();
console.log(data)
if (data.output && data.output==="exit"){
displayMessage(data.content,"exit");
}
else {
displayMessage(data.output.choices[0].message.content,"assisent");
}
} catch (error) {
console.error(error);
displayMessage('又出问题了,卧槽.', 'error');
} finally {
input.value = ''; // Clear the input field
}
}
function displayMessage(text, type) {
const messagesContainer = document.getElementById('chat-messages');
const messageElement = document.createElement('div');
messageElement.textContent = type+':'+text;
messageElement.className = `message ${type}`;
messagesContainer.appendChild(messageElement);
messagesContainer.scrollTop = messagesContainer.scrollHeight; // Scroll to the bottom
}
</script>
</body>
<style type="text/css">
/* styles.css */
.chat-button {
position: fixed;
bottom: 20px;
right: 20px;
width: 60px;
height: 60px;
background-color: #007BFF;
color: white;
border: none;
border-radius: 50%;
font-size: 16px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.chat-container {
position: 0px 0px;
bottom: 20px;
right: 20px;
width: 300px;
height: 400px;
background-color: white;
border: 1px solid #ccc;
border-radius: 10px;
overflow: hidden;
transition: all 0.3s ease-in-out;
}
.hidden {
transform: translateX(-100%);
}
.chat-header {
background-color: #007BFF;
color: white;
padding: 10px;
text-align: center;
}
.chat-messages {
height: 300px;
overflow-y: auto;
padding: 10px;
}
.chat-input {
display: flex;
align-items: center;
padding: 10px;
}
.chat-input input {
flex-grow: 1;
margin-right: 10px;
}
</style>
</html>
后端Controller类,messages是多轮对话消息对象,具体可以参考一下通义千问api。单轮和多轮的原理都是消息对象构建->参数对象构建->文本生成返回结果。在此基础上,加了一点ip限制和exit退出。
package com.vnicy_ty.controller;
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 com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.JsonUtils;
import com.google.gson.JsonObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.vnicy_ty.service.IpRateService;
@RestController
@RequestMapping("/ai")
public class AiController {
@Value("${ai-api-key}")
private String appKey;
private final Generation generation;
private List<Message> messages = new ArrayList<>();
private final IpRateService ipRateLimiter;
//spring 注入
@Autowired
public AiController(Generation generation, IpRateService ipRateLimiter) {
this.generation = generation;
this.ipRateLimiter = ipRateLimiter;
}
//单轮对话
@PostMapping("/send")
public String aiTalk(@RequestBody String question) throws NoApiKeyException, InputRequiredException {
// 构建消息对象
Message message = Message.builder().role(Role.USER.getValue()).content(question).build();
// 构建通义千问参数对象
GenerationParam param = GenerationParam.builder()
.model(Generation.Models.QWEN_PLUS)
.messages(Arrays.asList(message))
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
.topP(0.8)
.apiKey(appKey)
.build();
// 执行文本生成操作,并流式返回结果
Generation gen = new Generation();
return JsonUtils.toJson(gen.call(param));
}
private static Message createMessage(Role role, String content) {
return Message.builder().role(role.getValue()).content(content).build();
}
//多轮对话参数构造
public static GenerationParam createGenerationParam(List<Message> messages, String appKey) {
return GenerationParam.builder()
.model("qwen-turbo")
.messages(messages)
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
.topP(0.8)
.apiKey(appKey)
.build();
}
//获取ip
private String getClientIP(HttpServletRequest request) {
String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader != null) {
return xfHeader.split(",")[0];
}
return request.getRemoteAddr();
}
@PostMapping("/send1")
public String aiTalk1(@RequestBody String question, HttpServletRequest request) throws NoApiKeyException, InputRequiredException, JSONException {
//获取当前ip
String clientIP = getClientIP(request);
if (!ipRateLimiter.isIpAllowed(clientIP)) {
JsonObject responseJson = new JsonObject ();
responseJson.addProperty("content", "已达最大聊天次数");
responseJson.addProperty("output", "exit");
return JsonUtils.toJson(responseJson);
}
messages.add(createMessage(Role.SYSTEM, "You are a helpful assistant."));
JSONObject temp = new JSONObject(question);
if ("exit".equalsIgnoreCase(temp.getString("question"))) {
JsonObject responseJson = new JsonObject ();
responseJson.addProperty("content", "已退出聊天");
responseJson.addProperty("output", "exit");
this.messages = new ArrayList<>();
return JsonUtils.toJson(responseJson);
}
messages.add(createMessage(Role.USER, question));
GenerationParam param = createGenerationParam(messages, appKey);
Generation gen = new Generation();
return JsonUtils.toJson(gen.call(param));
}
}
AiConfiguration类如下:
package com.vnicy_ty.dto;
import com.alibaba.dashscope.aigc.generation.Generation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiConfiguration {
@Bean
public Generation generation() {
return new Generation();
}
}
IpRateService类如下,就请求次数统计和定时清除:
package com.vnicy_ty.service;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@Service
public class IpRateService {
private final Map<String, AtomicInteger> ipCounters = new ConcurrentHashMap<>();
private final ThreadPoolTaskScheduler taskScheduler;
public IpRateService(ThreadPoolTaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
scheduleDailyCleanup();
}
public boolean isIpAllowed(String ip) {
AtomicInteger counter = ipCounters.computeIfAbsent(ip, k -> new AtomicInteger(0));
int count = counter.incrementAndGet();
System.out.println("-----");
System.out.println("Request from IP "+ ip+": Count is now "+ count);
System.out.println("-----");
if (count > 20) {
return false; // 已达到限制次数
}
return true;
}
private void scheduleDailyCleanup() {
Runnable cleanupTask = this::cleanupExpiredRecords;
taskScheduler.schedule(cleanupTask, new CronTrigger("0 0 0 * * ?")); // 每天凌晨0点运行
}
private void cleanupExpiredRecords() {
ipCounters.clear(); // 每天清空一次
}
}
最后是TaskSchedulerConfig类,Ip设置的时候需要用到
package com.vnicy_ty.dto;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
public class TaskSchedulerConfig {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(1); // 根据需要调整线程池大小
scheduler.initialize();
return scheduler;
}
}
最后就是api-key配置【application.properties文件】
ai-api-key = 自己的api-key
github地址: