一、安装依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.13</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- Generated by https://start.springboot.io -->
<!-- 优质的 spring/boot/data/security/cloud 框架中文文档尽在 => https://springdoc.cn -->
<groupId>com.example</groupId>
<artifactId>xunfeigpt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>xunfeigpt</name>
<description>xunfeigpt</description>
<properties>
<java.version>1.8</java.version>
<netty.verson>4.1.45.Final</netty.verson>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.verson}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
二、配置文件
在科大讯飞官网申请key
server:
port: 1644
xf:
config:
hostUrl: https://spark-api.xf-yun.com/v3.5/chat
appId: xxxxxxx
apiSecret: xxxxxxxx
apiKey: xxxxxxx
maxResponseTime: 30
三、项目结构
四、各个模块代码
1、在bean目录下
1)Choices类
package com.example.xunfeigpt.bean;
import lombok.Data;
import java.util.List;
@Data
public class Choices {
private List<Text> text;
}
2)Header类型
import lombok.Data;
@Data
public class Header {
private int code;
private int status;
private String sid;
}
3)JsonParse类型
import lombok.Data;
@Data
public class JsonParse {
private Header header;
private Payload payload;
}
4)NettyGroup类型
package com.example.xunfeigpt.bean;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.util.concurrent.ConcurrentHashMap;
public class NettyGroup {
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
* 存放用户与Chanel的对应信息,用于给指定用户发送消息
*/
private static ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
private NettyGroup() {
}
/**
* 获取channel组
*/
public static ChannelGroup getChannelGroup() {
return channelGroup;
}
/**
* 获取连接channel map
*/
public static ConcurrentHashMap<String, Channel> getUserChannelMap() {
return channelMap;
}
}
5)Payload类型
import lombok.Data;
@Data
public class Payload {
private Choices choices;
}
6)ResultBean类型
package com.example.xunfeigpt.bean;
import lombok.*;
/**
* @Author: ChengLiang
* @CreateTime: 2023-05-22 11:04
* @Description: TODO
* @Version: 1.0
*/
@Getter
@Setter
@ToString(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
public class ResultBean<T> {
private String errorCode;
private String message;
private T data;
public ResultBean(T data) {
this.errorCode = ErrorMessage.SUCCESS.getErrorCode();
this.message = ErrorMessage.SUCCESS.getMessage();
this.data = data;
}
public ResultBean(ErrorMessage errorMessage, T data) {
this.errorCode = errorMessage.getErrorCode();
this.message = errorMessage.getMessage();
this.data = data;
}
public static <T> ResultBean success(T data) {
ResultBean resultBean = new ResultBean(data);
return resultBean;
}
public static <T> ResultBean fail(T data) {
ResultBean resultBean = new ResultBean(ErrorMessage.FAIL.getErrorCode(), ErrorMessage.FAIL.getMessage(), data);
return resultBean;
}
public enum ErrorMessage {
SUCCESS("0", "success"),
FAIL("001", "fail"),
NOAUTH("1001", "非法访问");
private String errorCode;
private String message;
ErrorMessage(String errorCode, String message) {
this.errorCode = errorCode;
this.message = message;
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
}
7)RoleContent类型
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RoleContent {
public static final String ROLE_USER = "user";
public static final String ROLE_ASSISTANT = "assistant";
private String role;
private String content;
public static RoleContent createUserRoleContent(String content) {
return new RoleContent(ROLE_USER, content);
}
public static RoleContent createAssistantRoleContent(String content) {
return new RoleContent(ROLE_ASSISTANT, content);
}
}
8)Text类型
import lombok.Data;
@Data
public class Text {
private String role;
private String content;
}
2、在config目录下新建XFConfig类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties("xf.config")
public class XFConfig {
private String appId;
private String apiSecret;
private String apiKey;
private String hostUrl;
private Integer maxResponseTime;
}
3、在listener目录下
1)新建XFWebClient类
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.example.xunfeigpt.bean.RoleContent;
import com.example.xunfeigpt.config.XFConfig;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
@Slf4j
@Component
public class XFWebClient {
@Autowired
private XFConfig xfConfig;
/**
* 发送消息
*
* @param uid 每个用户的id,用于区分不同用户
* @param questions 发送给大模型的消息,可以包含上下文内容
* @return 获取websocket连接,以便于我们在获取完整大模型回复后手动关闭连接
*/
/**
* @description: 发送请求至大模型方法
* @author: ChengLiang
* @date: 2023/10/19 16:27
* @param: [用户id, 请求内容, 返回结果监听器listener]
* @return: okhttp3.WebSocket
**/
public WebSocket sendMsg(String uid, List<RoleContent> questions, WebSocketListener listener) {
// 获取鉴权url
String authUrl = null;
try {
authUrl = getAuthUrl(xfConfig.getHostUrl(), xfConfig.getApiKey(), xfConfig.getApiSecret());
} catch (Exception e) {
log.error("鉴权失败:{}", e);
return null;
}
// 鉴权方法生成失败,直接返回 null
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
// 将 https/http 连接替换为 ws/wss 连接
String url = authUrl.replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(url).build();
// 建立 wss 连接
WebSocket webSocket = okHttpClient.newWebSocket(request, listener);
// 组装请求参数
JSONObject requestDTO = createRequestParams(uid, questions);
// 发送请求
webSocket.send(JSONObject.toJSONString(requestDTO));
return webSocket;
}
/**
* @description: 鉴权方法
* @author: ChengLiang
* @date: 2023/10/19 16:25
* @param: [讯飞大模型请求地址, apiKey, apiSecret]
* @return: java.lang.String
**/
public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {
URL url = new URL(hostUrl);
// 时间
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
String date = format.format(new Date());
// 拼接
String preStr = "host: " + url.getHost() + "\n" +
"date: " + date + "\n" +
"GET " + url.getPath() + " HTTP/1.1";
// SHA256加密
Mac mac = Mac.getInstance("hmacsha256");
SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "hmacsha256");
mac.init(spec);
byte[] hexDigits = mac.doFinal(preStr.getBytes(StandardCharsets.UTF_8));
// Base64加密
String sha = Base64.getEncoder().encodeToString(hexDigits);
// 拼接
String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);
// 拼接地址
HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.parse("https://" + url.getHost() + url.getPath())).newBuilder().//
addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(StandardCharsets.UTF_8))).//
addQueryParameter("date", date).//
addQueryParameter("host", url.getHost()).//
build();
return httpUrl.toString();
}
/**
* @description: 请求参数组装方法
* @author: ChengLiang
* @date: 2023/10/19 16:26
* @param: [用户id, 请求内容]
* @return: com.alibaba.fastjson.JSONObject
**/
public JSONObject createRequestParams(String uid, List<RoleContent> questions) {
JSONObject requestJson = new JSONObject();
// header参数
JSONObject header = new JSONObject();
header.put("app_id", xfConfig.getAppId());
header.put("uid", uid);
// parameter参数
JSONObject parameter = new JSONObject();
JSONObject chat = new JSONObject();
chat.put("domain", "generalv2");
chat.put("temperature", 0.5);
chat.put("max_tokens", 4096);
parameter.put("chat", chat);
// payload参数
JSONObject payload = new JSONObject();
JSONObject message = new JSONObject();
JSONArray jsonArray = new JSONArray();
jsonArray.addAll(questions);
message.put("text", jsonArray);
payload.put("message", message);
requestJson.put("header", header);
requestJson.put("parameter", parameter);
requestJson.put("payload", payload);
return requestJson;
}
}
2)新建XFWebSocketListener类
package com.example.xunfeigpt.listener;
import com.alibaba.fastjson.JSON;
import com.example.xunfeigpt.bean.JsonParse;
import com.example.xunfeigpt.bean.Text;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import java.io.IOException;
import java.util.*;
@Slf4j
public class XFWebSocketListener extends WebSocketListener {
//断开websocket标志位
private boolean wsCloseFlag = false;
//语句组装buffer,将大模型返回结果全部接收,在组装成一句话返回
private StringBuilder answer = new StringBuilder();
public String getAnswer() {
return answer.toString();
}
public boolean isWsCloseFlag() {
return wsCloseFlag;
}
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
log.info("大模型服务器连接成功!");
}
@Override
public void onMessage(WebSocket webSocket, String text) {
super.onMessage(webSocket, text);
JsonParse myJsonParse = JSON.parseObject(text, JsonParse.class);
log.info("myJsonParse:{}", JSON.toJSONString(myJsonParse));
if (myJsonParse.getHeader().getCode() != 0) {
log.error("发生错误,错误信息为:{}", JSON.toJSONString(myJsonParse.getHeader()));
this.answer.append("大模型响应异常,请联系管理员");
// 关闭连接标识
wsCloseFlag = true;
return;
}
List<Text> textList = myJsonParse.getPayload().getChoices().getText();
for (Text temp : textList) {
log.info("返回结果信息为:【{}】", JSON.toJSONString(temp));
this.answer.append(temp.getContent());
}
log.info("result:{}", this.answer.toString());
if (myJsonParse.getHeader().getStatus() == 2) {
wsCloseFlag = true;
//todo 将问答信息入库进行记录,可自行实现
}
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
super.onFailure(webSocket, t, response);
try {
if (null != response) {
int code = response.code();
log.error("onFailure body:{}", response.body().string());
if (101 != code) {
log.error("讯飞星火大模型连接异常");
}
}
} catch (IOException e) {
log.error("IO异常:{}", e);
}
}
}
4、在netty目录下
1)新建NettyServer类
package com.example.xunfeigpt.netty;
import com.example.xunfeigpt.netty.handler.WebSocketHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
@Slf4j
@Component
public class NettyServer {
/**
* webSocket协议名
*/
private static final String WEBSOCKET_PROTOCOL = "WebSocket";
/**
* 端口号
*/
@Value("${webSocket.netty.port:62632}")
private int port;
/**
* webSocket路径
*/
@Value("${webSocket.netty.path:/webSocket}")
private String webSocketPath;
@Autowired
private WebSocketHandler webSocketHandler;
private EventLoopGroup bossGroup;
private EventLoopGroup workGroup;
/**
* 启动
*
* @throws InterruptedException
*/
private void start() throws InterruptedException {
bossGroup = new NioEventLoopGroup();
workGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
// bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作
bootstrap.group(bossGroup, workGroup);
// 设置NIO类型的channel
bootstrap.channel(NioServerSocketChannel.class);
// 设置监听端口
bootstrap.localAddress(new InetSocketAddress(port));
// 连接到达时会创建一个通道
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 流水线管理通道中的处理程序(Handler),用来处理业务
// webSocket协议本身是基于http协议的,所以这边也要使用http编解码器
ch.pipeline().addLast(new HttpServerCodec());
ch.pipeline().addLast(new ObjectEncoder());
// 以块的方式来写的处理器
ch.pipeline().addLast(new ChunkedWriteHandler());
/*
说明:
1、http数据在传输过程中是分段的,HttpObjectAggregator可以将多个段聚合
2、这就是为什么,当浏览器发送大量数据时,就会发送多次http请求
*/
ch.pipeline().addLast(new HttpObjectAggregator(8192));
/*
说明:
1、对应webSocket,它的数据是以帧(frame)的形式传递
2、浏览器请求时 ws://localhost:58080/xxx 表示请求的uri
3、核心功能是将http协议升级为ws协议,保持长连接
*/
ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));
// 自定义的handler,处理业务逻辑
ch.pipeline().addLast(webSocketHandler);
}
});
// 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
ChannelFuture channelFuture = bootstrap.bind().sync();
log.info("Server started and listen on:{}", channelFuture.channel().localAddress());
// 对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}
/**
* 释放资源
*
* @throws InterruptedException
*/
@PreDestroy
public void destroy() throws InterruptedException {
if (bossGroup != null) {
bossGroup.shutdownGracefully().sync();
}
if (workGroup != null) {
workGroup.shutdownGracefully().sync();
}
}
@PostConstruct()
public void init() {
//需要开启一个新的线程来执行netty server 服务器
new Thread(() -> {
try {
start();
log.info("消息推送线程开启!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
2)在handler目录下新建handler类
package com.example.xunfeigpt.netty.handler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.xunfeigpt.bean.NettyGroup;
import com.example.xunfeigpt.bean.ResultBean;
import com.example.xunfeigpt.service.PushService;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@ChannelHandler.Sharable
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Autowired
private PushService pushService;
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
log.info("handlerAdded被调用,{}", JSON.toJSONString(ctx));
//todo 添加校验功能,校验合法后添加到group中
// 添加到channelGroup 通道组
NettyGroup.getChannelGroup().add(ctx.channel());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
log.info("服务器收到消息:{}", msg.text());
// 获取用户ID,关联channel
JSONObject jsonObject = JSON.parseObject(msg.text());
String channelId = jsonObject.getString("uid");
// 将用户ID作为自定义属性加入到channel中,方便随时channel中获取用户ID
AttributeKey<String> key = AttributeKey.valueOf("userId");
//String channelId = CharUtil.generateStr(uid);
NettyGroup.getUserChannelMap().put(channelId, ctx.channel());
boolean containsKey = NettyGroup.getUserChannelMap().containsKey(channelId);
//通道已存在,请求信息返回
if (containsKey) {
//接收消息格式{"uid":"123456","text":"中华人民共和国成立时间"}
String text = jsonObject.getString("text");
//请求大模型服务器,获取结果
ResultBean resultBean = pushService.pushMessageToXFServer(channelId, text);
String data = (String) resultBean.getData();
//推送
pushService.pushToOne(channelId, JSON.toJSONString(data));
} else {
ctx.channel().attr(key).setIfAbsent(channelId);
log.info("连接通道id:{}", channelId);
// 回复消息
ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(ResultBean.success(channelId))));
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
log.info("handlerRemoved被调用,{}", JSON.toJSONString(ctx));
// 删除通道
NettyGroup.getChannelGroup().remove(ctx.channel());
removeUserId(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.info("通道异常:{}", cause.getMessage());
// 删除通道
NettyGroup.getChannelGroup().remove(ctx.channel());
removeUserId(ctx);
ctx.close();
}
private void removeUserId(ChannelHandlerContext ctx) {
AttributeKey<String> key = AttributeKey.valueOf("userId");
String userId = ctx.channel().attr(key).get();
NettyGroup.getUserChannelMap().remove(userId);
}
}
5、在service目录下
1)新建PushService接口
import com.example.xunfeigpt.bean.ResultBean;
public interface PushService {
void pushToOne(String uid, String text);
void pushToAll(String text);
ResultBean pushMessageToXFServer(String uid, String text);
}
2)在impl文件夹下新建PushServiceImpl类
package com.example.xunfeigpt.service.impl;
import com.alibaba.fastjson.JSON;
import com.example.xunfeigpt.bean.NettyGroup;
import com.example.xunfeigpt.bean.ResultBean;
import com.example.xunfeigpt.bean.RoleContent;
import com.example.xunfeigpt.config.XFConfig;
import com.example.xunfeigpt.listener.XFWebClient;
import com.example.xunfeigpt.listener.XFWebSocketListener;
import com.example.xunfeigpt.service.PushService;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;
import okhttp3.WebSocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Service
public class PushServiceImpl implements PushService {
@Autowired
private XFConfig xfConfig;
@Autowired
private XFWebClient xfWebClient;
@Override
public void pushToOne(String uid, String text) {
if (StringUtils.isEmpty(uid) || StringUtils.isEmpty(text)) {
log.error("uid或text均不能为空");
throw new RuntimeException("uid或text均不能为空");
}
ConcurrentHashMap<String, Channel> userChannelMap = NettyGroup.getUserChannelMap();
for (String channelId : userChannelMap.keySet()) {
if (channelId.equals(uid)) {
Channel channel = userChannelMap.get(channelId);
if (channel != null) {
ResultBean success = ResultBean.success(text);
channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(success)));
log.info("信息发送成功:{}", JSON.toJSONString(success));
} else {
log.error("该id对于channelId不存在!");
}
return;
}
}
log.error("该用户不存在!");
}
@Override
public void pushToAll(String text) {
String trim = text.trim();
ResultBean success = ResultBean.success(trim);
NettyGroup.getChannelGroup().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(success)));
log.info("信息推送成功:{}", JSON.toJSONString(success));
}
//测试账号只有2个并发,此处只使用一个,若是生产环境允许多个并发,可以采用分布式锁
@Override
public synchronized ResultBean pushMessageToXFServer(String uid, String text) {
RoleContent userRoleContent = RoleContent.createUserRoleContent(text);
ArrayList<RoleContent> questions = new ArrayList<>();
questions.add(userRoleContent);
XFWebSocketListener xfWebSocketListener = new XFWebSocketListener();
WebSocket webSocket = xfWebClient.sendMsg(uid, questions, xfWebSocketListener);
if (webSocket == null) {
log.error("webSocket连接异常");
ResultBean.fail("请求异常,请联系管理员");
}
try {
int count = 0;
int maxCount = xfConfig.getMaxResponseTime() * 5;
while (count <= maxCount) {
Thread.sleep(200);
if (xfWebSocketListener.isWsCloseFlag()) {
break;
}
count++;
}
if (count > maxCount) {
return ResultBean.fail("响应超时,请联系相关人员");
}
return ResultBean.success(xfWebSocketListener.getAnswer());
} catch (Exception e) {
log.error("请求异常:{}", e);
} finally {
webSocket.close(1000, "");
}
return ResultBean.success("");
}
}
6、测试controller
import com.example.xunfeigpt.bean.ResultBean;
import com.example.xunfeigpt.service.PushService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/xfModel")
public class XFMessageController {
@Autowired
private PushService pushService;
@GetMapping("/test")
public ResultBean test(String uid, String text) {
if (StringUtils.isEmpty(uid) || StringUtils.isEmpty(text)) {
log.error("uid或text不能为空");
return ResultBean.fail("uid或text不能为空");
}
return pushService.pushMessageToXFServer(uid, text);
}
}
7、websocket测试
地址:ws://localhost:62632/webSocket
传参:
["uid":"xxxxxx","text";"水泥的种类有哪些?}