Java结合AI技术学习总结【kaki与时俱进】

近期呢kaki也是准备发愤图强了,学了一波Java与Ai结合的技术,当然kaki平时工作中最多用到的也就是ai对话类的助手,但内部的部署逻辑确实不太清楚,知己知彼百战百胜。还是要从最简单的入手学习!

先在自己本地部署个deepseek玩玩,科研一下哈~

ollama简单起手

第一步下载ollama,https://ollama.com/download

第二部:控制台黑窗口 输入ollama命令,出现如下信息,那就是成功了

第三步,在ollama官网下载deepseek r1 (https://ollama.com/library/deepseek-r1

配置低的小伙伴不要担心,使用1.5b或者8b即可,这里主要是了解整个流程

如此就算部署完成了

结合Java 的配置

此处jdk使用21,spring-boot使用338版本,spring-ai使用的是spring-ai-ollama-spring-boot-starter的,黏贴pom如下

XML 复制代码
<?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>com.kaki</groupId>
    <artifactId>kaki-deepseek</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.8</version>
        <relativePath />
    </parent>

    <dependencies>
        <!-- springboot ai start -->
        <dependency>
            <groupId>io.springboot.ai</groupId>
            <artifactId>spring-ai-ollama</artifactId>
            <version>1.0.3</version>
        </dependency>
        <dependency>
            <groupId>io.springboot.ai</groupId>
            <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
            <version>1.0.3</version>
        </dependency>
        <!-- springboot ai end -->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
<!--            <version>3.4.2</version>-->
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

</project>

application.properties,这里配置了spring-ai的配置信息

TypeScript 复制代码
spring:
  application:
    name: kaki-deepseek
  profiles:
    active: prod
  ai:
    anthropic:
      base-url: http://127.0.0.1:11434
    ollama:
      chat:
        model: deepseek-r1:1.5b

logging:
  level:
    root: debug

java核心代码如下(spring官方资料:https://spring.io/projects/spring-ai)

java 复制代码
package com.kaki.service;

import jakarta.annotation.Resource;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.OllamaChatClient;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class OllamaStreamDiscover {

    @Resource
    private OllamaChatClient ollamaChatClient;

    public void doSendSteamV3(String userId,String msg){
        Prompt prompt = new Prompt(new UserMessage(msg));

        Flux<ChatResponse> streamFlux = ollamaChatClient.stream(prompt);

        List<Object> collect = streamFlux.toStream().map(chatResponse -> {
            String content = chatResponse.getResult().getOutput().getContent();
            System.out.println(content);
            SSEServer.sendMsg(userId,content,"add");
            return content;
        }).collect(Collectors.toList());
        SSEServer.sendMsg(userId,"done","finish");

    }

}

流式官网写法:https://docs.spring.io/spring-ai/reference/api/chatclient.html

官网主页面默认提供的是完全回答完毕再返回,这种对于用户来说问完一个问题后,无法一个字一个字的回拼,页面是空白的,直到全部回答完才显示,所以才有流式写法。不过流式写法就需要结合SSE进行

SSE核心代码:

java 复制代码
package com.kaki.service;


import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

//@Slf4j
@Service
public class SSEServer {
    private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();

    public static SseEmitter connect(String userId){
        SseEmitter sseEmitter = new SseEmitter(0L);
        sseEmitter.onCompletion(completionCallBack(userId));
        sseEmitter.onTimeout(timeoutCallBack(userId));

        sseEmitterMap.put(userId,sseEmitter);
        System.out.println("创建用户,id:"+userId);
        return sseEmitter;
    }

    /**
     * 容器,保存连接,用于输出返回 ;可使用其他方法实现
     */
    private static final Map<String, SseEmitter> sseCache = new ConcurrentHashMap<>();


    /**
     * 根据客户端id获取SseEmitter对象
     *
     * @param clientId 客户端ID
     */
    public SseEmitter getSseEmitterByClientId(String clientId) {
        return sseCache.get(clientId);
    }

    public static void sendMsg(String userId,String msg,String msgType){
        if (sseEmitterMap.isEmpty()){
            return;
        }
        if (sseEmitterMap.containsKey(userId)){
            SseEmitter sseEmitter = sseEmitterMap.get(userId);
            sendSseEmitterMsg(sseEmitter,userId,msg,msgType);
        }
    }

    public static void stop(String userId){
        if (sseEmitterMap.isEmpty()){
            return;
        }

        SseEmitter sseEmitter = sseEmitterMap.get(userId);

        if (sseEmitter != null){
            // 执行完毕,断开链接
            sseEmitter.complete();
            System.out.println("执行完毕,断开链接");
        }else {
            System.out.println("无当前会话");
        }

    }

    public static void sendSseEmitterMsg(SseEmitter sseEmitter,String userId,String msg,String msgType){
        try {
            SseEmitter.SseEventBuilder data = SseEmitter.event().id(userId).name(msgType).data(msg);
            sseEmitter.send(data);
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("xxxvcvvv");
        }
    }


    /**
     * 关闭连接
     *
     * @param clientId 客户端ID
     */

    public void closeConnect(String clientId) {
        SseEmitter sseEmitter = sseCache.get(clientId);
        if (sseEmitter != null) {
            sseEmitter.complete();
            removeUser(clientId);
        }
    }


    /**
     * 长链接完成后回调接口(即关闭连接时调用)
     *
     * @param clientId 客户端ID
     **/
    private static Runnable completionCallBack(String clientId) {
        return () -> {
            System.out.println("结束连接:{}"+clientId);
            removeUser(clientId);
        };
    }

    /**
     * 连接超时时调用
     *
     * @param clientId 客户端ID
     **/
    private static Runnable timeoutCallBack(String clientId) {
        return () -> {
            System.out.println("连接超时:{}"+ clientId);
            removeUser(clientId);
        };
    }


    /**
     * 移除用户连接
     *
     * @param clientId 客户端ID
     **/
    private static void removeUser(String clientId) {
        sseCache.remove(clientId);
        System.out.println("SseEmitterServiceImpl[removeUser]:移除用户:{}"+clientId);
    }
}

前端html demo

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Server-Sent Events Example</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        #messages {
            border: 1px solid #ccc;
            height: 300px;
            overflow-y: scroll;
            padding: 10px;
            margin: 20px 0;
            background-color: #f9f9f9;
        }
        .message {
            margin-bottom: 10px;
            padding: 5px;
            border-radius: 3px;
        }
        .status {
            margin: 10px 0;
        }
        button {
            padding: 10px 15px;
            margin-right: 10px;
            cursor: pointer;
        }
        
        /* Chat container styles */
        .chat-container {
            display: flex;
            flex-direction: column;
            height: 400px;
            border: 1px solid #ddd;
            border-radius: 8px;
            overflow: hidden;
        }
        
        .chat-messages {
            flex: 1;
            padding: 15px;
            overflow-y: auto;
            background-color: #f5f5f5;
        }
        
        .message-bubble {
            max-width: 70%;
            padding: 10px 15px;
            margin-bottom: 10px;
            border-radius: 18px;
            position: relative;
            word-wrap: break-word;
        }
        
        .user-message {
            background-color: #0084ff;
            color: white;
            margin-left: auto;
            text-align: right;
        }
        
        .received-message {
            background-color: #e5e5ea;
            color: black;
            margin-right: auto;
        }
        
        .chat-input-area {
            display: flex;
            padding: 10px;
            background-color: white;
            border-top: 1px solid #ddd;
        }
        
        #userInput {
            flex: 1;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 20px;
            outline: none;
        }
        
        #sendBtn {
            margin-left: 10px;
            padding: 10px 20px;
            background-color: #0084ff;
            color: white;
            border: none;
            border-radius: 20px;
            cursor: pointer;
        }
        
        #sendBtn:hover {
            background-color: #0066cc;
        }
        
        .timestamp {
            font-size: 0.7em;
            color: #999;
            margin-top: 5px;
            text-align: right;
        }
    </style>

