mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/per-boe/java-servers.git
synced 2025-12-11 11:56:50 +08:00
案例专家对话增加时长记录,增加任务;
上传文档时增加metadata
This commit is contained in:
@@ -8,6 +8,8 @@ import com.xboe.module.boecase.service.ICaseAiChatService;
|
|||||||
import com.xboe.module.boecase.service.ICaseAiPermissionService;
|
import com.xboe.module.boecase.service.ICaseAiPermissionService;
|
||||||
import com.xboe.module.boecase.service.IElasticSearchIndexService;
|
import com.xboe.module.boecase.service.IElasticSearchIndexService;
|
||||||
import com.xboe.module.boecase.vo.CaseAiMessageVo;
|
import com.xboe.module.boecase.vo.CaseAiMessageVo;
|
||||||
|
import com.xboe.module.excel.ExportsExcelSenderUtil;
|
||||||
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -15,7 +17,12 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,6 +80,44 @@ public class CaseAiChatApi extends ApiBaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出会话记录为Excel
|
||||||
|
* @param startTime 开始时间
|
||||||
|
* @param endTime 结束时间
|
||||||
|
* @param response HTTP响应
|
||||||
|
*/
|
||||||
|
@GetMapping("/export-conversations")
|
||||||
|
public void downloadConversationExcel(@RequestParam String startTime,
|
||||||
|
@RequestParam String endTime,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
try {
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
LocalDateTime start = LocalDateTime.parse(startTime, formatter);
|
||||||
|
LocalDateTime end = LocalDateTime.parse(endTime, formatter);
|
||||||
|
|
||||||
|
// TODO: 这里需要修改为实际返回数据的方法
|
||||||
|
caseAiChatService.downloadConversationExcel(start, end);
|
||||||
|
|
||||||
|
response.setContentType("application/vnd.ms-excel");
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=conversations.xls");
|
||||||
|
|
||||||
|
// 示例数据,实际应该从Service获取
|
||||||
|
LinkedHashMap<String, String> headers = new LinkedHashMap<>();
|
||||||
|
headers.put("会话ID", "conversationId");
|
||||||
|
headers.put("会话名称", "conversationName");
|
||||||
|
headers.put("用户", "user");
|
||||||
|
headers.put("开始时间", "startTime");
|
||||||
|
headers.put("会话时长", "duration");
|
||||||
|
|
||||||
|
List<ConversationExcelVo> dataList = new ArrayList<>();
|
||||||
|
// 这里应该填充实际数据
|
||||||
|
|
||||||
|
ExportsExcelSenderUtil.export(headers, dataList, response.getOutputStream(), "yyyy-MM-dd HH:mm:ss");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("导出会话记录为Excel异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断当前登录用户是否显示"案例专家"功能入口
|
* 判断当前登录用户是否显示"案例专家"功能入口
|
||||||
* @return 是否显示功能入口
|
* @return 是否显示功能入口
|
||||||
@@ -123,4 +168,16 @@ public class CaseAiChatApi extends ApiBaseController {
|
|||||||
}
|
}
|
||||||
return error("创建失败");
|
return error("创建失败");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* 用于Excel导出的VO类
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
static class ConversationExcelVo {
|
||||||
|
private String conversationId;
|
||||||
|
private String conversationName;
|
||||||
|
private String user;
|
||||||
|
private String startTime;
|
||||||
|
private String duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,28 +54,27 @@ public class AiChatConversationData {
|
|||||||
*/
|
*/
|
||||||
private String userName;
|
private String userName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息时间戳
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息时间戳
|
* 消息时间戳
|
||||||
*/
|
*/
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
private LocalDateTime timestamp;
|
private LocalDateTime timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天时长(秒)
|
||||||
|
*/
|
||||||
|
private Integer durationSeconds;
|
||||||
|
|
||||||
// ================== 构造函数 ==================
|
// ================== 构造函数 ==================
|
||||||
|
|
||||||
public AiChatConversationData() {
|
public AiChatConversationData() {
|
||||||
this.timestamp = LocalDateTime.now();
|
this.startTime = LocalDateTime.now();
|
||||||
}
|
|
||||||
|
|
||||||
public AiChatConversationData(String conversationId, String query, String answer,
|
|
||||||
List<CaseReferVo> caseRefers, List<String> suggestions,
|
|
||||||
String userId) {
|
|
||||||
this.conversationId = conversationId;
|
|
||||||
this.query = query;
|
|
||||||
this.answer = new StringBuilder(answer != null ? answer : "");
|
|
||||||
this.caseRefers = caseRefers != null ? caseRefers : new ArrayList<>();
|
|
||||||
this.suggestions = suggestions != null ? suggestions : new ArrayList<>();
|
|
||||||
this.userId = userId;
|
|
||||||
this.timestamp = LocalDateTime.now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================== 便捷方法 ==================
|
// ================== 便捷方法 ==================
|
||||||
|
|||||||
@@ -72,4 +72,14 @@ public class CaseAiProperties {
|
|||||||
* AI处理失败告警邮件收件人列表
|
* AI处理失败告警邮件收件人列表
|
||||||
*/
|
*/
|
||||||
private List<String> alertEmailRecipients;
|
private List<String> alertEmailRecipients;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否发送AI对话记录到邮箱
|
||||||
|
*/
|
||||||
|
private boolean aiChatDataSendEmail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI对话记录保存根路径
|
||||||
|
*/
|
||||||
|
private String aiChatRootPath;
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import com.xboe.module.boecase.entity.CaseAiConversations;
|
|||||||
import com.xboe.module.boecase.vo.CaseAiMessageVo;
|
import com.xboe.module.boecase.vo.CaseAiMessageVo;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,4 +36,11 @@ public interface ICaseAiChatService {
|
|||||||
* @return 消息记录列表
|
* @return 消息记录列表
|
||||||
*/
|
*/
|
||||||
List<CaseAiMessageVo> getConversationMessages(String conversationId);
|
List<CaseAiMessageVo> getConversationMessages(String conversationId);
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* 导出会话记录为Excel
|
||||||
|
* @param startTime 开始时间
|
||||||
|
* @param endTime 结束时间
|
||||||
|
*/
|
||||||
|
void downloadConversationExcel(LocalDateTime startTime, LocalDateTime endTime);
|
||||||
|
}
|
||||||
@@ -12,20 +12,17 @@ public interface IElasticSearchIndexService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 查看索引是否存在
|
* 查看索引是否存在
|
||||||
* @param indexName
|
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
boolean checkIndexExists();
|
boolean checkIndexExists();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建索引
|
* 创建索引
|
||||||
* @param indexName
|
|
||||||
*/
|
*/
|
||||||
boolean createIndex();
|
boolean createIndex();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除索引
|
* 删除索引
|
||||||
* @param indexName
|
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
boolean deleteIndex();
|
boolean deleteIndex();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.xboe.module.boecase.service.impl;
|
|||||||
import com.alibaba.fastjson.JSONArray;
|
import com.alibaba.fastjson.JSONArray;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.xboe.core.CurrentUser;
|
import com.xboe.core.CurrentUser;
|
||||||
|
import com.xboe.core.orm.FieldFilters;
|
||||||
import com.xboe.enums.CaseAiChatStatusEnum;
|
import com.xboe.enums.CaseAiChatStatusEnum;
|
||||||
import com.xboe.module.boecase.dao.CaseAiConversationsDao;
|
import com.xboe.module.boecase.dao.CaseAiConversationsDao;
|
||||||
import com.xboe.module.boecase.dao.CaseDocumentLogDao;
|
import com.xboe.module.boecase.dao.CaseDocumentLogDao;
|
||||||
@@ -18,8 +19,10 @@ import com.xboe.module.boecase.service.IElasticSearchIndexService;
|
|||||||
import com.xboe.module.boecase.vo.CaseAiMessageVo;
|
import com.xboe.module.boecase.vo.CaseAiMessageVo;
|
||||||
import com.xboe.module.boecase.vo.CaseReferVo;
|
import com.xboe.module.boecase.vo.CaseReferVo;
|
||||||
import com.xboe.module.boecase.entity.AiChatConversationData;
|
import com.xboe.module.boecase.entity.AiChatConversationData;
|
||||||
|
import com.xboe.module.boecase.vo.ConversationExcelVo;
|
||||||
import com.xboe.system.organization.vo.OrgSimpleVo;
|
import com.xboe.system.organization.vo.OrgSimpleVo;
|
||||||
import com.xboe.system.user.service.IUserService;
|
import com.xboe.system.user.service.IUserService;
|
||||||
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import okhttp3.*;
|
import okhttp3.*;
|
||||||
import okhttp3.sse.EventSource;
|
import okhttp3.sse.EventSource;
|
||||||
@@ -32,6 +35,11 @@ import org.apache.http.entity.StringEntity;
|
|||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.impl.client.HttpClients;
|
import org.apache.http.impl.client.HttpClients;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
import org.elasticsearch.action.index.IndexRequest;
|
import org.elasticsearch.action.index.IndexRequest;
|
||||||
import org.elasticsearch.action.index.IndexResponse;
|
import org.elasticsearch.action.index.IndexResponse;
|
||||||
import org.elasticsearch.action.search.SearchRequest;
|
import org.elasticsearch.action.search.SearchRequest;
|
||||||
@@ -53,8 +61,13 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -314,7 +327,117 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
|||||||
}
|
}
|
||||||
return elasticSearchIndexService.queryData(conversationId);
|
return elasticSearchIndexService.queryData(conversationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadConversationExcel(LocalDateTime startTime, LocalDateTime endTime) {
|
||||||
|
// 1. 根据startTime和endTime,查询在这个时间区间内的CaseAiConversations数据
|
||||||
|
List<CaseAiConversations> conversations = caseAiConversationsDao.getGenericDao().findList(
|
||||||
|
CaseAiConversations.class,
|
||||||
|
FieldFilters.ge("sysCreateTime", startTime),
|
||||||
|
FieldFilters.le("sysCreateTime", endTime)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 准备Excel数据
|
||||||
|
List<ConversationExcelVo> excelDataList = new ArrayList<>();
|
||||||
|
|
||||||
|
// 2. 遍历这组数据,根据aiConversationId从es中查询数据(可调用getConversationMessages()方法)
|
||||||
|
for (CaseAiConversations conversation : conversations) {
|
||||||
|
String aiConversationId = conversation.getAiConversationId();
|
||||||
|
String conversationName = conversation.getConversationName();
|
||||||
|
String conversationUser = conversation.getConversationUser();
|
||||||
|
|
||||||
|
List<CaseAiMessageVo> messages = getConversationMessages(aiConversationId);
|
||||||
|
|
||||||
|
// 计算会话时长
|
||||||
|
long duration = 0; // 默认为0,如果需要精确计算,需要从消息中提取时间信息
|
||||||
|
|
||||||
|
// 3. 写入Excel,包括每个会话的用户,会话标题,会话内的问答记录,每次对话时长等
|
||||||
|
ConversationExcelVo excelData = new ConversationExcelVo();
|
||||||
|
excelData.setConversationId(aiConversationId);
|
||||||
|
excelData.setConversationName(conversationName);
|
||||||
|
excelData.setUser(conversationUser);
|
||||||
|
excelData.setMessages(messages);
|
||||||
|
|
||||||
|
excelDataList.add(excelData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入Excel文件
|
||||||
|
Workbook workbook = new XSSFWorkbook();
|
||||||
|
Sheet sheet = workbook.createSheet("AI会话数据");
|
||||||
|
// 标题行
|
||||||
|
Row headerRow = sheet.createRow(0);
|
||||||
|
headerRow.createCell(0).setCellValue("会话ID");
|
||||||
|
headerRow.createCell(1).setCellValue("会话名称");
|
||||||
|
headerRow.createCell(2).setCellValue("用户");
|
||||||
|
headerRow.createCell(3).setCellValue("提问");
|
||||||
|
headerRow.createCell(4).setCellValue("回答");
|
||||||
|
headerRow.createCell(5).setCellValue("开始时间");
|
||||||
|
headerRow.createCell(6).setCellValue("问答时长(秒)");
|
||||||
|
|
||||||
|
// 内容行
|
||||||
|
if (!excelDataList.isEmpty()) {
|
||||||
|
int rowNum = 1; // 从第二行开始写入数据
|
||||||
|
for (ConversationExcelVo excelData : excelDataList) {
|
||||||
|
List<CaseAiMessageVo> messages = excelData.getMessages();
|
||||||
|
|
||||||
|
if (messages != null && !messages.isEmpty()) {
|
||||||
|
// 记录起始行号,用于后续合并单元格
|
||||||
|
int startRow = rowNum;
|
||||||
|
|
||||||
|
// 遍历每个消息
|
||||||
|
for (CaseAiMessageVo message : messages) {
|
||||||
|
Row row = sheet.createRow(rowNum++);
|
||||||
|
// 填充每行数据
|
||||||
|
row.createCell(0).setCellValue(excelData.getConversationId());
|
||||||
|
row.createCell(1).setCellValue(excelData.getConversationName());
|
||||||
|
row.createCell(2).setCellValue(excelData.getUser());
|
||||||
|
row.createCell(3).setCellValue(message.getQuery() != null ? message.getQuery() : "");
|
||||||
|
row.createCell(4).setCellValue(message.getAnswer() != null ? message.getAnswer() : "");
|
||||||
|
row.createCell(5).setCellValue(""); // 开始时间字段暂留空
|
||||||
|
row.createCell(6).setCellValue(message.getDurationSeconds() != null ? message.getDurationSeconds() : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并单元格(会话ID、会话名称、用户三列)
|
||||||
|
// 参数说明:起始行号,结束行号,起始列号,结束列号
|
||||||
|
if (rowNum > startRow + 1) { // 只有当有多行时才合并
|
||||||
|
sheet.addMergedRegion(new CellRangeAddress(startRow, rowNum - 1, 0, 0));
|
||||||
|
sheet.addMergedRegion(new CellRangeAddress(startRow, rowNum - 1, 1, 1));
|
||||||
|
sheet.addMergedRegion(new CellRangeAddress(startRow, rowNum - 1, 2, 2));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有消息,则仍然创建一行显示基本信息
|
||||||
|
Row row = sheet.createRow(rowNum++);
|
||||||
|
row.createCell(0).setCellValue(excelData.getConversationId());
|
||||||
|
row.createCell(1).setCellValue(excelData.getConversationName());
|
||||||
|
row.createCell(2).setCellValue(excelData.getUser());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3. 创建Excel文件并保存
|
||||||
|
if (caseAiProperties.isAiChatDataSendEmail()) {
|
||||||
|
// TODO 发送邮件附件
|
||||||
|
} else {
|
||||||
|
// 保存文件
|
||||||
|
String dirPath = caseAiProperties.getAiChatRootPath() + File.separator + startTime.format(DateTimeFormatter.ofPattern("yyyyMM"));
|
||||||
|
Path dir = Paths.get(dirPath);
|
||||||
|
if (!Files.exists(dir)) {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(dir);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String fileName = "AI会话数据-" + startTime.format(DateTimeFormatter.ofPattern("yyyyMMdd")) + "-" + System.currentTimeMillis() + ".xlsx";
|
||||||
|
Path filePath = dir.resolve(fileName);
|
||||||
|
try (OutputStream out = Files.newOutputStream(filePath)) {
|
||||||
|
workbook.write(out);
|
||||||
|
out.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("保存文件错误", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从 ES 数据中解析消息对象
|
* 从 ES 数据中解析消息对象
|
||||||
* @param sourceMap ES数据
|
* @param sourceMap ES数据
|
||||||
@@ -554,9 +677,4 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
|
|||||||
sseEmitter.completeWithError(e);
|
sseEmitter.completeWithError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 对话数据容器
|
|
||||||
*/
|
|
||||||
// ConversationData 已移动到独立的Entity类:AiChatConversationData
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,15 @@ import com.xboe.enums.CaseDocumentLogRunStatusEnum;
|
|||||||
import com.xboe.module.assistance.service.ISmtpEmailService;
|
import com.xboe.module.assistance.service.ISmtpEmailService;
|
||||||
import com.xboe.module.boecase.dao.CaseDocumentLogDao;
|
import com.xboe.module.boecase.dao.CaseDocumentLogDao;
|
||||||
import com.xboe.module.boecase.dao.CasesDao;
|
import com.xboe.module.boecase.dao.CasesDao;
|
||||||
|
import com.xboe.module.boecase.dao.CasesMajorTypeDao;
|
||||||
import com.xboe.module.boecase.entity.CaseDocumentLog;
|
import com.xboe.module.boecase.entity.CaseDocumentLog;
|
||||||
import com.xboe.module.boecase.entity.Cases;
|
import com.xboe.module.boecase.entity.Cases;
|
||||||
|
import com.xboe.module.boecase.entity.CasesMajorType;
|
||||||
import com.xboe.module.boecase.properties.CaseAiProperties;
|
import com.xboe.module.boecase.properties.CaseAiProperties;
|
||||||
import com.xboe.module.boecase.service.IAiAccessTokenService;
|
import com.xboe.module.boecase.service.IAiAccessTokenService;
|
||||||
import com.xboe.module.boecase.service.ICaseKnowledgeService;
|
import com.xboe.module.boecase.service.ICaseKnowledgeService;
|
||||||
|
import com.xboe.module.dict.entity.DictItem;
|
||||||
|
import com.xboe.module.dict.service.ISysDictionaryService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
@@ -44,6 +48,8 @@ import java.io.File;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 案例-知识库Service实现类
|
* 案例-知识库Service实现类
|
||||||
@@ -63,9 +69,15 @@ public class CaseKnowledgeServiceImpl implements ICaseKnowledgeService {
|
|||||||
@Resource
|
@Resource
|
||||||
private CaseDocumentLogDao caseDocumentLogDao;
|
private CaseDocumentLogDao caseDocumentLogDao;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CasesMajorTypeDao casesMajorTypeDao;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private XFileUploader fileUploader;
|
private XFileUploader fileUploader;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISysDictionaryService sysDictionaryService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IAiAccessTokenService aiAccessTokenService;
|
private IAiAccessTokenService aiAccessTokenService;
|
||||||
|
|
||||||
@@ -145,6 +157,52 @@ public class CaseKnowledgeServiceImpl implements ICaseKnowledgeService {
|
|||||||
builder.addTextBody("fileType", fileType, ContentType.TEXT_PLAIN);
|
builder.addTextBody("fileType", fileType, ContentType.TEXT_PLAIN);
|
||||||
requestBody.put("fileType", fileType);
|
requestBody.put("fileType", fileType);
|
||||||
builder.addTextBody("parseType", "AUTO", ContentType.TEXT_PLAIN);
|
builder.addTextBody("parseType", "AUTO", ContentType.TEXT_PLAIN);
|
||||||
|
// metadata
|
||||||
|
JSONObject fileMetaData = new JSONObject();
|
||||||
|
fileMetaData.put("标题", cases.getTitle());
|
||||||
|
fileMetaData.put("作者", cases.getAuthorName());
|
||||||
|
fileMetaData.put("年份", String.valueOf(cases.getSysCreateTime().getYear()));
|
||||||
|
fileMetaData.put("摘要", cases.getSummary());
|
||||||
|
// 组织领域:orgDomainParent
|
||||||
|
String orgDomainParent = cases.getOrgDomainParent();
|
||||||
|
List<DictItem> orgDomainParentItems = sysDictionaryService.findByKey("org_domain");
|
||||||
|
Optional<DictItem> orgDomainParentItem = orgDomainParentItems.stream()
|
||||||
|
.filter(item -> StringUtils.equals(orgDomainParent, item.getCode()))
|
||||||
|
.findFirst();
|
||||||
|
if (orgDomainParentItem.isPresent()) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(orgDomainParentItem.get().getName());
|
||||||
|
if (StringUtils.isNotBlank(cases.getOrgDomainParent2())) {
|
||||||
|
Optional<DictItem> orgDomainParent2Item = orgDomainParentItems.stream()
|
||||||
|
.filter(item -> StringUtils.equals(cases.getOrgDomainParent2(), item.getCode()))
|
||||||
|
.findFirst();
|
||||||
|
orgDomainParent2Item.ifPresent(dictItem -> sb.append(" - ").append(dictItem.getName()));
|
||||||
|
if (StringUtils.isNotBlank(cases.getOrgDomainParent3())) {
|
||||||
|
Optional<DictItem> orgDomainParent3Item = orgDomainParentItems.stream()
|
||||||
|
.filter(item -> StringUtils.equals(cases.getOrgDomainParent3(), item.getCode()))
|
||||||
|
.findFirst();
|
||||||
|
orgDomainParent3Item.ifPresent(dictItem -> sb.append(" - ").append(dictItem.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileMetaData.put("组织领域", sb.toString());
|
||||||
|
}
|
||||||
|
// 分类:majorIds
|
||||||
|
List<CasesMajorType> cmtList = casesMajorTypeDao.findList(FieldFilters.eq("caseId", cases.getId()));
|
||||||
|
if (cmtList != null && !cmtList.isEmpty()) {
|
||||||
|
List<String> majorIds = cmtList.stream().map(CasesMajorType::getMajorId).collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<DictItem> majorItems = sysDictionaryService.findByKey("major_type");
|
||||||
|
if (majorItems != null && !majorItems.isEmpty()) {
|
||||||
|
List<String> majorNames = majorItems.stream()
|
||||||
|
.filter(item -> majorIds.contains(item.getCode()))
|
||||||
|
.map(DictItem::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
fileMetaData.put("组织领域", String.join(", ", majorNames));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.addTextBody("fileMetaData", fileMetaData.toJSONString(), ContentType.TEXT_PLAIN);
|
||||||
|
requestBody.put("fileMetaData", fileMetaData);
|
||||||
// 由于接口权限,目前采用不回调,而是通过批处理的方式,处理文件状态
|
// 由于接口权限,目前采用不回调,而是通过批处理的方式,处理文件状态
|
||||||
if (caseAiProperties.isFileUploadUseCallback()) {
|
if (caseAiProperties.isFileUploadUseCallback()) {
|
||||||
builder.addTextBody("callbackUrl", caseAiProperties.getFileUploadCallbackUrl(), ContentType.TEXT_PLAIN);
|
builder.addTextBody("callbackUrl", caseAiProperties.getFileUploadCallbackUrl(), ContentType.TEXT_PLAIN);
|
||||||
@@ -432,6 +490,52 @@ public class CaseKnowledgeServiceImpl implements ICaseKnowledgeService {
|
|||||||
builder.addTextBody("fileType", fileType, ContentType.TEXT_PLAIN);
|
builder.addTextBody("fileType", fileType, ContentType.TEXT_PLAIN);
|
||||||
requestBody.put("fileType", fileType);
|
requestBody.put("fileType", fileType);
|
||||||
builder.addTextBody("parseType", "AUTO", ContentType.TEXT_PLAIN);
|
builder.addTextBody("parseType", "AUTO", ContentType.TEXT_PLAIN);
|
||||||
|
// metadata
|
||||||
|
JSONObject fileMetaData = new JSONObject();
|
||||||
|
fileMetaData.put("标题", cases.getTitle());
|
||||||
|
fileMetaData.put("作者", cases.getAuthorName());
|
||||||
|
fileMetaData.put("年份", String.valueOf(cases.getSysCreateTime().getYear()));
|
||||||
|
fileMetaData.put("摘要", cases.getSummary());
|
||||||
|
// 组织领域:orgDomainParent
|
||||||
|
String orgDomainParent = cases.getOrgDomainParent();
|
||||||
|
List<DictItem> orgDomainParentItems = sysDictionaryService.findByKey("org_domain");
|
||||||
|
Optional<DictItem> orgDomainParentItem = orgDomainParentItems.stream()
|
||||||
|
.filter(item -> StringUtils.equals(orgDomainParent, item.getCode()))
|
||||||
|
.findFirst();
|
||||||
|
if (orgDomainParentItem.isPresent()) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(orgDomainParentItem.get().getName());
|
||||||
|
if (StringUtils.isNotBlank(cases.getOrgDomainParent2())) {
|
||||||
|
Optional<DictItem> orgDomainParent2Item = orgDomainParentItems.stream()
|
||||||
|
.filter(item -> StringUtils.equals(cases.getOrgDomainParent2(), item.getCode()))
|
||||||
|
.findFirst();
|
||||||
|
orgDomainParent2Item.ifPresent(dictItem -> sb.append(" - ").append(dictItem.getName()));
|
||||||
|
if (StringUtils.isNotBlank(cases.getOrgDomainParent3())) {
|
||||||
|
Optional<DictItem> orgDomainParent3Item = orgDomainParentItems.stream()
|
||||||
|
.filter(item -> StringUtils.equals(cases.getOrgDomainParent3(), item.getCode()))
|
||||||
|
.findFirst();
|
||||||
|
orgDomainParent3Item.ifPresent(dictItem -> sb.append(" - ").append(dictItem.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileMetaData.put("组织领域", sb.toString());
|
||||||
|
}
|
||||||
|
// 分类:majorIds
|
||||||
|
List<CasesMajorType> cmtList = casesMajorTypeDao.findList(FieldFilters.eq("caseId", cases.getId()));
|
||||||
|
if (cmtList != null && !cmtList.isEmpty()) {
|
||||||
|
List<String> majorIds = cmtList.stream().map(CasesMajorType::getMajorId).collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<DictItem> majorItems = sysDictionaryService.findByKey("major_type");
|
||||||
|
if (majorItems != null && !majorItems.isEmpty()) {
|
||||||
|
List<String> majorNames = majorItems.stream()
|
||||||
|
.filter(item -> majorIds.contains(item.getCode()))
|
||||||
|
.map(DictItem::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
fileMetaData.put("组织领域", String.join(", ", majorNames));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.addTextBody("fileMetaData", fileMetaData.toJSONString(), ContentType.TEXT_PLAIN);
|
||||||
|
requestBody.put("fileMetaData", fileMetaData);
|
||||||
// 由于接口权限,目前采用不回调,而是通过批处理的方式,处理文件状态
|
// 由于接口权限,目前采用不回调,而是通过批处理的方式,处理文件状态
|
||||||
if (caseAiProperties.isFileUploadUseCallback()) {
|
if (caseAiProperties.isFileUploadUseCallback()) {
|
||||||
builder.addTextBody("callbackUrl", caseAiProperties.getFileUploadCallbackUrl(), ContentType.TEXT_PLAIN);
|
builder.addTextBody("callbackUrl", caseAiProperties.getFileUploadCallbackUrl(), ContentType.TEXT_PLAIN);
|
||||||
@@ -696,6 +800,52 @@ public class CaseKnowledgeServiceImpl implements ICaseKnowledgeService {
|
|||||||
builder.addTextBody("fileType", fileType, ContentType.TEXT_PLAIN);
|
builder.addTextBody("fileType", fileType, ContentType.TEXT_PLAIN);
|
||||||
requestBody.put("fileType", fileType);
|
requestBody.put("fileType", fileType);
|
||||||
builder.addTextBody("parseType", "AUTO", ContentType.TEXT_PLAIN);
|
builder.addTextBody("parseType", "AUTO", ContentType.TEXT_PLAIN);
|
||||||
|
// metadata
|
||||||
|
JSONObject fileMetaData = new JSONObject();
|
||||||
|
fileMetaData.put("标题", cases.getTitle());
|
||||||
|
fileMetaData.put("作者", cases.getAuthorName());
|
||||||
|
fileMetaData.put("年份", String.valueOf(cases.getSysCreateTime().getYear()));
|
||||||
|
fileMetaData.put("摘要", cases.getSummary());
|
||||||
|
// 组织领域:orgDomainParent
|
||||||
|
String orgDomainParent = cases.getOrgDomainParent();
|
||||||
|
List<DictItem> orgDomainParentItems = sysDictionaryService.findByKey("org_domain");
|
||||||
|
Optional<DictItem> orgDomainParentItem = orgDomainParentItems.stream()
|
||||||
|
.filter(item -> StringUtils.equals(orgDomainParent, item.getCode()))
|
||||||
|
.findFirst();
|
||||||
|
if (orgDomainParentItem.isPresent()) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(orgDomainParentItem.get().getName());
|
||||||
|
if (StringUtils.isNotBlank(cases.getOrgDomainParent2())) {
|
||||||
|
Optional<DictItem> orgDomainParent2Item = orgDomainParentItems.stream()
|
||||||
|
.filter(item -> StringUtils.equals(cases.getOrgDomainParent2(), item.getCode()))
|
||||||
|
.findFirst();
|
||||||
|
orgDomainParent2Item.ifPresent(dictItem -> sb.append(" - ").append(dictItem.getName()));
|
||||||
|
if (StringUtils.isNotBlank(cases.getOrgDomainParent3())) {
|
||||||
|
Optional<DictItem> orgDomainParent3Item = orgDomainParentItems.stream()
|
||||||
|
.filter(item -> StringUtils.equals(cases.getOrgDomainParent3(), item.getCode()))
|
||||||
|
.findFirst();
|
||||||
|
orgDomainParent3Item.ifPresent(dictItem -> sb.append(" - ").append(dictItem.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileMetaData.put("组织领域", sb.toString());
|
||||||
|
}
|
||||||
|
// 分类:majorIds
|
||||||
|
List<CasesMajorType> cmtList = casesMajorTypeDao.findList(FieldFilters.eq("caseId", cases.getId()));
|
||||||
|
if (cmtList != null && !cmtList.isEmpty()) {
|
||||||
|
List<String> majorIds = cmtList.stream().map(CasesMajorType::getMajorId).collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<DictItem> majorItems = sysDictionaryService.findByKey("major_type");
|
||||||
|
if (majorItems != null && !majorItems.isEmpty()) {
|
||||||
|
List<String> majorNames = majorItems.stream()
|
||||||
|
.filter(item -> majorIds.contains(item.getCode()))
|
||||||
|
.map(DictItem::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
fileMetaData.put("组织领域", String.join(", ", majorNames));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.addTextBody("fileMetaData", fileMetaData.toJSONString(), ContentType.TEXT_PLAIN);
|
||||||
|
requestBody.put("fileMetaData", fileMetaData);
|
||||||
// 由于接口权限,目前采用不回调,而是通过批处理的方式,处理文件状态
|
// 由于接口权限,目前采用不回调,而是通过批处理的方式,处理文件状态
|
||||||
if (caseAiProperties.isFileUploadUseCallback()) {
|
if (caseAiProperties.isFileUploadUseCallback()) {
|
||||||
builder.addTextBody("callbackUrl", caseAiProperties.getFileUploadCallbackUrl(), ContentType.TEXT_PLAIN);
|
builder.addTextBody("callbackUrl", caseAiProperties.getFileUploadCallbackUrl(), ContentType.TEXT_PLAIN);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -134,7 +135,11 @@ public class ElasticSearchIndexServiceImpl implements IElasticSearchIndexService
|
|||||||
esData.put("answer", conversationData.getAnswerAsString());
|
esData.put("answer", conversationData.getAnswerAsString());
|
||||||
esData.put("conversationId", conversationData.getConversationId());
|
esData.put("conversationId", conversationData.getConversationId());
|
||||||
esData.put("userId", conversationData.getUserId());
|
esData.put("userId", conversationData.getUserId());
|
||||||
esData.put("timestamp", LocalDateTime.now().toString());
|
// 持续时间
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
esData.put("startTime", conversationData.getStartTime().toString());
|
||||||
|
esData.put("timestamp", now.toString());
|
||||||
|
esData.put("durationSeconds", Duration.between(conversationData.getStartTime(), now).getSeconds());
|
||||||
|
|
||||||
// 构建 caseRefer 数据
|
// 构建 caseRefer 数据
|
||||||
JSONArray caseReferArray = new JSONArray();
|
JSONArray caseReferArray = new JSONArray();
|
||||||
@@ -206,6 +211,9 @@ public class ElasticSearchIndexServiceImpl implements IElasticSearchIndexService
|
|||||||
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"));
|
||||||
|
String startTimeStr = (String) sourceMap.get("startTime");
|
||||||
|
messageVo.setStartTime(LocalDateTime.parse(startTimeStr));
|
||||||
|
messageVo.setDurationSeconds((Long) sourceMap.get("durationSeconds"));
|
||||||
|
|
||||||
// 解析 suggestions
|
// 解析 suggestions
|
||||||
Object suggestionsObj = sourceMap.get("suggestions");
|
Object suggestionsObj = sourceMap.get("suggestions");
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.xboe.module.boecase.task;
|
||||||
|
|
||||||
|
import com.xboe.module.boecase.service.ICaseAiChatService;
|
||||||
|
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.YearMonth;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class CaseAiChatDataTask {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ICaseAiChatService caseAiChatService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询上月聊天数据并下载
|
||||||
|
* cron: 0/10 * * * * ?
|
||||||
|
*/
|
||||||
|
@XxlJob("chatDataExcelDownloadJob")
|
||||||
|
public void chatDataExcelDownload() {
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
// 取上个月的1号00:00:00到上个月最后一天的23:59:59
|
||||||
|
YearMonth lastMonth = YearMonth.from(now).minusMonths(1);
|
||||||
|
LocalDateTime startTime = now.minusMonths(1)
|
||||||
|
.withDayOfMonth(1)
|
||||||
|
.withHour(0)
|
||||||
|
.withMinute(0)
|
||||||
|
.withSecond(0);
|
||||||
|
LocalDateTime endTime = lastMonth.atEndOfMonth().atTime(23, 59, 59);
|
||||||
|
// 执行
|
||||||
|
caseAiChatService.downloadConversationExcel(startTime, endTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.xboe.module.boecase.vo;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,6 +21,16 @@ public class CaseAiMessageVo {
|
|||||||
*/
|
*/
|
||||||
private String answer;
|
private String answer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话开始时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话时长(秒)
|
||||||
|
*/
|
||||||
|
private Long durationSeconds;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 案例引用列表
|
* 案例引用列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.xboe.module.boecase.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话Excel导出VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ConversationExcelVo {
|
||||||
|
/**
|
||||||
|
* 会话ID
|
||||||
|
*/
|
||||||
|
private String conversationId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话名称
|
||||||
|
*/
|
||||||
|
private String conversationName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户
|
||||||
|
*/
|
||||||
|
private String user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 问答记录
|
||||||
|
*/
|
||||||
|
private List<CaseAiMessageVo> messages;
|
||||||
|
}
|
||||||
@@ -84,7 +84,7 @@ xboe:
|
|||||||
secret-key: db4d24279e3d6dbf1524af42cd0bedd2
|
secret-key: db4d24279e3d6dbf1524af42cd0bedd2
|
||||||
ai-api-code: 30800
|
ai-api-code: 30800
|
||||||
chat-api-code: 32065
|
chat-api-code: 32065
|
||||||
case-knowledge-id: de2e006e-82fb-4ace-8813-f25c316be4ff
|
case-knowledge-id: 92a0e117-b1f0-4eb7-a4b3-c79fb18f2ede
|
||||||
file-upload-callback-url: http://10.251.113.95:9090/xboe/m/boe/caseDocumentLog/uploadCallback
|
file-upload-callback-url: http://10.251.113.95:9090/xboe/m/boe/caseDocumentLog/uploadCallback
|
||||||
use-white-list: true
|
use-white-list: true
|
||||||
white-user-code-list:
|
white-user-code-list:
|
||||||
@@ -112,6 +112,7 @@ xboe:
|
|||||||
- chengmeng@boe.com.cn
|
- chengmeng@boe.com.cn
|
||||||
- liyubing@boe.com.cn
|
- liyubing@boe.com.cn
|
||||||
- lijian-hq@boe.com.cn
|
- lijian-hq@boe.com.cn
|
||||||
|
ai-chat-root-path: /home/www/elearning/upload/ai/chat
|
||||||
xxl:
|
xxl:
|
||||||
job:
|
job:
|
||||||
accessToken: 65ddc683-22f5-83b4-de3a-3c97a0a29af0
|
accessToken: 65ddc683-22f5-83b4-de3a-3c97a0a29af0
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ xboe:
|
|||||||
- "11339772"
|
- "11339772"
|
||||||
alert-email-recipients:
|
alert-email-recipients:
|
||||||
- chengmeng@boe.com.cn
|
- chengmeng@boe.com.cn
|
||||||
|
ai-chat-root-path: /home/www/elearning/upload/ai/chat
|
||||||
jasypt:
|
jasypt:
|
||||||
encryptor:
|
encryptor:
|
||||||
algorithm: PBEWithMD5AndDES
|
algorithm: PBEWithMD5AndDES
|
||||||
|
|||||||
Reference in New Issue
Block a user