案例专家对话增加时长记录,增加任务;

上传文档时增加metadata
This commit is contained in:
liu.zixi
2025-11-03 13:49:42 +08:00
parent e9682dcb61
commit 748ec8c072
13 changed files with 455 additions and 26 deletions

View File

@@ -8,6 +8,8 @@ import com.xboe.module.boecase.service.ICaseAiChatService;
import com.xboe.module.boecase.service.ICaseAiPermissionService;
import com.xboe.module.boecase.service.IElasticSearchIndexService;
import com.xboe.module.boecase.vo.CaseAiMessageVo;
import com.xboe.module.excel.ExportsExcelSenderUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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 javax.servlet.http.HttpServletResponse;
import java.io.IOException;
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;
/**
@@ -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 是否显示功能入口
@@ -123,4 +168,16 @@ public class CaseAiChatApi extends ApiBaseController {
}
return error("创建失败");
}
}
/**
* 用于Excel导出的VO类
*/
@Data
static class ConversationExcelVo {
private String conversationId;
private String conversationName;
private String user;
private String startTime;
private String duration;
}
}

View File

@@ -54,28 +54,27 @@ public class AiChatConversationData {
*/
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")
private LocalDateTime timestamp;
/**
* 聊天时长(秒)
*/
private Integer durationSeconds;
// ================== 构造函数 ==================
public AiChatConversationData() {
this.timestamp = 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();
this.startTime = LocalDateTime.now();
}
// ================== 便捷方法 ==================

View File

@@ -72,4 +72,14 @@ public class CaseAiProperties {
* AI处理失败告警邮件收件人列表
*/
private List<String> alertEmailRecipients;
/**
* 是否发送AI对话记录到邮箱
*/
private boolean aiChatDataSendEmail;
/**
* AI对话记录保存根路径
*/
private String aiChatRootPath;
}

View File

@@ -6,6 +6,7 @@ import com.xboe.module.boecase.entity.CaseAiConversations;
import com.xboe.module.boecase.vo.CaseAiMessageVo;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.time.LocalDateTime;
import java.util.List;
/**
@@ -35,4 +36,11 @@ public interface ICaseAiChatService {
* @return 消息记录列表
*/
List<CaseAiMessageVo> getConversationMessages(String conversationId);
}
/**
* 导出会话记录为Excel
* @param startTime 开始时间
* @param endTime 结束时间
*/
void downloadConversationExcel(LocalDateTime startTime, LocalDateTime endTime);
}

View File

@@ -12,20 +12,17 @@ public interface IElasticSearchIndexService {
/**
* 查看索引是否存在
* @param indexName
* @return
*/
boolean checkIndexExists();
/**
* 创建索引
* @param indexName
*/
boolean createIndex();
/**
* 删除索引
* @param indexName
* @return
*/
boolean deleteIndex();

View File

@@ -3,6 +3,7 @@ package com.xboe.module.boecase.service.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xboe.core.CurrentUser;
import com.xboe.core.orm.FieldFilters;
import com.xboe.enums.CaseAiChatStatusEnum;
import com.xboe.module.boecase.dao.CaseAiConversationsDao;
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.CaseReferVo;
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.user.service.IUserService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
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.HttpClients;
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.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
@@ -53,8 +61,13 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
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.format.DateTimeFormatter;
import java.util.*;
@@ -314,7 +327,117 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
}
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 数据中解析消息对象
* @param sourceMap ES数据
@@ -554,9 +677,4 @@ public class CaseAiChatServiceImpl implements ICaseAiChatService {
sseEmitter.completeWithError(e);
}
}
/**
* 对话数据容器
*/
// ConversationData 已移动到独立的Entity类AiChatConversationData
}

View File

