|
|
@@ -0,0 +1,413 @@
|
|
|
+package com.fs.crm.utils;
|
|
|
+
|
|
|
+import cn.hutool.core.collection.ListUtil;
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
|
|
+import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
+import com.fasterxml.jackson.core.type.TypeReference;
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.fs.common.core.domain.R;
|
|
|
+import com.fs.common.core.domain.entity.SysDictData;
|
|
|
+import com.fs.common.exception.CustomException;
|
|
|
+import com.fs.common.utils.DictUtils;
|
|
|
+import com.fs.common.utils.spring.SpringUtils;
|
|
|
+import com.fs.config.ai.AiHostProper;
|
|
|
+import com.fs.crm.domain.CrmCustomer;
|
|
|
+import com.fs.crm.domain.CrmCustomerInfo;
|
|
|
+import com.fs.crm.domain.CrmCustomerPropertyTemplate;
|
|
|
+import com.fs.crm.dto.CrmCustomerAiAutoTagVo;
|
|
|
+import com.fs.crm.mapper.CrmCustomerMapper;
|
|
|
+import com.fs.crm.param.CrmCustomerAiTagParam;
|
|
|
+import com.fs.crm.service.ICrmCustomerPropertyTemplateService;
|
|
|
+import com.fs.crm.vo.CrmCustomerAiTagVo;
|
|
|
+import com.fs.fastgptApi.param.ChatParam;
|
|
|
+import com.fs.fastgptApi.service.ChatService;
|
|
|
+import com.fs.hisapi.util.MapUtil;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
+import java.util.*;
|
|
|
+import java.util.concurrent.CompletableFuture;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+import java.util.stream.Stream;
|
|
|
+
|
|
|
+@Component
|
|
|
+public class CrmCustomerAiTagUtil {
|
|
|
+
|
|
|
+ //行业字典名称
|
|
|
+ private static final String TRADE_TYPE = "trade_type";
|
|
|
+ @Value("${crm.customer.ai.key:mygpt-tbQfq4ejR162mGJBCTTDUH9ecP1XCVuUfaOGTipnLjb1hP8x5prg}")
|
|
|
+ private String appKey;
|
|
|
+ private static final String CRM_AI_REDIS_KEY = "crm:AI:data:processing";
|
|
|
+
|
|
|
+ private static final ObjectMapper mapper = new ObjectMapper();
|
|
|
+
|
|
|
+ private static String APP_KEY;
|
|
|
+
|
|
|
+ @PostConstruct
|
|
|
+ public void initStatic() {
|
|
|
+ APP_KEY = this.appKey;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static List<CrmCustomerAiTagVo> getCrmCustomerAiTag(CrmCustomerAiTagParam content) throws JsonProcessingException {
|
|
|
+ // 1. 参数校验
|
|
|
+ validateParams(content);
|
|
|
+ Long customerId = content.getCustomerId();
|
|
|
+ Long logId = content.getLogId();
|
|
|
+ String tradeType = content.getTradeType();
|
|
|
+ List<Map<String, Object>> communication = parseCommunicationJson(content.getJson());
|
|
|
+
|
|
|
+ // 2. 构建请求参数
|
|
|
+ Map<String, Object> requestParam = buildRequestParam(customerId, tradeType, communication);
|
|
|
+
|
|
|
+ // 3. 调用AI服务
|
|
|
+ R aiResponse = callAiService(requestParam, logId);
|
|
|
+
|
|
|
+ // 4. 解析响应并保存
|
|
|
+ List<CrmCustomerAiTagVo> results = parseAiResponse(aiResponse, customerId);
|
|
|
+
|
|
|
+ // 5. 异步保存到Redis,后续调用ai分析其他数据
|
|
|
+ saveToRedisAsync(customerId, aiResponse);
|
|
|
+ return results;
|
|
|
+ }
|
|
|
+ private static void saveToRedisAsync(Long customerId, R aiResponse) {
|
|
|
+ // 使用线程池异步保存,避免影响主流程
|
|
|
+ CompletableFuture.runAsync(() -> {
|
|
|
+ try {
|
|
|
+ Map<String, Object> dataMap = new HashMap<>();
|
|
|
+ dataMap.put("customerId", customerId);
|
|
|
+ dataMap.put("data", aiResponse);
|
|
|
+ dataMap.put("timestamp", System.currentTimeMillis());
|
|
|
+
|
|
|
+ RedisTemplate<String, Object> redisTemplate = SpringUtils.getBean(RedisTemplate.class);
|
|
|
+
|
|
|
+ // 存储队列索引
|
|
|
+ redisTemplate.opsForList().rightPush(CRM_AI_REDIS_KEY, dataMap);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ private static CrmCustomerAiTagVo buildTagVo(Map<String, String> tag, Long customerId) {
|
|
|
+ CrmCustomerAiTagVo vo = new CrmCustomerAiTagVo();
|
|
|
+ vo.setCustomerId(customerId);
|
|
|
+ vo.setId(tag.get("id"));
|
|
|
+ vo.setName(tag.get("name"));
|
|
|
+ vo.setValue(tag.get("value"));
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+ private static List<CrmCustomerAiTagVo> parseAiResponse(R aiResponse, Long customerId) {
|
|
|
+ if (aiResponse == null || !Integer.valueOf(200).equals(aiResponse.get("code"))) {
|
|
|
+ throw new RuntimeException("AI响应异常: " +
|
|
|
+ (aiResponse != null ? aiResponse.get("msg") : "响应为空"));
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, String>> tagInfos = extractTagInfos(JSONUtil.toJsonStr(aiResponse));
|
|
|
+ if (CollectionUtils.isEmpty(tagInfos)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ return tagInfos.stream()
|
|
|
+ .map(tag -> buildTagVo(tag, customerId))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+ private static R callAiService(Map<String, Object> requestParam, Long logId) {
|
|
|
+ try {
|
|
|
+ String requestJson = mapper.writeValueAsString(requestParam);
|
|
|
+
|
|
|
+ ChatParam param = new ChatParam();
|
|
|
+ param.setChatId(logId.toString());
|
|
|
+ param.setStream(false);
|
|
|
+ param.setDetail(true);
|
|
|
+ ChatParam.Message message = new ChatParam.Message();
|
|
|
+ List<ChatParam.Message> messageList = new ArrayList<ChatParam.Message>();
|
|
|
+ message.setContent(requestJson);
|
|
|
+ message.setRole("user");
|
|
|
+ messageList.add(message);
|
|
|
+ param.setMessages(messageList);
|
|
|
+ ChatService chatService = SpringUtils.getBean(ChatService.class);
|
|
|
+ AiHostProper aiHost = SpringUtils.getBean(AiHostProper.class);
|
|
|
+
|
|
|
+ return chatService.initiatingTakeChat(param, aiHost.getAiApi(), APP_KEY);
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("AI服务调用失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private static Map<String, Object> buildRequestParam(Long customerId,
|
|
|
+ String tradeType,
|
|
|
+ List<Map<String, Object>> communication) {
|
|
|
+ Map<String, Object> requestParam = new HashMap<>();
|
|
|
+
|
|
|
+ // 获取各类数据
|
|
|
+ String tradeName = getDictLabel(tradeType);
|
|
|
+ Map<String, Object> tags = getTags(tradeType);
|
|
|
+ Map<String, Object> history = getHistory(communication,customerId.toString());
|
|
|
+ Map<String, Object> userInfo = getUserInfo(customerId);
|
|
|
+ Map<String, Object> aiInfo = getAiInfo(communication.remove(0));
|
|
|
+
|
|
|
+ // 合并数据
|
|
|
+ Stream.of(tags, history, userInfo, aiInfo)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .forEach(requestParam::putAll);
|
|
|
+
|
|
|
+ // 设置其他参数
|
|
|
+ requestParam.put("tradeName", tradeName);
|
|
|
+ requestParam.put("tradeType", tradeType);
|
|
|
+ requestParam.put("tagInfos", Collections.emptyList());
|
|
|
+ requestParam.put("isRepository", "");
|
|
|
+ requestParam.put("userContent", "");
|
|
|
+ requestParam.put("aiContent", "");
|
|
|
+ requestParam.put("likeRatio", userInfo != null ? userInfo.remove("likeRatio") : null);
|
|
|
+
|
|
|
+ return requestParam;
|
|
|
+ }
|
|
|
+ private static List<Map<String, Object>> parseCommunicationJson(String jsonStr) {
|
|
|
+ try {
|
|
|
+ return mapper.readValue(jsonStr,
|
|
|
+ new TypeReference<List<Map<String, Object>>>() {});
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("数据格式错误", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private static void validateParams(CrmCustomerAiTagParam content) {
|
|
|
+ if (ObjectUtil.isEmpty(content.getTradeType())
|
|
|
+ || ObjectUtil.isEmpty(content.getCustomerId())
|
|
|
+ || ObjectUtil.isEmpty(content.getLogId())
|
|
|
+ || ObjectUtil.isEmpty(content.getJson())) {
|
|
|
+ throw new IllegalArgumentException("参数不能为空");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 提取 tagInfos 数据
|
|
|
+ */
|
|
|
+ public static List<Map<String, String>> extractTagInfos(String jsonStr) {
|
|
|
+ try {
|
|
|
+ JsonNode root = mapper.readTree(jsonStr);
|
|
|
+
|
|
|
+ // 获取 responseData 数组
|
|
|
+ JsonNode responseData = root.path("data").path("responseData");
|
|
|
+
|
|
|
+ // 查找 AI 对话节点
|
|
|
+ for (JsonNode node : responseData) {
|
|
|
+ String moduleName = node.path("moduleName").asText();
|
|
|
+ if ("AI 对话".equals(moduleName)) {
|
|
|
+ // 获取 historyPreview 数组
|
|
|
+ JsonNode historyPreview = node.path("historyPreview");
|
|
|
+
|
|
|
+ // 查找 AI 节点的 historyPreview
|
|
|
+ for (JsonNode preview : historyPreview) {
|
|
|
+ String objType = preview.path("obj").asText();
|
|
|
+ if ("AI".equals(objType)) {
|
|
|
+ JsonNode valueNode = preview.path("value");
|
|
|
+
|
|
|
+ // 如果 value 是字符串,需要再次解析
|
|
|
+ if (valueNode.isTextual()) {
|
|
|
+ String valueStr = valueNode.asText();
|
|
|
+ JsonNode tagInfosNode = mapper.readTree(valueStr).path("tagInfos");
|
|
|
+
|
|
|
+ if (tagInfosNode.isArray()) {
|
|
|
+ return mapper.convertValue(tagInfosNode,
|
|
|
+ new TypeReference<List<Map<String, String>>>() {
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } else if (valueNode.isObject()) {
|
|
|
+ JsonNode tagInfosNode = valueNode.path("tagInfos");
|
|
|
+ if (tagInfosNode.isArray()) {
|
|
|
+ return mapper.convertValue(tagInfosNode,
|
|
|
+ new TypeReference<List<Map<String, String>>>() {
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取完整的 AI 响应数据
|
|
|
+ */
|
|
|
+ public static Map<String, Object> getAIResponseData(String jsonStr) {
|
|
|
+ try {
|
|
|
+ JsonNode root = mapper.readTree(jsonStr);
|
|
|
+ JsonNode responseData = root.path("data").path("responseData");
|
|
|
+
|
|
|
+ for (JsonNode node : responseData) {
|
|
|
+ if ("AI 对话".equals(node.path("moduleName").asText())) {
|
|
|
+ JsonNode historyPreview = node.path("historyPreview");
|
|
|
+
|
|
|
+ for (JsonNode preview : historyPreview) {
|
|
|
+ if ("AI".equals(preview.path("obj").asText())) {
|
|
|
+ JsonNode valueNode = preview.path("value");
|
|
|
+
|
|
|
+ if (valueNode.isTextual()) {
|
|
|
+ return mapper.readValue(valueNode.asText(),
|
|
|
+ new TypeReference<Map<String, Object>>() {
|
|
|
+ });
|
|
|
+ } else if (valueNode.isObject()) {
|
|
|
+ return mapper.convertValue(valueNode,
|
|
|
+ new TypeReference<Map<String, Object>>() {
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ return new HashMap<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 提取所有标签(包括 tagInfos 和 tags 定义)
|
|
|
+ */
|
|
|
+ public static Map<String, Object> getAllTagInfo(String jsonStr) {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取 AI 响应数据
|
|
|
+ Map<String, Object> aiResponse = getAIResponseData(jsonStr);
|
|
|
+
|
|
|
+ // tagInfos - 实际提取的标签值
|
|
|
+ List<Map<String, String>> tagInfos = (List<Map<String, String>>) aiResponse.get("tagInfos");
|
|
|
+ result.put("tagInfos", tagInfos != null ? tagInfos : new ArrayList<>());
|
|
|
+
|
|
|
+ // tags - 标签定义(从原始 query 中获取)
|
|
|
+ JsonNode root = mapper.readTree(jsonStr);
|
|
|
+ JsonNode responseData = root.path("data").path("responseData");
|
|
|
+
|
|
|
+ for (JsonNode node : responseData) {
|
|
|
+ if ("AI 对话".equals(node.path("moduleName").asText())) {
|
|
|
+ JsonNode query = node.path("query");
|
|
|
+ JsonNode tagsNode = query.path("tags");
|
|
|
+
|
|
|
+ if (tagsNode.isArray()) {
|
|
|
+ List<Map<String, String>> tags = mapper.convertValue(tagsNode,
|
|
|
+ new TypeReference<List<Map<String, String>>>() {
|
|
|
+ });
|
|
|
+ result.put("tags", tags);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Map<String, Object> getAiInfo(Map<String, Object> remove) {
|
|
|
+ HashMap<String, String> aiInfo = new HashMap<>();
|
|
|
+ aiInfo.put("name", "");
|
|
|
+ aiInfo.put("sex", "");
|
|
|
+ aiInfo.put("age", "");
|
|
|
+ aiInfo.put("city", "");
|
|
|
+ aiInfo.put("habits", "");
|
|
|
+ aiInfo.put("describe", "");
|
|
|
+ HashMap<String, Object> result = new HashMap<>();
|
|
|
+ result.put("aiInfo", aiInfo);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Map<String, Object> getUserInfo(Long customerId) {
|
|
|
+ CrmCustomerMapper customerMapper = SpringUtils.getBean(CrmCustomerMapper.class);
|
|
|
+ CrmCustomer crmCustomer = customerMapper.selectCrmCustomerById(customerId);
|
|
|
+ if (ObjectUtil.isEmpty(crmCustomer)) throw new RuntimeException("客户不存在");
|
|
|
+ CrmCustomerInfo crmCustomerInfo = customerMapper.selectCrmCustomerInfoById(customerId);
|
|
|
+ if (ObjectUtil.isEmpty(crmCustomerInfo)) {
|
|
|
+ crmCustomerInfo = new CrmCustomerInfo();
|
|
|
+ crmCustomerInfo.setCustomerId(crmCustomer.getCustomerId()).setName(crmCustomer.getCustomerName()).setSex(crmCustomer.getSex().toString())
|
|
|
+ .setTalk("首次交流");
|
|
|
+ customerMapper.insertCrmCustomerInfo(crmCustomerInfo);
|
|
|
+ }
|
|
|
+ HashMap<String, String> userInfo = new HashMap<String, String>();
|
|
|
+ userInfo.put("name", crmCustomerInfo.getName());
|
|
|
+ userInfo.put("sex", crmCustomerInfo.getSex());
|
|
|
+ userInfo.put("age", "");
|
|
|
+ userInfo.put("city", "");
|
|
|
+ userInfo.put("habits", "");
|
|
|
+ userInfo.put("describe", "");
|
|
|
+ HashMap<String, Object> result = new HashMap<>();
|
|
|
+ result.put("userInfo", userInfo);
|
|
|
+ result.put("likeRatio", ObjectUtil.isNotEmpty(crmCustomer.getIntention()) ? crmCustomer.getIntention() : "");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Map<String, Object> getHistory(List<Map<String, Object>> communication,String customerId) {
|
|
|
+ StringBuilder history = new StringBuilder();
|
|
|
+ history.append("{");
|
|
|
+ for (Map<String, Object> o :
|
|
|
+ communication) {
|
|
|
+ String role = (String) o.get("role");
|
|
|
+ String content = (String) o.get("content");
|
|
|
+ String roleTag = "user".equals(role) ? "user" : "ai";
|
|
|
+ history.append(String.format("\"%s\":\"%s\",", roleTag, content));
|
|
|
+ }
|
|
|
+ history.deleteCharAt(history.length() - 1).append("}");
|
|
|
+ Map<String, Object> result = new HashMap<String, Object>();
|
|
|
+ result.put("history", history);
|
|
|
+ ArrayList<Map> maps = new ArrayList<>();
|
|
|
+ communication.forEach(o->{
|
|
|
+ String role = (String) o.get("role");
|
|
|
+ String content = (String) o.get("content");
|
|
|
+ if (content != null && !content.trim().isEmpty()) { // 过滤空内容
|
|
|
+ String roleTag = "user".equals(role) ? "user" : "ai";
|
|
|
+ Map<String, String> message = new HashMap<>();
|
|
|
+ message.put(roleTag, content);
|
|
|
+ maps.add(message);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (!maps.isEmpty()){
|
|
|
+ CrmCustomerInfo crmCustomerInfo = new CrmCustomerInfo();
|
|
|
+ crmCustomerInfo.setCustomerId(Long.valueOf(customerId)).setAiChatRecord(JSONUtil.toJsonStr(maps));
|
|
|
+ SpringUtils.getBean(CrmCustomerMapper.class).updateCrmCustomerInfo(crmCustomerInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Map<String, Object> getTags(String tradeType) {
|
|
|
+ List<CrmCustomerPropertyTemplate> templates = SpringUtils.getBean(ICrmCustomerPropertyTemplateService.class).getBaseMapper().selectList(new LambdaQueryWrapper<CrmCustomerPropertyTemplate>().eq(
|
|
|
+ CrmCustomerPropertyTemplate::getTradeType, tradeType
|
|
|
+ ));
|
|
|
+ if (ObjectUtil.isEmpty(templates)) throw new RuntimeException("该行业无标签模板");
|
|
|
+ ArrayList<Map<String, String>> tags = new ArrayList<>();//标签及提示词
|
|
|
+ templates.forEach(o -> {
|
|
|
+ Map<String, String> tag = MapUtil.convertToMap(new CrmCustomerAiAutoTagVo(String.valueOf(o.getId()), o.getName(), o.getAiHint()));
|
|
|
+ tags.add(tag);
|
|
|
+ });
|
|
|
+ HashMap<String, Object> resultMap = new HashMap<>();
|
|
|
+ resultMap.put("tags", tags);
|
|
|
+ return resultMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String getDictLabel(String tradeType) {
|
|
|
+ List<SysDictData> tradeTypeDict = DictUtils.getDictCache(TRADE_TYPE);
|
|
|
+ String dictLabel;
|
|
|
+ if (ObjectUtil.isEmpty(tradeTypeDict)) {
|
|
|
+ dictLabel = DictUtils.getDictLabel(TRADE_TYPE, tradeType);
|
|
|
+ } else {
|
|
|
+ Map<String, String> collect = tradeTypeDict.stream().collect(Collectors.toMap(SysDictData::getDictValue,
|
|
|
+ SysDictData::getDictLabel, (v1, v2) -> v1)
|
|
|
+ );
|
|
|
+ dictLabel = collect.get(tradeType);
|
|
|
+ }
|
|
|
+ if (ObjectUtil.isEmpty(dictLabel)) {
|
|
|
+ throw new RuntimeException("字典中不存在该行业");
|
|
|
+ } else return dictLabel;
|
|
|
+ }
|
|
|
+}
|