新增ollama模型对话
This commit is contained in:
parent
1c936afe4c
commit
37c0f3feef
|
|
@ -141,6 +141,8 @@
|
||||||
<!-- <version>1.18.30</version>-->
|
<!-- <version>1.18.30</version>-->
|
||||||
<!-- <scope>provided</scope>-->
|
<!-- <scope>provided</scope>-->
|
||||||
<!-- </dependency>-->
|
<!-- </dependency>-->
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
//package cn.qihangerp.erp.config;
|
|
||||||
//
|
|
||||||
//import cn.qihangerp.erp.filter.UrlTokenFilter;
|
|
||||||
//import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
//import org.springframework.context.annotation.Bean;
|
|
||||||
//import org.springframework.context.annotation.Configuration;
|
|
||||||
//import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
//import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
|
||||||
//import org.springframework.security.web.SecurityFilterChain;
|
|
||||||
//import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
||||||
//
|
|
||||||
///**
|
|
||||||
// * Spring Security配置
|
|
||||||
// *
|
|
||||||
// * @author qihang
|
|
||||||
// */
|
|
||||||
//@Configuration
|
|
||||||
//@EnableWebSecurity
|
|
||||||
//public class SecurityConfig {
|
|
||||||
// @Autowired
|
|
||||||
// private UrlTokenFilter urlTokenFilter;
|
|
||||||
//
|
|
||||||
// @Bean
|
|
||||||
// public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
|
||||||
// http
|
|
||||||
// .csrf(AbstractHttpConfigurer::disable)
|
|
||||||
// .authorizeRequests(authorizeRequests ->
|
|
||||||
// authorizeRequests
|
|
||||||
// .anyRequest().permitAll()
|
|
||||||
// )
|
|
||||||
// .addFilterBefore(urlTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
|
||||||
//
|
|
||||||
// return http.build();
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
package cn.qihangerp.erp.controller;
|
package cn.qihangerp.erp.controller;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSONArray;
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
@ -10,11 +9,9 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
|
import cn.qihangerp.erp.serviceImpl.AiService;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
@ -29,6 +26,9 @@ public class SseController {
|
||||||
private static final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
|
private static final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
|
||||||
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
|
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AiService aiService;
|
||||||
|
|
||||||
@GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
@GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
public SseEmitter connect(@RequestParam String clientId) {
|
public SseEmitter connect(@RequestParam String clientId) {
|
||||||
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
|
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
|
||||||
|
|
@ -69,14 +69,16 @@ public class SseController {
|
||||||
SseEmitter emitter = emitters.get(clientId);
|
SseEmitter emitter = emitters.get(clientId);
|
||||||
if (emitter != null) {
|
if (emitter != null) {
|
||||||
try {
|
try {
|
||||||
// 调用opencode接口获取回复
|
// 使用AiService处理消息
|
||||||
String response = callOpencodeApi(message);
|
String response = aiService.processMessage(message);
|
||||||
|
|
||||||
emitter.send(SseEmitter.event()
|
emitter.send(SseEmitter.event()
|
||||||
.name("message")
|
.name("message")
|
||||||
.data(response));
|
.data(response));
|
||||||
|
|
||||||
return "消息发送成功";
|
return "消息发送成功";
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
log.error("消息处理失败: {}", e.getMessage());
|
||||||
emitters.remove(clientId);
|
emitters.remove(clientId);
|
||||||
return "消息发送失败";
|
return "消息发送失败";
|
||||||
}
|
}
|
||||||
|
|
@ -84,79 +86,6 @@ public class SseController {
|
||||||
return "客户端不存在";
|
return "客户端不存在";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String callOpencodeApi(String message) throws Exception {
|
|
||||||
// 创建HTTP客户端
|
|
||||||
HttpClient client = HttpClient.newHttpClient();
|
|
||||||
|
|
||||||
// 1. 创建新会话
|
|
||||||
HttpRequest createSessionRequest = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create("http://localhost:14967/session"))
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.POST(HttpRequest.BodyPublishers.ofString("{}"))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
HttpResponse<String> createSessionResponse = client.send(createSessionRequest, HttpResponse.BodyHandlers.ofString());
|
|
||||||
String sessionId = parseSessionId(createSessionResponse.body());
|
|
||||||
|
|
||||||
// 2. 构建消息请求体
|
|
||||||
JSONObject requestBody = new JSONObject();
|
|
||||||
JSONArray parts = new JSONArray();
|
|
||||||
JSONObject part = new JSONObject();
|
|
||||||
part.put("type", "text");
|
|
||||||
part.put("text", message);
|
|
||||||
parts.add(part);
|
|
||||||
requestBody.put("parts", parts);
|
|
||||||
|
|
||||||
// 3. 向会话发送消息
|
|
||||||
HttpRequest sendMessageRequest = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create("http://localhost:14967/session/" + sessionId + "/message"))
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody.toJSONString()))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// 发送请求并获取响应
|
|
||||||
HttpResponse<String> response = client.send(sendMessageRequest, HttpResponse.BodyHandlers.ofString());
|
|
||||||
|
|
||||||
// 解析响应,提取AI回复
|
|
||||||
return parseAIResponse(response.body());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseSessionId(String responseBody) {
|
|
||||||
// 简单解析JSON,提取sessionId
|
|
||||||
// 实际项目中建议使用JSON库
|
|
||||||
int idIndex = responseBody.indexOf("\"id\":\"");
|
|
||||||
if (idIndex != -1) {
|
|
||||||
int start = idIndex + 6;
|
|
||||||
int end = responseBody.indexOf("\"", start);
|
|
||||||
if (end != -1) {
|
|
||||||
return responseBody.substring(start, end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseAIResponse(String responseBody) {
|
|
||||||
log.info("=================AI回复==========");
|
|
||||||
log.info(responseBody);
|
|
||||||
try {
|
|
||||||
// 解析响应,提取AI回复
|
|
||||||
JSONObject jsonObject = JSONObject.parseObject(responseBody);
|
|
||||||
if (jsonObject.containsKey("info")) {
|
|
||||||
JSONArray parts = jsonObject.getJSONArray("parts");
|
|
||||||
for (int i = 0; i < parts.size(); i++) {
|
|
||||||
JSONObject part = parts.getJSONObject(i);
|
|
||||||
if (part.containsKey("text")) {
|
|
||||||
return part.getString("text");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return "无法获取AI回复";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/disconnect")
|
@GetMapping("/disconnect")
|
||||||
public String disconnect(@RequestParam String clientId) {
|
public String disconnect(@RequestParam String clientId) {
|
||||||
SseEmitter emitter = emitters.remove(clientId);
|
SseEmitter emitter = emitters.remove(clientId);
|
||||||
|
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
//package cn.qihangerp.erp.filter;
|
|
||||||
//
|
|
||||||
//import cn.qihangerp.common.AjaxResult;
|
|
||||||
//import cn.qihangerp.common.enums.HttpStatus;
|
|
||||||
//import cn.qihangerp.security.LoginUser;
|
|
||||||
//import cn.qihangerp.security.TokenService;
|
|
||||||
//import com.alibaba.fastjson2.JSON;
|
|
||||||
//import jakarta.servlet.FilterChain;
|
|
||||||
//import jakarta.servlet.ServletException;
|
|
||||||
//import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
//import jakarta.servlet.http.HttpServletRequestWrapper;
|
|
||||||
//import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
//import org.slf4j.Logger;
|
|
||||||
//import org.slf4j.LoggerFactory;
|
|
||||||
//import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
//import org.springframework.http.MediaType;
|
|
||||||
//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
//import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
//import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
|
||||||
//import org.springframework.stereotype.Component;
|
|
||||||
//import org.springframework.web.filter.OncePerRequestFilter;
|
|
||||||
//
|
|
||||||
//import java.io.IOException;
|
|
||||||
//import java.io.PrintWriter;
|
|
||||||
//
|
|
||||||
///**
|
|
||||||
// * token过滤器 从URL参数中获取token并验证有效性
|
|
||||||
// *
|
|
||||||
// * @author qihang
|
|
||||||
// */
|
|
||||||
//@Component
|
|
||||||
//public class UrlTokenFilter extends OncePerRequestFilter {
|
|
||||||
// @Autowired
|
|
||||||
// private TokenService tokenService;
|
|
||||||
// private Logger log = LoggerFactory.getLogger(getClass());
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
|
||||||
// throws ServletException, IOException {
|
|
||||||
// // 从URL参数中获取token
|
|
||||||
// String token = request.getParameter("token");
|
|
||||||
// String url = request.getRequestURI();
|
|
||||||
//
|
|
||||||
// // 跳过登录等不需要token的请求
|
|
||||||
// if (url.contains("/login") || url.contains("/captchaImage")) {
|
|
||||||
// chain.doFilter(request, response);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 如果URL参数中没有token,尝试从header中获取(保持兼容性)
|
|
||||||
// if (token == null || token.isEmpty()) {
|
|
||||||
// token = request.getHeader("Authorization");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 验证token
|
|
||||||
// if (token != null && !token.isEmpty()) {
|
|
||||||
// // 移除Bearer前缀
|
|
||||||
// if (token.startsWith("Bearer ")) {
|
|
||||||
// token = token.substring(7);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 将token声明为final,以便内部类可以引用
|
|
||||||
// final String finalToken = token;
|
|
||||||
//
|
|
||||||
// // 将token设置到请求的header中,以便TokenService能够正常工作
|
|
||||||
// final HttpServletRequest modifiedRequest = new HttpServletRequestWrapper(request) {
|
|
||||||
// @Override
|
|
||||||
// public String getHeader(String name) {
|
|
||||||
// if ("Authorization".equals(name)) {
|
|
||||||
// return "Bearer " + finalToken;
|
|
||||||
// }
|
|
||||||
// return super.getHeader(name);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// // 验证token并设置用户信息
|
|
||||||
// try {
|
|
||||||
// LoginUser loginUser = tokenService.getLoginUser(modifiedRequest);
|
|
||||||
// if (loginUser != null) {
|
|
||||||
// tokenService.verifyToken(loginUser);
|
|
||||||
// UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
|
|
||||||
// authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(modifiedRequest));
|
|
||||||
// SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
|
||||||
// chain.doFilter(modifiedRequest, response);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// log.error("Token validation failed: {}", e.getMessage());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // token无效或不存在
|
|
||||||
// fallback("授权过期!", response);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void fallback(String message, HttpServletResponse response) {
|
|
||||||
// response.setCharacterEncoding("UTF-8");
|
|
||||||
// response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
|
||||||
// PrintWriter writer = null;
|
|
||||||
// try {
|
|
||||||
// if (message == null) {
|
|
||||||
// message = "401 Forbidden";
|
|
||||||
// }
|
|
||||||
// AjaxResult res = AjaxResult.error(HttpStatus.UNAUTHORIZED, message);
|
|
||||||
// writer = response.getWriter();
|
|
||||||
// writer.append(JSON.toJSONString(res));
|
|
||||||
// } catch (IOException e) {
|
|
||||||
// log.error(e.getMessage());
|
|
||||||
// } finally {
|
|
||||||
// if (writer != null) {
|
|
||||||
// writer.close();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package cn.qihangerp.erp.serviceImpl;
|
||||||
|
|
||||||
|
import cn.qihangerp.common.ResultVo;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI服务类,直接调用Ollama API处理聊天内容
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class AiService {
|
||||||
|
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
private final String ollamaUrl;
|
||||||
|
|
||||||
|
public AiService() {
|
||||||
|
this.httpClient = HttpClient.newHttpClient();
|
||||||
|
this.ollamaUrl = "http://localhost:11434/api/generate";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理聊天消息
|
||||||
|
* @param message 用户消息
|
||||||
|
* @return AI回复
|
||||||
|
*/
|
||||||
|
public String processMessage(String message) {
|
||||||
|
try {
|
||||||
|
// 构建请求体
|
||||||
|
JSONObject requestBody = new JSONObject();
|
||||||
|
requestBody.put("model", "llama3");
|
||||||
|
requestBody.put("prompt", message);
|
||||||
|
requestBody.put("stream", false);
|
||||||
|
requestBody.put("temperature", 0.7);
|
||||||
|
|
||||||
|
// 创建HTTP请求
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(ollamaUrl))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(requestBody.toJSONString()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 发送请求并获取响应
|
||||||
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
JSONObject responseBody = JSONObject.parseObject(response.body());
|
||||||
|
|
||||||
|
// 检查是否有错误
|
||||||
|
if (responseBody.containsKey("error")) {
|
||||||
|
String errorMessage = responseBody.getString("error");
|
||||||
|
return "错误: " + errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseBody.getString("response");
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return "抱歉,我暂时无法处理您的请求,请稍后重试。";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue