接口风格应该都是openAI
一、后端
后端使用常规的spring boot,需要检查安装包,需要使用到webFlux+SseEmitter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
controller层
@PostMapping(path = "/test", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter queryPage(@RequestBody TestParam param){
return testService.test(param);
}
service层
import cn.hutool.json.JSONObject;
import cn.kunming.kgoa.service.aihelper.api.model.param.TestParam;
import cn.kunming.kgoa.service.common.api.utils.SecurityUtil;
import io.netty.channel.ChannelOption;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Flux;
import reactor.netty.http.client.HttpClient;
public SseEmitter test(TestParam param){
SseEmitter emitter = new SseEmitter(300_000L);
HttpClient httpClient = HttpClient.create().tcpConfiguration(tcpClient -> tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000));
WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl("http://192.168.70.226/v1")
.build();
ParameterizedTypeReference<ServerSentEvent<String>> type = new ParameterizedTypeReference<ServerSentEvent<String>>() {
};
JSONObject body = new JSONObject();
body.set("inputs", new JSONObject());
body.set("query", param.getContent());
body.set("user", SecurityUtil.getUserId().toString());
body.set("response_mode", "streaming");
body.set("conversation_id", param.getConversationId());
log.info("Dify chat body: {}", body);
Flux<ServerSentEvent<String>> eventStream = client.post()
.uri("/chat-messages")
.header("Authorization", "Bearer " + "app-dgEzITfxrVqxrgSyRnS7p3I1")
.header("Content-Type", "application/json")
.bodyValue(body.toString())
.retrieve()
.bodyToFlux(type);
eventStream.subscribe((content) -> {
String data = content.data();
log.info("收到数据:"+data);
}, emitter::completeWithError, emitter::complete);
return emitter;
}
二、前端
前端需要使用fetch-event-source框架,因为需要使用post进行参数传递,默认的SSE是不支持post方法的。
npm install @microsoft/fetch-event-source
实现代码
import { fetchEventSource } from "@microsoft/fetch-event-source";
const ctrl = new AbortController();
let content = ref("你好");
let send = function (content) {
var url =
"http://localhost:8001/kgoa-service-aihelper/aihelper/test/test";
fetchEventSource(url, {
method: "POST",
headers: {
Accept: "*/*",
Connection: "keep-alive",
"Content-Type": "application/json",
Authorization: "pc_3aaac4d1343a3526cc86dd1d0f4eda35",
},
body: JSON.stringify({
content: content,
}),
signal: ctrl.signal,
async onopen(response) {
console.log("Connection to server opened.");
},
onmessage(msg) {
let data = JSON.parse(msg.data);
console.log(data);
},
onclose() {
// if the server closes the connection unexpectedly, retry:
},
onerror(event) {
console.log(event);
if (event.target.readyState === EventSource.CLOSED) {
console.error("EventSource connection closed by the server.");
} else if (event.target.readyState === 0) {
console.log("对方回答结束...关闭...");
}
},
});
};