@@ -17,11 +17,15 @@ import com.xboe.enums.CaseDocumentLogRunStatusEnum;
import com.xboe.module.assistance.service.ISmtpEmailService;
import com.xboe.module.boecase.dao.CaseDocumentLogDao;
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.Cases;
import com.xboe.module.boecase.entity.CasesMajorType;
import com.xboe.module.boecase.properties.CaseAiProperties;
import com.xboe.module.boecase.service.IAiAccessTokenService;
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 org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
@@ -44,6 +48,8 @@ import java.io.File;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 案例-知识库Service实现类
@@ -63,9 +69,15 @@ public class CaseKnowledgeServiceImpl implements ICaseKnowledgeService {
@Resource
private CaseDocumentLogDao caseDocumentLogDao;
@Resource
private CasesMajorTypeDao casesMajorTypeDao;
@Resource
private XFileUploader fileUploader;
@Autowired
private ISysDictionaryService sysDictionaryService;
@Autowired
private IAiAccessTokenService aiAccessTokenService;
@@ -145,6 +157,52 @@ public class CaseKnowledgeServiceImpl implements ICaseKnowledgeService {
builder.addTextBody("fileType", fileType, ContentType.TEXT_PLAIN);
requestBody.put("fileType", fileType);
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()) {
builder.addTextBody("callbackUrl", caseAiProperties.getFileUploadCallbackUrl(), ContentType.TEXT_PLAIN);
@@ -432,6 +490,52 @@ public class CaseKnowledgeServiceImpl implements ICaseKnowledgeService {
builder.addTextBody("fileType", fileType, ContentType.TEXT_PLAIN);
requestBody.put("fileType", fileType);
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()) {
builder.addTextBody("callbackUrl", caseAiProperties.getFileUploadCallbackUrl(), ContentType.TEXT_PLAIN);
@@ -696,6 +800,52 @@ public class CaseKnowledgeServiceImpl implements ICaseKnowledgeService {
builder.addTextBody("fileType", fileType, ContentType.TEXT_PLAIN);
requestBody.put("fileType", fileType);
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()) {
builder.addTextBody("callbackUrl", caseAiProperties.getFileUploadCallbackUrl(), ContentType.TEXT_PLAIN);

View File

@@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@@ -134,7 +135,11 @@ public class ElasticSearchIndexServiceImpl implements IElasticSearchIndexService
esData.put("answer", conversationData.getAnswerAsString());
esData.put("conversationId", conversationData.getConversationId());
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 数据
JSONArray caseReferArray = new JSONArray();
@@ -206,6 +211,9 @@ public class ElasticSearchIndexServiceImpl implements IElasticSearchIndexService
CaseAiMessageVo messageVo = new CaseAiMessageVo();
messageVo.setQuery((String) sourceMap.get("query"));
messageVo.setAnswer((String) sourceMap.get("answer"));
String startTimeStr = (String) sourceMap.get("startTime");
messageVo.setStartTime(LocalDateTime.parse(startTimeStr));
messageVo.setDurationSeconds((Long) sourceMap.get("durationSeconds"));
// 解析 suggestions
Object suggestionsObj = sourceMap.get("suggestions");

View File

@@ -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);
}
}

View File

@@ -2,6 +2,7 @@ package com.xboe.module.boecase.vo;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
@@ -20,6 +21,16 @@ public class CaseAiMessageVo {
*/
private String answer;
/**
* 会话开始时间
*/
private LocalDateTime startTime;
/**
* 会话时长(秒)
*/
private Long durationSeconds;
/**
* 案例引用列表
*/

View File

@@ -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;
}

View File

@@ -84,7 +84,7 @@ xboe:
secret-key: db4d24279e3d6dbf1524af42cd0bedd2
ai-api-code: 30800
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
use-white-list: true
white-user-code-list:
@@ -112,6 +112,7 @@ xboe:
- chengmeng@boe.com.cn
- liyubing@boe.com.cn
- lijian-hq@boe.com.cn
ai-chat-root-path: /home/www/elearning/upload/ai/chat
xxl:
job:
accessToken: 65ddc683-22f5-83b4-de3a-3c97a0a29af0

View File

@@ -144,6 +144,7 @@ xboe:
- "11339772"
alert-email-recipients:
- chengmeng@boe.com.cn
ai-chat-root-path: /home/www/elearning/upload/ai/chat
jasypt:
encryptor:
algorithm: PBEWithMD5AndDES