</head>
<body>

    <div>        
        <h1>SSE 客户端</h1>
    </div>

    <div id="userBox"></div>

    <div class="chat-container">
        <div class="chat-messages" id="chatMessages">
            <div id="message" class="message-bubble received-message">
                <!-- 欢迎使用客服系统! -->
                <p id="message"></p>
                <!-- <div class="timestamp">刚刚</div> -->
            </div>
        </div>
        <div class="chat-input-area">
            <input type="text" id="userInput" placeholder="请输入消息...">
            <button id="sendBtn" onclick="aaa()">发送</button>
        </div>
    </div>

    <script>
        let source = null;
        let userId = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);

        if(window.EventSource) {
            source = new EventSource('http://localhost:8099/sse/flux?userId='+'aaabbb');
           
            source.addEventListener('open', function(event) {
                console.log('Connection 连接成功');
                // document.getElementById('message').innerHTML = '连接成功';
            },false);

            source.addEventListener('message', function(event) {
                console.log('message',event.data)
                let text = document.getElementById('message').innerHTML;
                text+='<br>'+event.data
               
                displayReceivedMessage(text);
            },false);

            source.addEventListener('error', function(event) {
                console.log('连接失败');
            },false);

            source.addEventListener('add',function(event){
                 console.log('add',event.data)
                let text = document.getElementById('message').innerHTML;
                text+=event.data
                document.getElementById('message').innerHTML = text
            },false)

            source.addEventListener('finish',function(event){
                console.log('finish !!!!')
                //let text = document.getElementById('message').innerHTML;
                //text+='<br>';
                //document.getElementById('message').innerHTML = text
                //let text = document.getElementById('message').innerHTML;
                //text+='<br>'+event.data
               
                //displayReceivedMessage(text);
            },false)
            
        }

        const chatMessages = document.getElementById('chatMessages');
        const userInput = document.getElementById('userInput');

        function displayUserMessage(text) {
            const messageDiv = document.createElement('div');
            messageDiv.className = 'message-bubble user-message';
            
            const messageContent = document.createElement('div');
            messageContent.textContent = text;
            
            const timestamp = document.createElement('div');
            timestamp.className = 'timestamp';
            timestamp.textContent = getCurrentTime();
            
            messageDiv.appendChild(messageContent);
            messageDiv.appendChild(timestamp);
            chatMessages.appendChild(messageDiv);
            
            // Scroll to bottom
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }

        // Function to display received message
        function displayReceivedMessage(text) {
            const messageDiv = document.createElement('div');
            messageDiv.className = 'message-bubble received-message';
            
            const messageContent = document.createElement('div');
            messageContent.textContent = text;
            
            const timestamp = document.createElement('div');
            timestamp.className = 'timestamp';
            timestamp.textContent = getCurrentTime();
            
            messageDiv.appendChild(messageContent);
            messageDiv.appendChild(timestamp);
            chatMessages.appendChild(messageDiv);
            
            // Scroll to bottom
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }

        // Function to get current time in HH:MM format
        function getCurrentTime() {
            const now = new Date();
            return now.getHours() + ':' + (now.getMinutes() < 10 ? '0' : '') + now.getMinutes();
        }

        function aaa(){
            if (userInput.value.trim() === '') {
                console.log('请输入消息!');
                return;
            }
            
            var xhr = new XMLHttpRequest();
            xhr.open('GET', 'http://localhost:8099/sse/letsChat?userId=aaabbb&msg='+userInput.value, true);
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    console.log(xhr.responseText);
                    }
            };
            xhr.send();

            displayUserMessage(userInput.value);

            //console.log(123);
            
        }

        function sendMessageToServer(message) {
            console.log('Sending message to server:', message);
        }

        let eventSource;
        const messagesContainer = document.getElementById('messages');
        const statusElement = document.getElementById('status');
        const startBtn = document.getElementById('startBtn');
        const stopBtn = document.getElementById('stopBtn');

        function addMessage(message) {
            const messageElement = document.createElement('div');
            messageElement.className = 'message';
            messageElement.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
            messagesContainer.appendChild(messageElement);
            messagesContainer.scrollTop = messagesContainer.scrollHeight;
        }
    </script>
