聊天后端
一些相关配置
直接照抄
HttpsClientRequestFactory
TLS的三个作用: (1)身份认证 通过证书认证来确认对方的身份,防止中间人攻击 (2)数据私密性 使用对称性密钥加密传输的数据,由于密钥只有客户端/服务端有,其他人无法窥探。 (3)数据完整性 使用摘要算法对报文进行计算,收到消息后校验该值防止数据被篡改或丢失。
代码
public class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory {
@Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
try {
if (!(connection instanceof HttpsURLConnection)) {
throw new RuntimeException("An instance of HttpsURLConnection is expected");
}
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
httpsConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
super.prepareConnection(httpsConnection, httpMethod);
} catch (Exception e) {
e.printStackTrace();
}
}
private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
// 返回默认启用的密码套件。除非一个列表启用,对SSL连接的握手会使用这些密码套件。
// 这些默认的服务的最低质量要求保密保护和服务器身份验证
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
// 返回的密码套件可用于SSL连接启用的名字
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(final Socket socket, final String host, final int port,
final boolean autoClose) throws IOException {
final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final String host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final String host, final int port, final InetAddress localAddress,
final int localPort) throws
IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final InetAddress host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress,
final int localPort) throws
IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
private Socket overrideProtocol(final Socket socket) {
if (!(socket instanceof SSLSocket)) {
throw new RuntimeException("An instance of SSLSocket is expected");
}
//((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.2"});
((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"});
return socket;
}
}
}
RestTemplate
使用RestTemplate进行HTTPS请求访问:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
一些工具类
/** * 向ChatGLM发送https请求 * @param url 请求路径 * @param jsonObject 请求体 * @return */
参考ChatGLM官网说明
public static Object post(String url, JSONObject jsonObject) {
RestTemplate restTemplate = new RestTemplate(new HttpsClientRequestFactory());
HttpHeaders headers = new HttpHeaders();
Map<String, Object> payload = new HashMap<>();
payload.put("api_key", id);
long expMillis = System.currentTimeMillis() + 100000;
payload.put("exp", expMillis);
payload.put("timestamp", System.currentTimeMillis());
String token = JwtUtil.createJWT(secret, 100000, payload);
headers.add("Authorization", "Bearer " + token);
headers.add("Content-Type"," application/json");
HttpEntity<JSONObject> httpEntity = new HttpEntity<>(jsonObject,headers);
ResponseEntity response=restTemplate.postForEntity(url,httpEntity,Object.class);
return response.getBody();
}
Controller层
与前端交互,将传来的数据丢给service层,然后将返回值传回前端
public class PartnerController {
@Autowired
private PartnerService partnerService;
@PostMapping("/chat")
public Result chat(@RequestBody ChatRequestDTO dto) throws Exception {
String result = partnerService.chatMessage(dto);
if(result!=null){
return Result.success(result);
}else{
return Result.error("-1","连接失败");
}
}
}
Service层
主要就是将传来的数据进行一些包装,转变成chatGLM需要的形式
首先对chatGLM进行一个角色设定
MessageDTO messageDTO = new MessageDTO("system","你是一位心理助手,可以帮助安慰你的对话者,同时回答他/她的问题");
先用list类型存起来
其他的就是转换成JSON格式,一些按部就班地操作,最后获得返回来的数据,一切操作都是按照官方文档上的说明来的。
dto.getMessages().add(messageDTO);
JSONObject jsonObject = new JSONObject();
jsonObject.put("model","glm-4");
jsonObject.put("messages",dto.getMessages());
System.out.println(jsonObject.toString());
Object result = ChatGLMHttpsUtil.post("https://open.bigmodel.cn/api/paas/v4/chat/completions",jsonObject);
JSONObject response = (JSONObject) JSONObject.toJSON(result);
String report;
try{
//将JSON字符串解析为 JSONObject
JSONObject jsonObject1 = new JSONObject(response);
JSONArray choicesArray = jsonObject1.getJSONArray("choices");
JSONObject firstChoice = choicesArray.getJSONObject(0);
JSONObject messageObject = firstChoice.getJSONObject("message");
report = messageObject.getString("content");
}catch (JSONException e){
e.printStackTrace();
return null;
}
return report;
}
上下文调用AI
试用时发现系统只能一次发送一个问题,ai无法对问题进行连续回答,由此,进一步优化
前端
data中多了一个 list: [],
主要用来存储用户发过的信息和ai的回答,方便一起传回后端
this.request
.post("/partner/chat", {
messages: this.list
})
.then((res) => {
if (res.data.code === "0") {
//console.log(this.data.data);
this.list.push(
{
role: "assistant",
content: res.data.data
}
);
this.messages.push({
text: res.data.data,
author: "assistant",
});
} else if (res.data.code === "-1") {
//this.$message.warning("出错");
}
之前传的只是单条信息,是String类型的,现在将以前的信息一并传回后端,list类型
然后就是接收时,同时在list和messages中更新数据,方便下回传数据,和显示
前端的改动就这些
后端
entity层
以前传递的数据是String类型的,改为自定义类型
@Data
@AllArgsConstructor// lombox 把类里面的变量全变成全参数的构造器
public class MessageDTO implements Serializable {
// private static final Long serialVersionUID = 1L;
private String role;
private String content;
}
Controller层
Service层
相比之前,除了消息体的一些必要改动外,只是多了一个函数检查消息参数是否符合规范
即消息个数必须为奇数
if (messages.size() % 2 == 0) {
throw new RuntimeException("messages参数个数必须为奇数");
}
messages奇数参数的role必须为user,messages偶数参数的role必须为assistant
for (int i = 0; i < messages.size(); i++) {
if (i % 2 == 0) {
if (!"user".equals(messages.get(i).getRole())) {
throw new RuntimeException("messages奇数参数的role必须为user");
}
} else {
if (!"assistant".equals(messages.get(i).getRole())) {
throw new RuntimeException("messages偶数参数的role必须为assistant");
}
}
}
结果展示
总结
本周是对ai陪聊的后端实现,及所发现的问题的一些改进
总得来说,本周最大的难题是对如何调用ChatGLM的学习,尤其是有关如何发起HTTP请求和阅读理解官方文档上,在此基础上,完成了本次后端任务,至于具体业务逻辑,比较简单。