SSEUtils
package com.wantong.intelligent.util;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
public class SSEUtils {
private static Long DEFAULT_TIME_OUT = 1 * 60 * 1000L;
public static Map<String, SseEmitter> subscribeMap = new ConcurrentHashMap<>();
public static SseEmitter createConnect(String connectId) {
return createConnect(connectId, DEFAULT_TIME_OUT);
}
public static SseEmitter createConnect(String connectId, Long timeout) {
if (null == connectId || "".equals(connectId)) {
return null;
}
if (subscribeMap.containsKey(connectId)){
subscribeMap.get(connectId).complete();
}
SseEmitter emitter = new SseEmitter(timeout);
emitter.onCompletion(completionCallBack(connectId));
emitter.onTimeout(timeoutCallBack(connectId));
emitter.onError(errorCallback(connectId));
subscribeMap.put(connectId, emitter);
return emitter;
}
public static void sendMsg(String connectId, String msg) {
SseEmitter emitter = subscribeMap.get(connectId);
if (null != emitter) {
try {
emitter.send(SseEmitter.event().data(msg));
} catch (Exception e) {
closeConnect(connectId);
}
}
}
public static void closeConnect(String connectId) {
SseEmitter emitter = subscribeMap.get(connectId);
if (null != emitter) {
try {
emitter.complete();
} catch (Exception e) {
e.printStackTrace();
}
subscribeMap.remove(connectId);
}
}
private static Runnable completionCallBack(String connectId) {
return () -> {
closeConnect(connectId);
};
}
private static Runnable timeoutCallBack(String connectId) {
return () -> {
closeConnect(connectId);
};
}
private static Consumer<Throwable> errorCallback(String connectId) {
return throwable -> {
closeConnect(connectId);
};
}
}
调用
public SseEmitter streamBaike(JSONObject param) {
String uuid = UUIDGenerator.getUUID32();
Headers hds = new Headers.Builder()
.add("Content-Type", "application/json")
.add("Authorization", "Bearer " + miniMaxConfig.getApiKey())
.build();
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), JSONObject.toJSONString(param));
Request request = new Request.Builder().url(chatUrl).post(body).headers(hds).build();
buildBaikeEvent(uuid, request);
return SSEUtils.createConnect(uuid);
}
private void buildBaikeEvent(String connectId, Request request) {
EventSourceListener eventSourceListener = new EventSourceListener() {
@Override
public void onOpen(@NotNull EventSource eventSource, @NotNull okhttp3.Response response) {
log.info("baike connectId[{}] connection opened.", connectId);
}
@Override
public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, @NotNull String data) {
log.debug("baike connectId[{}] onEvent, data: \n{}", connectId, data);
MiniMaxChatResponse response = JSONObject.parseObject(data, MiniMaxChatResponse.class);
if (response == null || (response.getBaseResp() != null && response.getBaseResp().getStatusCode() != 0)
|| CollectionUtils.isEmpty(response.getChoices())) {
return;
}
BaikeContent respData = new BaikeContent();
MiniMaxChatResponse.MessageVO message = response.getChoices().get(0).getMessage();
if (message == null) {
respData.setStatus(1);
message = response.getChoices().get(0).getDelta();
} else {
respData.setStatus(2);
}
respData.setContent(message.getContent());
SSEUtils.sendMsg(connectId, JSONObject.toJSONString(respData));
}
@Override
public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable t, @Nullable okhttp3.Response response) {
log.error("baike connectId[{}] connection Failure.", connectId, t);
SSEUtils.closeConnect(connectId);
}
@Override
public void onClosed(@NotNull EventSource eventSource) {
log.info("baike connectId[{}] connection closed.", connectId);
SSEUtils.closeConnect(connectId);
}
};
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build();
EventSource.Factory factory = EventSources.createFactory(client);
factory.newEventSource(request, eventSourceListener);
}
nginx配置跨域、流式传输优化
server {
listen 80;
listen 443 ssl;
server_name ~^api([1-5]+)-intelligent\.fgbear\.cn$;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET,POST, HEAD, OPTIONS';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,token';
ssl_certificate cert/xxx.pem;
ssl_certificate_key cert/xxx.key;
index index.html;
location = /50x.html {
root html;
}
...
location ~ ^/base/(tts|baike) {
proxy_http_version 1.1;
proxy_set_header Connection '';
chunked_transfer_encoding on;
proxy_buffering off;
proxy_cache off;
proxy_pass http://api-intelligent-base;
}
...
}