</body>
</html>

最终效果

kaki与时俱进,加油💪

相关推荐
小李子呢02111 小时前
Node.js
开发语言·前端·学习·node.js
鱼很腾apoc1 小时前
【实战篇】 第13期 算法竞赛_数据结构超详解(上)
c语言·开发语言·数据结构·学习·算法·青少年编程
老蒋每日coding2 小时前
AI Agent 设计模式系列(九)——学习和适应模式
人工智能·学习·设计模式
今晚努力早睡2 小时前
渗透学习总结
学习·安全·网络安全
世人万千丶2 小时前
Day 5: Flutter 框架 SQLite 数据库进阶 - 在跨端应用中构建结构化数据中心
数据库·学习·flutter·sqlite·harmonyos·鸿蒙·鸿蒙系统
丝斯20112 小时前
AI学习笔记整理(53)——大模型之Agent 智能体开发
人工智能·笔记·学习
星火开发设计2 小时前
循环结构进阶:while 与 do-while 循环的适用场景
java·开发语言·数据结构·学习·知识·循环
青衫码上行2 小时前
Maven高级:分模块、聚合继承、多环境配置与私服搭建
java·学习·maven
开开心心_Every2 小时前
无广告输入法推荐:内置丰富词库免费皮肤
服务器·前端·学习·决策树·edge·powerpoint·动态规划