feat: AI消息点赞/踩/取消点赞/取消踩/问题反馈信息保存功能完善

This commit is contained in:
郭诚奇
2025-12-04 14:25:39 +08:00
parent ee0a853b1b
commit b9fc27f4fb
4 changed files with 71 additions and 53 deletions

View File

@@ -89,6 +89,7 @@ public class CaseAiChatApi extends ApiBaseController {
@PostMapping("/likeMsg") @PostMapping("/likeMsg")
public JsonResponse<Boolean> likeMsg(@RequestBody CaseAiMsgLikeDto caseAiMsgLikeDto) { public JsonResponse<Boolean> likeMsg(@RequestBody CaseAiMsgLikeDto caseAiMsgLikeDto) {
try { try {
caseAiMsgLikeDto.setOperation(true);
if (caseAiChatService.msgFeedback(caseAiMsgLikeDto)) { if (caseAiChatService.msgFeedback(caseAiMsgLikeDto)) {
return success(true, "保存成功"); return success(true, "保存成功");
} else { } else {
@@ -109,6 +110,7 @@ public class CaseAiChatApi extends ApiBaseController {
@PostMapping("/msgFeedback") @PostMapping("/msgFeedback")
public JsonResponse<Boolean> msgFeedback(@RequestBody CaseAiMsgLikeDto caseAiMsgLikeDto) { public JsonResponse<Boolean> msgFeedback(@RequestBody CaseAiMsgLikeDto caseAiMsgLikeDto) {
try { try {
caseAiMsgLikeDto.setOperation(false);
if (caseAiChatService.msgFeedback(caseAiMsgLikeDto)) { if (caseAiChatService.msgFeedback(caseAiMsgLikeDto)) {
return success(true, "保存成功"); return success(true, "保存成功");
} else { } else {

View File

@@ -21,4 +21,12 @@ public class CaseAiMsgLikeDto {
* 反馈 * 反馈
*/ */
private String feedback; private String feedback;
/**
* 操作
* true: 点踩
* false: 反馈
* 为空:其他情况
*/
private Boolean operation;
} }

View File

@@ -130,7 +130,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
// 2. 查询历史 // 2. 查询历史
List<CaseAiMessageVo> historyMessages = elasticSearchIndexService.queryData(conversationId); List<CaseAiMessageVo> historyMessages = elasticSearchIndexService.queryData(conversationId);
// 3. 构建请求参数 // 3. 构建请求参数
String userId = currentUser.getCode(); String userId = currentUser.getCode();
@@ -165,7 +165,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
} }
String chatParamStr = chatParam.toJSONString(); String chatParamStr = chatParam.toJSONString();
log.info("案例问答接口请求参数: [{}]", chatParamStr); log.info("案例问答接口请求参数: [{}]", chatParamStr);
// 4. 设置请求头 // 4. 设置请求头
String accessToken; String accessToken;
try { try {
@@ -195,7 +195,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
RequestBody bodyRequestBody = RequestBody.create(chatParamStr, MediaType.parse("application/json")); RequestBody bodyRequestBody = RequestBody.create(chatParamStr, MediaType.parse("application/json"));
builder.post(bodyRequestBody); builder.post(bodyRequestBody);
Request request = builder.build(); Request request = builder.build();
// 7. 创建事件监听器 // 7. 创建事件监听器
EventSourceListener listener = new EventSourceListener() { EventSourceListener listener = new EventSourceListener() {
@Override @Override
@@ -257,14 +257,14 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
@Override @Override
public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, @NotNull String data) { public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, @NotNull String data) {
log.info("调用接口 [{}] 监听数据 id: [{}] type: [{}] data: [{}]", request.url(), id, type, data); log.info("调用接口 [{}] 监听数据 id: [{}] type: [{}] data: [{}]", request.url(), id, type, data);
try { try {
// 解析返回的数据 // 解析返回的数据
JSONObject jsonData = JSONObject.parseObject(data); JSONObject jsonData = JSONObject.parseObject(data);
if (jsonData.getBooleanValue("success") && jsonData.getIntValue("code") == 0) { if (jsonData.getBooleanValue("success") && jsonData.getIntValue("code") == 0) {
JSONObject responseData = jsonData.getJSONObject("data"); JSONObject responseData = jsonData.getJSONObject("data");
Integer status = responseData.getInteger("status"); Integer status = responseData.getInteger("status");
if (status != null) { if (status != null) {
CaseAiChatStatusEnum statusEnum = CaseAiChatStatusEnum.getByCode(status); CaseAiChatStatusEnum statusEnum = CaseAiChatStatusEnum.getByCode(status);
if (statusEnum == CaseAiChatStatusEnum.REFERS) { // 返回引用文件 if (statusEnum == CaseAiChatStatusEnum.REFERS) { // 返回引用文件
@@ -350,7 +350,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
saveConversationData(conversationData); saveConversationData(conversationData);
} }
}; };
// 8. 执行HTTP请求 // 8. 执行HTTP请求
OkHttpClient client = new OkHttpClient.Builder() OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS) .connectTimeout(60, TimeUnit.SECONDS)
@@ -361,16 +361,16 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
.build(); .build();
EventSource.Factory factory = EventSources.createFactory(client); EventSource.Factory factory = EventSources.createFactory(client);
factory.newEventSource(request, listener); factory.newEventSource(request, listener);
return sseEmitter; return sseEmitter;
} }
/** /**
* 获取或创建会话ID * 获取或创建会话ID
*/ */
private String getOrCreateConversationId(CaseAiChatDto caseAiChatDto, CurrentUser currentUser) { private String getOrCreateConversationId(CaseAiChatDto caseAiChatDto, CurrentUser currentUser) {
String conversationId = caseAiChatDto.getConversationId(); String conversationId = caseAiChatDto.getConversationId();
if (StringUtils.isEmpty(conversationId)) { if (StringUtils.isEmpty(conversationId)) {
// 新会话,调用创建会话接口 // 新会话,调用创建会话接口
String conversationName = "AI案例咨询-" + LocalDateTime.now().toString(); String conversationName = "AI案例咨询-" + LocalDateTime.now().toString();
@@ -388,39 +388,39 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) { try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
String url = caseAiProperties.getBaseUrl() + "/apigateway/knowledge/v1/conversation"; String url = caseAiProperties.getBaseUrl() + "/apigateway/knowledge/v1/conversation";
HttpPost httpPost = new HttpPost(url); HttpPost httpPost = new HttpPost(url);
// 设置请求头 // 设置请求头
String accessToken = aiAccessTokenService.getAccessToken(); String accessToken = aiAccessTokenService.getAccessToken();
String apiCode = caseAiProperties.getChatApiCode(); String apiCode = caseAiProperties.getChatApiCode();
httpPost.setHeader("access_token", accessToken); httpPost.setHeader("access_token", accessToken);
httpPost.setHeader("X-AI-ApiCode", apiCode); httpPost.setHeader("X-AI-ApiCode", apiCode);
httpPost.setHeader("Content-Type", "application/json"); httpPost.setHeader("Content-Type", "application/json");
// 设置请求体 // 设置请求体
JSONObject requestBody = new JSONObject(); JSONObject requestBody = new JSONObject();
requestBody.put("userId", userId); requestBody.put("userId", userId);
requestBody.put("name", conversationName); requestBody.put("name", conversationName);
StringEntity entity = new StringEntity(requestBody.toJSONString(), StandardCharsets.UTF_8); StringEntity entity = new StringEntity(requestBody.toJSONString(), StandardCharsets.UTF_8);
httpPost.setEntity(entity); httpPost.setEntity(entity);
try (CloseableHttpResponse response = httpClient.execute(httpPost)) { try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode(); int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
if (statusCode == 200) { if (statusCode == 200) {
JSONObject result = JSONObject.parseObject(responseBody); JSONObject result = JSONObject.parseObject(responseBody);
if (result.getIntValue("code") == 0 && result.getBooleanValue("success")) { if (result.getIntValue("code") == 0 && result.getBooleanValue("success")) {
JSONObject data = result.getJSONObject("data"); JSONObject data = result.getJSONObject("data");
String aiConversationId = data.getString("id"); String aiConversationId = data.getString("id");
String name = data.getString("name"); String name = data.getString("name");
// 保存到数据库 // 保存到数据库
CaseAiConversations conversation = new CaseAiConversations(); CaseAiConversations conversation = new CaseAiConversations();
conversation.setAiConversationId(aiConversationId); conversation.setAiConversationId(aiConversationId);
conversation.setConversationName(name); conversation.setConversationName(name);
conversation.setConversationUser(userId); conversation.setConversationUser(userId);
caseAiConversationsDao.save(conversation); caseAiConversationsDao.save(conversation);
log.info("创建AI会话成功aiConversationId: {}, name: {}", aiConversationId, name); log.info("创建AI会话成功aiConversationId: {}, name: {}", aiConversationId, name);
return conversation; return conversation;
} else { } else {
@@ -503,44 +503,44 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
CaseAiMessageVo messageVo = new CaseAiMessageVo(); CaseAiMessageVo messageVo = new CaseAiMessageVo();
messageVo.setQuery((String) sourceMap.get("query")); messageVo.setQuery((String) sourceMap.get("query"));
messageVo.setAnswer((String) sourceMap.get("answer")); messageVo.setAnswer((String) sourceMap.get("answer"));
// 解析 suggestions // 解析 suggestions
Object suggestionsObj = sourceMap.get("suggestions"); Object suggestionsObj = sourceMap.get("suggestions");
if (suggestionsObj instanceof List) { if (suggestionsObj instanceof List) {
messageVo.setSuggestions((List<String>) suggestionsObj); messageVo.setSuggestions((List<String>) suggestionsObj);
} }
// 解析 caseRefer // 解析 caseRefer
Object caseReferObj = sourceMap.get("caseRefer"); Object caseReferObj = sourceMap.get("caseRefer");
if (caseReferObj instanceof List) { if (caseReferObj instanceof List) {
List<CaseReferVo> caseReferList = new ArrayList<>(); List<CaseReferVo> caseReferList = new ArrayList<>();
List<Map<String, Object>> caseReferMaps = (List<Map<String, Object>>) caseReferObj; List<Map<String, Object>> caseReferMaps = (List<Map<String, Object>>) caseReferObj;
for (Map<String, Object> caseReferMap : caseReferMaps) { for (Map<String, Object> caseReferMap : caseReferMaps) {
CaseReferVo caseRefer = new CaseReferVo(); CaseReferVo caseRefer = new CaseReferVo();
caseRefer.setCaseId((String) caseReferMap.get("caseId")); caseRefer.setCaseId((String) caseReferMap.get("caseId"));
caseRefer.setTitle((String) caseReferMap.get("title")); caseRefer.setTitle((String) caseReferMap.get("title"));
caseRefer.setAuthorName((String) caseReferMap.get("authorName")); caseRefer.setAuthorName((String) caseReferMap.get("authorName"));
caseRefer.setContent((String) caseReferMap.get("content")); caseRefer.setContent((String) caseReferMap.get("content"));
// 解析 keywords // 解析 keywords
Object keywordsObj = caseReferMap.get("keywords"); Object keywordsObj = caseReferMap.get("keywords");
if (keywordsObj instanceof List) { if (keywordsObj instanceof List) {
caseRefer.setKeywords((List<String>) keywordsObj); caseRefer.setKeywords((List<String>) keywordsObj);
} }
caseReferList.add(caseRefer); caseReferList.add(caseRefer);
} }
messageVo.setCaseRefer(caseReferList); messageVo.setCaseRefer(caseReferList);
} }
return messageVo; return messageVo;
} catch (Exception e) { } catch (Exception e) {
log.error("解析ES消息数据异常", e); log.error("解析ES消息数据异常", e);
return null; return null;
} }
} }
/** /**
* 处理文件引用并构建返给前端的响应数据 * 处理文件引用并构建返给前端的响应数据
*/ */
@@ -549,7 +549,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
// 先处理文件引用收集CaseReferVo数据 // 先处理文件引用收集CaseReferVo数据
List<CaseReferVo> currentCaseRefers = new ArrayList<>(); List<CaseReferVo> currentCaseRefers = new ArrayList<>();
Set<String> docIds = new HashSet<>(); Set<String> docIds = new HashSet<>();
JSONObject fileRefer = responseData.getJSONObject("fileRefer"); JSONObject fileRefer = responseData.getJSONObject("fileRefer");
if (fileRefer != null && fileRefer.containsKey("files")) { if (fileRefer != null && fileRefer.containsKey("files")) {
JSONArray files = fileRefer.getJSONArray("files"); JSONArray files = fileRefer.getJSONArray("files");
@@ -567,13 +567,13 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
} }
} }
} }
// 构建返给前端的数据结构 // 构建返给前端的数据结构
JSONObject data = new JSONObject(); JSONObject data = new JSONObject();
data.put("status", 0); data.put("status", 0);
data.put("conversationId", conversationData.getConversationId()); data.put("conversationId", conversationData.getConversationId());
data.put("content", responseData.getString("content")); data.put("content", responseData.getString("content"));
// 添加处理后的案例引用数据 // 添加处理后的案例引用数据
JSONArray caseReferArray = new JSONArray(); JSONArray caseReferArray = new JSONArray();
for (CaseReferVo caseRefer : currentCaseRefers) { for (CaseReferVo caseRefer : currentCaseRefers) {
@@ -589,11 +589,11 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
caseReferArray.add(caseReferObj); caseReferArray.add(caseReferObj);
} }
// 构建新的fileRefer结构包含案例引用 // 构建新的fileRefer结构包含案例引用
JSONObject newFileRefer = new JSONObject(); JSONObject newFileRefer = new JSONObject();
newFileRefer.put("caseRefers", caseReferArray); newFileRefer.put("caseRefers", caseReferArray);
// 保留原始的docs和files信息如果需要 // 保留原始的docs和files信息如果需要
if (fileRefer != null) { if (fileRefer != null) {
if (fileRefer.containsKey("docs")) { if (fileRefer.containsKey("docs")) {
@@ -603,19 +603,19 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
newFileRefer.put("files", fileRefer.get("files")); newFileRefer.put("files", fileRefer.get("files"));
} }
} }
data.put("fileRefer", newFileRefer); data.put("fileRefer", newFileRefer);
data.put("suggestions", responseData.get("suggestions")); data.put("suggestions", responseData.get("suggestions"));
log.info("处理文件引用成功,返回 {} 个案例引用", currentCaseRefers.size()); log.info("处理文件引用成功,返回 {} 个案例引用", currentCaseRefers.size());
return data; return data;
} catch (Exception e) { } catch (Exception e) {
log.error("处理文件引用并构建响应数据异常", e); log.error("处理文件引用并构建响应数据异常", e);
return null; return null;
} }
} }
/** /**
* 处理文件引用(原方法,保留用于数据收集) * 处理文件引用(原方法,保留用于数据收集)
*/ */
@@ -641,7 +641,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
log.error("处理文件引用异常", e); log.error("处理文件引用异常", e);
} }
} }
/** /**
* 处理建议 * 处理建议
*/ */
@@ -660,7 +660,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
log.error("处理建议异常", e); log.error("处理建议异常", e);
} }
} }
/** /**
* 根据docId查询案例引用信息 * 根据docId查询案例引用信息
*/ */
@@ -671,7 +671,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
if (docLog == null) { if (docLog == null) {
return null; return null;
} }
// 根据 case_id 查询案例详情 // 根据 case_id 查询案例详情
Cases caseEntity = casesDao.get(docLog.getCaseId()); Cases caseEntity = casesDao.get(docLog.getCaseId());
if (caseEntity == null) { if (caseEntity == null) {
@@ -688,7 +688,7 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
caseRefer.setContent(caseEntity.getSummary()); caseRefer.setContent(caseEntity.getSummary());
caseRefer.setUploadTime(caseEntity.getSysCreateTime()); caseRefer.setUploadTime(caseEntity.getSysCreateTime());
caseRefer.setOrgInfo(authorOrg.getName()); caseRefer.setOrgInfo(authorOrg.getName());
// 构建关键词列表 // 构建关键词列表
List<String> keywords = new ArrayList<>(); List<String> keywords = new ArrayList<>();
if (caseEntity.getKeyword1() != null) keywords.add(caseEntity.getKeyword1()); if (caseEntity.getKeyword1() != null) keywords.add(caseEntity.getKeyword1());
@@ -697,14 +697,14 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
if (caseEntity.getKeyword4() != null) keywords.add(caseEntity.getKeyword4()); if (caseEntity.getKeyword4() != null) keywords.add(caseEntity.getKeyword4());
if (caseEntity.getKeyword5() != null) keywords.add(caseEntity.getKeyword5()); if (caseEntity.getKeyword5() != null) keywords.add(caseEntity.getKeyword5());
caseRefer.setKeywords(keywords); caseRefer.setKeywords(keywords);
return caseRefer; return caseRefer;
} catch (Exception e) { } catch (Exception e) {
log.error("根据docId查询案例引用信息异常", e); log.error("根据docId查询案例引用信息异常", e);
return null; return null;
} }
} }
/** /**
* 当 SSE 失败时,作为普通 HTTP 请求处理 * 当 SSE 失败时,作为普通 HTTP 请求处理
* 不再使用 * 不再使用
@@ -717,12 +717,12 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
.writeTimeout(60, TimeUnit.SECONDS) .writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS)
.build(); .build();
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
if (response.isSuccessful()) { if (response.isSuccessful()) {
String responseBody = response.body().string(); String responseBody = response.body().string();
log.info("作为普通 HTTP 请求处理成功,将响应原封不动推送给前端"); log.info("作为普通 HTTP 请求处理成功,将响应原封不动推送给前端");
// 将响应内容原封不动地推送到 SseEmitter // 将响应内容原封不动地推送到 SseEmitter
JSONObject responseData = JSONObject.parseObject(responseBody); JSONObject responseData = JSONObject.parseObject(responseBody);
if (responseBody.contains("message")) { if (responseBody.contains("message")) {
@@ -810,9 +810,17 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
log.error("操作失败docId为空"); log.error("操作失败docId为空");
return false; return false;
} }
if ((StringUtils.equalsAny(caseAiMsgLikeDto.getLikeStatus(), "0", "1", "-1")) || StringUtils.isBlank(caseAiMsgLikeDto.getFeedback())) { if (Boolean.TRUE.equals(caseAiMsgLikeDto.getOperation())) {
log.error("操作失败,参数错误"); String likeStatus = caseAiMsgLikeDto.getLikeStatus();
return false; if (!StringUtils.equals(likeStatus, "1") && !StringUtils.equals(likeStatus, "0") && !StringUtils.equals(likeStatus, "-1")) {
log.error("操作失败,参数错误");
return false;
}
} else if (Boolean.FALSE.equals(caseAiMsgLikeDto.getOperation())) {
if (StringUtils.isBlank(caseAiMsgLikeDto.getFeedback())) {
log.error("操作失败,参数错误");
return false;
}
} }
aiChatConversationData.setLikeStatus(caseAiMsgLikeDto.getLikeStatus()); aiChatConversationData.setLikeStatus(caseAiMsgLikeDto.getLikeStatus());
aiChatConversationData.setFeedback(caseAiMsgLikeDto.getFeedback()); aiChatConversationData.setFeedback(caseAiMsgLikeDto.getFeedback());

View File

@@ -11,22 +11,22 @@ spring:
cloud: cloud:
nacos: nacos:
discovery: discovery:
server-addr: 192.168.0.253:8848 server-addr: 192.168.10.74:8848
config: config:
server-addr: 192.168.0.253:8848 server-addr: 192.168.10.74:8848
redis: redis:
database: 1 database: 2
host: 192.168.0.253 host: 39.104.123.58
password: boe@123 password: Ebiz2020
port: 6379 port: 6378
jpa: jpa:
hibernate: hibernate:
ddl-auto: none ddl-auto: none
datasource: datasource:
driverClassName: com.mysql.jdbc.Driver driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.0.253:3306/boe_base?useSSL=false&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull url: jdbc:mysql://rm-hp3cpkk0u50q90eu9vo.mysql.huhehaote.rds.aliyuncs.com:3306/ebiz_doc_manage?characterEncoding=utf8&useUnicode=true&serverTimezone=Asia/Shanghai&useSSL=false&allowMultiQueries=true
username: root username: ebiz_ai
password: boe#1234A password: ebiz_ai123
type: com.zaxxer.hikari.HikariDataSource type: com.zaxxer.hikari.HikariDataSource
hikari: hikari:
auto-commit: true auto-commit: true
@@ -36,7 +36,7 @@ spring:
max-lifetime: 1800000 max-lifetime: 1800000
maximum-pool-size: 20 maximum-pool-size: 20
activemq: activemq:
broker-url: tcp://192.168.0.253:61616 broker-url: tcp://192.168.10.74:61616
user: admin user: admin
password: admin password: admin
jms: jms:
@@ -113,7 +113,7 @@ aop-log-record:
#不进行拦截的包或者类 #不进行拦截的包或者类
excludeClassNames: excludeClassNames:
activemq: activemq:
broker-url: tcp://192.168.0.253:61616 broker-url: tcp://192.168.10.74:61616
user: admin user: admin
password: admin password: admin
elasticsearch: elasticsearch: