Procházet zdrojové kódy

Merge remote-tracking branch 'origin/master'

zyy před 2 týdny
rodič
revize
e4c6fea685

+ 71 - 122
fs-admin/src/main/java/com/fs/task/CrmCustomerAiProcessingTask.java

@@ -1,18 +1,16 @@
 package com.fs.task;
 
+import com.fs.crm.domain.CrmCustomerAnalyze;
 import com.fs.crm.service.ICrmCustomerAnalyzeService;
-import com.google.common.collect.Lists;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
 
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
 
 @Component("CrmCustomerAiProcessingTask")
 @RequiredArgsConstructor
@@ -22,6 +20,7 @@ public class CrmCustomerAiProcessingTask {
     private final RedisTemplate redisTemplate;
 
     private static final String CRM_AI_REDIS_KEY = "crm:AI:data:processing";
+
     private final ICrmCustomerAnalyzeService crmCustomerAnalyzeService;
 
     // 自定义线程池
@@ -38,58 +37,49 @@ public class CrmCustomerAiProcessingTask {
             new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:由调用线程处理
     );
 
+    @SuppressWarnings("unchecked")
     public void process() {
-        List<Map<String,String>> range = (List<Map<String, String>>) redisTemplate.opsForList().range(CRM_AI_REDIS_KEY, 0, -1);
+//        一次只处理5条,AI响应慢避免阻塞
+        List<Map<String, Object>> range =
+                (List<Map<String, Object>>) redisTemplate.opsForList().range(CRM_AI_REDIS_KEY, 0, 4);
         if (range == null || range.isEmpty()) {
             log.info("CrmCustomerAiProcessingTask没有待处理的数据");
             return;
         }
-        log.info("CrmCustomerAiProcessingTask开始处理数据,条数"+range.size());
-        // 2. 每100条分成一批
-        List<List<Map<String, String>>> partitions = Lists.partition(range, 10);//ai沟通很慢,批量处理10条每批
-        int totalBatches = partitions.size();
-        log.info("共分为 {} 批, 每批"+(partitions.size()>1?"10":range.size())+"条", totalBatches);
+        final int total = range.size();
+        log.info("CrmCustomerAiProcessingTask开始处理数据, 条数: {}", total);
+
 
-        // 3. 统计计数器
         AtomicInteger successCount = new AtomicInteger(0);
         AtomicInteger failCount = new AtomicInteger(0);
-
         long startTime = System.currentTimeMillis();
-
-        // 4. 多线程处理
-        List<CompletableFuture<Void>> futures = partitions.stream()
-                .map(batch -> CompletableFuture.runAsync(() -> {
-                    processBatch(batch, successCount, failCount);
-                }, executorService))
-                .collect(Collectors.toList());
-
-        // 5. 等待所有任务完成
+        CompletableFuture<Void> futures = CompletableFuture.runAsync(
+                () -> processBatch(range, successCount, failCount), executorService);
         try {
-            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+            CompletableFuture.allOf(futures).join();
+        } catch (CompletionException e) {
+            Throwable cause = e.getCause() != null ? e.getCause() : e;
+            log.error("多线程处理异常", cause);
+            return;
+        }
 
-            long costTime = System.currentTimeMillis() - startTime;
-            log.info("CrmCustomerAiProcessingTask处理完成, 总条数: {}, 成功: {}, 失败: {}, 耗时: {}ms",
-                    range.size(), successCount.get(), failCount.get(), costTime);
-
-            // 6. 处理完成后,从Redis中删除已处理的数据
-            if (failCount.get() == 0) {
-                // 全部成功,删除整个key
-                redisTemplate.delete(CRM_AI_REDIS_KEY);
-                log.info("全部处理成功,已删除Redis数据");
-            } else {
-                // 有失败的数据,保留或移到失败队列
-                handleFailedData(partitions, successCount.get());
-            }
+        long costTime = System.currentTimeMillis() - startTime;
+        log.info("CrmCustomerAiProcessingTask处理完成, 总条数: {}, 成功: {}, 失败: {}, 耗时: {}ms",
+                total, successCount.get(), failCount.get(), costTime);
 
-        } catch (Exception e) {
-            log.error("多线程处理异常", e);
+        // 当前 processBatch 内任一条失败会抛异常导致 join 失败;能走到此处说明整批成功
+        if (failCount.get() == 0 && successCount.get() == total) {
+            redisTemplate.delete(CRM_AI_REDIS_KEY);
+            log.info("全部处理成功,已删除Redis数据");
+            return;
         }
-
+        log.warn("计数与预期不一致: 总条数={}, 成功={}, 失败={}, 未删除 Redis 队列", total,
+                successCount.get(), failCount.get());
     }
     /**
      * 处理单个批次
      */
-    private void processBatch(List<Map<String, String>> batch,
+    private void processBatch(List<Map<String, Object>> batch,
                               AtomicInteger successCount,
                               AtomicInteger failCount) {
         String threadName = Thread.currentThread().getName();
@@ -98,41 +88,13 @@ public class CrmCustomerAiProcessingTask {
         try {
             log.info("线程 {} 开始处理批次, 数据量: {}", threadName, batch.size());
 
-            List<CompletableFuture<Void>> customerFutures = batch.stream()
-                    .map(data -> CompletableFuture.runAsync(() -> {
-                        processSingleCustomer(data, successCount, failCount);
-                    }, executorService))
-                    .collect(Collectors.toList());
-
-            // 等待该批次内所有客户处理完成
-            CompletableFuture.allOf(customerFutures.toArray(new CompletableFuture[0]))
-                    .join();
-            // 示例:处理每条数据
-//            for (Map<String, String> data : batch) {
-//                // 获取数据
-//                Long customerId = Long.valueOf(data.get("customerId"));
-//                String dataJson = data.get("data");
-//                Long logId = Long.valueOf(data.get("logId"));
-//                //客户画像1
-//                crmCustomerAnalyzeService.aiGeneratedCustomerPortrait(customerId,dataJson,logId);
-//                //沟通总结
-//                crmCustomerAnalyzeService.aiCommunicationSummary(customerId,dataJson,logId);
-//                //沟通摘要
-//                crmCustomerAnalyzeService.aiCommunicationAbstract(customerId,dataJson,logId);
-//                //流失风险等级
-//                crmCustomerAnalyzeService.aiAttritionLevel(customerId,dataJson,logId);
-//                //客户关注点
-//                crmCustomerAnalyzeService.aiCustomerFocus(customerId,dataJson,logId);
-//                //客户意向度
-//                crmCustomerAnalyzeService.aiIntentionDegree(customerId,dataJson,logId);
-//
-//            }
+                for (Map<String, Object> data : batch) {
+                processSingleCustomer(data, successCount, failCount);
+            }
 
             long costTime = System.currentTimeMillis() - batchStartTime;
-            successCount.addAndGet(batch.size());
             log.info("线程 {} 批次处理完成, 数据量: {}, 耗时: {}ms",
                     threadName, batch.size(), costTime);
-
         } catch (Exception e) {
             failCount.addAndGet(batch.size());
             log.error("线程 {} 批次处理失败, 数据量: {}", threadName, batch.size(), e);
@@ -142,40 +104,53 @@ public class CrmCustomerAiProcessingTask {
     /**
      * 处理单个客户的AI分析(6个接口并行)
      */
-    private void processSingleCustomer(Map<String, String> data,
+    private void processSingleCustomer(Map<String, Object> data,
                                        AtomicInteger successCount,
-                                       AtomicInteger failCount) {
+                                       AtomicInteger failCount)  {
         try {
-            Long customerId = Long.valueOf(data.get("customerId"));
-            String dataJson = data.get("data");
-            Long logId = Long.valueOf(data.get("logId"));
+            Long customerId = (Long)data.get("customerId");
+            String dataJson = (String)data.get("data");
+            Long logId = (Long)data.get("logId");
 
             long startTime = System.currentTimeMillis();
 
-            // 并行调用6个AI分析接口
-            List<CompletableFuture<Void>> aiFutures = Arrays.asList(
-                    CompletableFuture.runAsync(() ->
-                            crmCustomerAnalyzeService.aiGeneratedCustomerPortrait(customerId, dataJson, logId), executorService),
-                    CompletableFuture.runAsync(() ->
-                            crmCustomerAnalyzeService.aiCommunicationSummary(customerId, dataJson, logId), executorService),
-                    CompletableFuture.runAsync(() ->
-                            crmCustomerAnalyzeService.aiCommunicationAbstract(customerId, dataJson, logId), executorService),
-                    CompletableFuture.runAsync(() ->
-                            crmCustomerAnalyzeService.aiAttritionLevel(customerId, dataJson, logId), executorService),
-                    CompletableFuture.runAsync(() ->
-                            crmCustomerAnalyzeService.aiCustomerFocus(customerId, dataJson, logId), executorService),
-                    CompletableFuture.runAsync(() ->
-                            crmCustomerAnalyzeService.aiIntentionDegree(customerId, dataJson, logId), executorService)
-            );
-
-            // 等待该客户的所有AI分析完成
-            CompletableFuture<Void> allAiFutures = CompletableFuture.allOf(
-                    aiFutures.toArray(new CompletableFuture[aiFutures.size()])
-            );
-            allAiFutures.get(30, TimeUnit.SECONDS);
+            // 6 个 AI 接口并行;使用 commonPool,避免与批次线程池 executorService 嵌套导致死锁
+            Executor asyncPool = ForkJoinPool.commonPool();
+            // 使用 supplyAsync 获取返回值,定义具体返回类型
+            CompletableFuture<String> portraitFuture = CompletableFuture.supplyAsync(() ->
+                    crmCustomerAnalyzeService.aiGeneratedCustomerPortrait(customerId, dataJson, logId), asyncPool);
+
+            CompletableFuture<String> summaryFuture = CompletableFuture.supplyAsync(() ->
+                    crmCustomerAnalyzeService.aiCommunicationSummary(customerId, dataJson, logId), asyncPool);
+
+            CompletableFuture<String> abstractFuture = CompletableFuture.supplyAsync(() ->
+                    crmCustomerAnalyzeService.aiCommunicationAbstract(customerId, dataJson, logId), asyncPool);
+
+            CompletableFuture<Long> attritionFuture = CompletableFuture.supplyAsync(() ->
+                    crmCustomerAnalyzeService.aiAttritionLevel(customerId, dataJson, logId), asyncPool);
+
+            CompletableFuture<String> focusFuture = CompletableFuture.supplyAsync(() ->
+                    crmCustomerAnalyzeService.aiCustomerFocus(customerId, dataJson, logId), asyncPool);
+
+            CompletableFuture<String> intentionFuture = CompletableFuture.supplyAsync(() ->
+                    crmCustomerAnalyzeService.aiIntentionDegree(customerId, dataJson, logId), asyncPool);
+
+// 等待所有异步任务完成
+            CompletableFuture.allOf(portraitFuture, summaryFuture, abstractFuture,
+                    attritionFuture, focusFuture, intentionFuture).join();
+        //      allAiFutures.get(60, TimeUnit.SECONDS);
+            CrmCustomerAnalyze crmCustomerAnalyze = new CrmCustomerAnalyze();
+            crmCustomerAnalyze.setCustomerId(customerId);
+            crmCustomerAnalyze.setCustomerPortraitJson(portraitFuture.get());
+            crmCustomerAnalyze.setCommunicationSummary(summaryFuture.get());
+            crmCustomerAnalyze.setCommunicationAbstract(abstractFuture.get());
+            crmCustomerAnalyze.setAttritionLevel(attritionFuture.get());
+            crmCustomerAnalyze.setCustomerFocusJson(focusFuture.get());
+            crmCustomerAnalyze.setIntentionDegree(intentionFuture.get());
+            Integer i = crmCustomerAnalyzeService.updateCrmCustomerAnalyzeByCustomerId(crmCustomerAnalyze);
             long costTime = System.currentTimeMillis() - startTime;
             successCount.incrementAndGet();
-            log.info("客户 {} 的AI分析完成, 耗时: {}ms", customerId, costTime);
+            log.info("客户 {} 的AI分析完成, 耗时: {}ms,更新{}条", customerId, costTime,i);
 
         } catch (Exception e) {
             failCount.incrementAndGet();
@@ -183,30 +158,4 @@ public class CrmCustomerAiProcessingTask {
                     data.get("customerId"), data.get("logId"), e);
         }
     }
-    /**
-     * 处理失败的数据
-     */
-    private void handleFailedData(List<List<Map<String, String>>> partitions, int successCount) {
-        try {
-            // 找出未成功处理的数据
-            List<Map<String, String>> failedData = partitions.stream()
-                    .flatMap(List::stream)
-                    .skip(successCount)
-                    .collect(Collectors.toList());
-
-            if (!failedData.isEmpty()) {
-                String failedKey = CRM_AI_REDIS_KEY + ":failed";
-                for (Map<String, String> data : failedData) {
-                    redisTemplate.opsForList().rightPush(failedKey, data);
-                }
-                log.info("失败数据已移至失败队列: {}, 数量: {}", failedKey, failedData.size());
-            }
-
-            // 清理已处理的数据(可选:根据业务需求决定是否删除)
-            // cleanProcessedData(partitions, successCount);
-
-        } catch (Exception e) {
-            log.error("处理失败数据异常", e);
-        }
-    }
 }

+ 8 - 6
fs-service/src/main/java/com/fs/crm/service/ICrmCustomerAnalyzeService.java

@@ -60,21 +60,23 @@ public interface ICrmCustomerAnalyzeService extends IService<CrmCustomerAnalyze>
      */
     int deleteCrmCustomerAnalyzeById(Long id);
 
-    void aiGeneratedCustomerPortrait(Long customerId, String dataJson,Long logId);
+    String aiGeneratedCustomerPortrait(Long customerId, String dataJson,Long logId);
 
-    void aiCommunicationSummary(Long customerId, String dataJson, Long logId);
+    String aiCommunicationSummary(Long customerId, String dataJson, Long logId);
 
-    void aiCommunicationAbstract(Long customerId, String dataJson, Long logId);
+    String aiCommunicationAbstract(Long customerId, String dataJson, Long logId);
 
-    void aiAttritionLevel(Long customerId, String dataJson, Long logId);
+    Long aiAttritionLevel(Long customerId, String dataJson, Long logId);
 
-    void aiCustomerFocus(Long customerId, String dataJson, Long logId);
+    String aiCustomerFocus(Long customerId, String dataJson, Long logId);
 
     String polishingScript(PolishingScriptParam param);
 
-    void aiIntentionDegree(Long customerId, String dataJson, Long logId);
+    String aiIntentionDegree(Long customerId, String dataJson, Long logId);
 
     List<CrmCustomerAnalyze> selectCrmCustomerAnalyzeListAll(CrmCustomerAnalyze crmCustomerAnalyze);
 
     String aiIntentionDegree(String content , Long chatId) ;
+
+    int updateCrmCustomerAnalyzeByCustomerId(CrmCustomerAnalyze crmCustomerAnalyze);
 }

+ 246 - 204
fs-service/src/main/java/com/fs/crm/service/impl/CrmCustomerAnalyzeServiceImpl.java

@@ -9,6 +9,7 @@ import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fasterxml.jackson.core.JsonProcessingException;
+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;
@@ -114,187 +115,194 @@ public class CrmCustomerAnalyzeServiceImpl extends ServiceImpl<CrmCustomerAnalyz
     }
     private static final String AI_PORTRAIT = "crm_ai_portrait";
     private static final ObjectMapper mapper = new ObjectMapper();
-    @Value("${crm.customer.ai.Key:mygpt-oPG2ifhnq0ODGioOBMUvMfOZGrtCykqw3oMeYLchdUDK5He6iNiactrhFWA0sID}")
+    @Value("${crm.customer.ai.Key:mygpt-iTUua2CHVd4WGrBbQQGl1HHjyyBAD1KuXARsxHj5eHpLYv5CfnOh8iwVU}")
     private String OTHER_KEY;
     //ai获取客户画像
     @Override
-    public void aiGeneratedCustomerPortrait(Long customerId, String dataJson,Long logId) {
+    public String aiGeneratedCustomerPortrait(Long customerId, String dataJson, Long logId) {
         Map<String, Object> stringObjectMap = buildRequestParam(customerId, dataJson);
-        stringObjectMap.put("modelType","客户画像");
-        log.info("请求参数:{}", stringObjectMap);
-        R aiResponse = CrmCustomerAiTagUtil.callAiService(stringObjectMap, logId,OTHER_KEY);
+        stringObjectMap.put("modelType", "客户画像");
+//        log.info("请求参数:{}", stringObjectMap);
+        R aiResponse = CrmCustomerAiTagUtil.callAiService(stringObjectMap, logId, OTHER_KEY);
 //        System.out.println(aiResponse);
-        JSONObject root = JSON.parseObject(JSONUtil.toJsonStr(aiResponse));
+        String result = "";
+        try {
 
-// 获取 data.responseData
-        JSONArray responseData = root.getJSONObject("data").getJSONArray("responseData");
+            JsonNode rootS = mapper.readTree(JSONUtil.toJsonStr(aiResponse));
+            JsonNode choices = rootS.path("data").path("choices");
 
-        JSONObject userInfo = null;
+            if (choices.isArray() && choices.size() > 0) {
+                JsonNode contentNode = choices.get(0).path("message").path("content");
 
-// 遍历 responseData
-        for (int i = 0; i < responseData.size(); i++) {
-            JSONObject node = responseData.getJSONObject(i);
-            JSONArray historyPreview = node.getJSONArray("historyPreview");
+                if (contentNode.isTextual()) {
+                    String contentStr = contentNode.asText();
+                    // 将content字符串解析为JsonNode
+                    JsonNode contentArray = mapper.readTree(contentStr);
 
-            if (historyPreview != null) {
-                for (int j = 0; j < historyPreview.size(); j++) {
-                    JSONObject historyItem = historyPreview.getJSONObject(j);
+                    if (contentArray.isArray() && contentArray.size() > 1) {
+                        JsonNode secondElement = contentArray.get(1);
+                        JsonNode textNode = secondElement.path("text");
 
-                    // 找到 obj 为 "AI" 的项
-                    if ("AI".equals(historyItem.getString("obj"))) {
-                        String valueStr = historyItem.getString("value");
-                        JSONObject valueObj = JSON.parseObject(valueStr);
-                        userInfo = valueObj.getJSONObject("userInfo");
-                        break;
+                        if (!textNode.isMissingNode()) {
+                            JsonNode contentInnerNode = textNode.path("content");
+
+                            if (contentInnerNode.isTextual()) {
+                                String innerJsonStr = contentInnerNode.asText();
+                                JsonNode innerJson = mapper.readTree(innerJsonStr);
+                                JsonNode userInfo = innerJson.path("userInfo");
+                                result =userInfo.toString();
+                            }
+                        }
                     }
                 }
             }
-            if (userInfo != null) break;
-        }
 
-        if (userInfo != null) {
-            CrmCustomerAnalyze crmCustomerAnalyze = new CrmCustomerAnalyze();
-            crmCustomerAnalyze.setCustomerId(customerId);
-            crmCustomerAnalyze.setCustomerPortraitJson(userInfo.toString());
-            baseMapper.updateCustomerPortrait(crmCustomerAnalyze);
+        } catch (Exception e) {
+            e.printStackTrace();
         }
+        return result;
     }
 
     @Override
-    public void aiCommunicationSummary(Long customerId, String dataJson, Long logId) {
+    public String aiCommunicationSummary(Long customerId, String dataJson, Long logId) {
         Map<String, Object> stringObjectMap = buildRequestParam(customerId, dataJson);
         stringObjectMap.put("modelType","沟通总结");
-        log.info("请求参数:{}", stringObjectMap);
+//        log.info("请求参数:{}", stringObjectMap);
         R aiResponse = CrmCustomerAiTagUtil.callAiService(stringObjectMap, logId,OTHER_KEY);
-        JSONObject root = JSON.parseObject(JSONUtil.toJsonStr(aiResponse));
-//        System.out.println(aiResponse);
-// 获取 data.responseData
-        JSONArray responseData = root.getJSONObject("data").getJSONArray("responseData");
+        String result = "";
+        try {
+
+            JsonNode rootS = mapper.readTree(JSONUtil.toJsonStr(aiResponse));
+            JsonNode choices = rootS.path("data").path("choices");
 
-        JSONObject summary = null;
+            if (choices.isArray() && choices.size() > 0) {
+                JsonNode contentNode = choices.get(0).path("message").path("content");
 
-// 遍历 responseData
-        for (int i = 0; i < responseData.size(); i++) {
-            JSONObject node = responseData.getJSONObject(i);
-            JSONArray historyPreview = node.getJSONArray("historyPreview");
+                if (contentNode.isTextual()) {
+                    String contentStr = contentNode.asText();
+                    // 将content字符串解析为JsonNode
+                    JsonNode contentArray = mapper.readTree(contentStr);
 
-            if (historyPreview != null) {
-                for (int j = 0; j < historyPreview.size(); j++) {
-                    JSONObject historyItem = historyPreview.getJSONObject(j);
+                    if (contentArray.isArray() && contentArray.size() > 1) {
+                        JsonNode secondElement = contentArray.get(1);
+                        JsonNode textNode = secondElement.path("text");
 
-                    // 找到 obj 为 "AI" 的项
-                    if ("AI".equals(historyItem.getString("obj"))) {
-                        String valueStr = historyItem.getString("value");
-                        JSONObject valueObj = JSON.parseObject(valueStr);
-                        summary = valueObj.getJSONObject("userInfo");
-                        break;
+                        if (!textNode.isMissingNode()) {
+                            JsonNode contentInnerNode = textNode.path("content");
+
+                            if (contentInnerNode.isTextual()) {
+                                String innerJsonStr = contentInnerNode.asText();
+                                JsonNode innerJson = mapper.readTree(innerJsonStr);
+                                JsonNode userInfo = innerJson.path("userInfo");
+                                result = userInfo.get("沟通总结").asText();
+                            }
+                        }
                     }
                 }
             }
-            if (summary != null) break;
-        }
 
-        if (!summary.isEmpty() ) {
-            String summaryText = summary.getString("沟通总结");
-            CrmCustomerAnalyze crmCustomerAnalyze = new CrmCustomerAnalyze();
-            crmCustomerAnalyze.setCustomerId(customerId);
-            crmCustomerAnalyze.setCommunicationSummary(summaryText);
-            baseMapper.updateCustomerPortrait(crmCustomerAnalyze);
+        } catch (Exception e) {
+            e.printStackTrace();
         }
+        return result;
     }
 
     @Override
-    public void aiCommunicationAbstract(Long customerId, String dataJson, Long logId) {
+    public String aiCommunicationAbstract(Long customerId, String dataJson, Long logId) {
         Map<String, Object> stringObjectMap = buildRequestParam(customerId, dataJson);
         stringObjectMap.put("modelType","沟通摘要");
         stringObjectMap.remove("userInfo");
         HashMap<String, String> map = MapUtil.of("沟通摘要", "");
         stringObjectMap.put("userInfo", map);
-        log.info("请求参数:{}", stringObjectMap);
+//        log.info("请求参数:{}", stringObjectMap);
 
         R aiResponse = CrmCustomerAiTagUtil.callAiService(stringObjectMap, logId,OTHER_KEY);
-        JSONObject root = JSON.parseObject(JSONUtil.toJsonStr(aiResponse));
 //        System.out.println(aiResponse);
 
-// 获取 data.responseData
-        JSONArray responseData = root.getJSONObject("data").getJSONArray("responseData");
+        String result = "";
+        try {
+
+            JsonNode rootS = mapper.readTree(JSONUtil.toJsonStr(aiResponse));
+            JsonNode choices = rootS.path("data").path("choices");
+
+            if (choices.isArray() && choices.size() > 0) {
+                JsonNode contentNode = choices.get(0).path("message").path("content");
 
-        JSONObject summary = null;
+                if (contentNode.isTextual()) {
+                    String contentStr = contentNode.asText();
+                    // 将content字符串解析为JsonNode
+                    JsonNode contentArray = mapper.readTree(contentStr);
 
-// 遍历 responseData
-        for (int i = 0; i < responseData.size(); i++) {
-            JSONObject node = responseData.getJSONObject(i);
-            JSONArray historyPreview = node.getJSONArray("historyPreview");
+                    if (contentArray.isArray() && contentArray.size() > 1) {
+                        JsonNode secondElement = contentArray.get(1);
+                        JsonNode textNode = secondElement.path("text");
 
-            if (historyPreview != null) {
-                for (int j = 0; j < historyPreview.size(); j++) {
-                    JSONObject historyItem = historyPreview.getJSONObject(j);
+                        if (!textNode.isMissingNode()) {
+                            JsonNode contentInnerNode = textNode.path("content");
 
-                    // 找到 obj 为 "AI" 的项
-                    if ("AI".equals(historyItem.getString("obj"))) {
-                        String valueStr = historyItem.getString("value");
-                        JSONObject valueObj = JSON.parseObject(valueStr);
-                        summary = valueObj.getJSONObject("userInfo");
-                        break;
+                            if (contentInnerNode.isTextual()) {
+                                String innerJsonStr = contentInnerNode.asText();
+                                JsonNode innerJson = mapper.readTree(innerJsonStr);
+                                JsonNode userInfo = innerJson.path("userInfo");
+                                result = userInfo.get("沟通摘要").asText();
+                            }
+                        }
                     }
                 }
             }
-            if (summary != null) break;
-        }
-        if (summary != null){
-            CrmCustomerAnalyze c = new CrmCustomerAnalyze();
-            c.setCustomerId(customerId);
-            c.setCommunicationAbstract(summary.getString("沟通摘要"));
-            baseMapper.updateCustomerPortrait(c);
+
+        } catch (Exception e) {
+            e.printStackTrace();
         }
+        return result;
     }
 
     @Override
-    public void aiAttritionLevel(Long customerId, String dataJson, Long logId) {
+    public Long aiAttritionLevel(Long customerId, String dataJson, Long logId) {
         Map<String, Object> stringObjectMap = buildRequestParam(customerId, dataJson);
         stringObjectMap.put("modelType","流失风险等级");
         stringObjectMap.remove("userInfo");
         HashMap<String, String> map = MapUtil.of("流失风险等级", "");
         stringObjectMap.put("userInfo", map);
-        log.info("请求参数:{}", stringObjectMap);
+//        log.info("请求参数:{}", stringObjectMap);
 
         R aiResponse = CrmCustomerAiTagUtil.callAiService(stringObjectMap, logId,OTHER_KEY);
-        JSONObject root = JSON.parseObject(JSONUtil.toJsonStr(aiResponse));
-//        System.out.println(aiResponse);
+        Long result = null;
+        try {
 
-// 获取 data.responseData
-        JSONArray responseData = root.getJSONObject("data").getJSONArray("responseData");
+            JsonNode rootS = mapper.readTree(JSONUtil.toJsonStr(aiResponse));
+            JsonNode choices = rootS.path("data").path("choices");
+
+            if (choices.isArray() && choices.size() > 0) {
+                JsonNode contentNode = choices.get(0).path("message").path("content");
 
-        JSONObject summary = null;
+                if (contentNode.isTextual()) {
+                    String contentStr = contentNode.asText();
+                    // 将content字符串解析为JsonNode
+                    JsonNode contentArray = mapper.readTree(contentStr);
 
-// 遍历 responseData
-        for (int i = 0; i < responseData.size(); i++) {
-            JSONObject node = responseData.getJSONObject(i);
-            JSONArray historyPreview = node.getJSONArray("historyPreview");
+                    if (contentArray.isArray() && contentArray.size() > 1) {
+                        JsonNode secondElement = contentArray.get(1);
+                        JsonNode textNode = secondElement.path("text");
 
-            if (historyPreview != null) {
-                for (int j = 0; j < historyPreview.size(); j++) {
-                    JSONObject historyItem = historyPreview.getJSONObject(j);
+                        if (!textNode.isMissingNode()) {
+                            JsonNode contentInnerNode = textNode.path("content");
 
-                    // 找到 obj 为 "AI" 的项
-                    if ("AI".equals(historyItem.getString("obj"))) {
-                        String valueStr = historyItem.getString("value");
-                        JSONObject valueObj = JSON.parseObject(valueStr);
-                        summary = valueObj.getJSONObject("userInfo");
-                        break;
+                            if (contentInnerNode.isTextual()) {
+                                String innerJsonStr = contentInnerNode.asText();
+                                JsonNode innerJson = mapper.readTree(innerJsonStr);
+                                String userInfo = innerJson.path("userInfo").path("流失风险等级").asText();
+                                result = getScore(userInfo);
+                            }
+                        }
                     }
                 }
             }
-            if (summary != null && !summary.isEmpty()) break;
-        }
-        if (summary != null && !summary.isEmpty()){
-            String level = summary.getString("流失风险等级");
 
-            CrmCustomerAnalyze c = new CrmCustomerAnalyze();
-            c.setCustomerId(customerId);
-            c.setAttritionLevel(getScore(level));
-            baseMapper.updateCustomerPortrait(c);
+        } catch (Exception e) {
+            e.printStackTrace();
         }
+        return result;
     }
 
 
@@ -308,49 +316,52 @@ public class CrmCustomerAnalyzeServiceImpl extends ServiceImpl<CrmCustomerAnalyz
 
 
     @Override
-    public void aiCustomerFocus(Long customerId, String dataJson, Long logId) {
+    public String aiCustomerFocus(Long customerId, String dataJson, Long logId) {
         Map<String, Object> stringObjectMap = buildRequestParam(customerId, dataJson);
         stringObjectMap.put("modelType","客户关注点");
         stringObjectMap.remove("userInfo");
         HashMap<String, String> map = MapUtil.of("客户关注点", "");
         stringObjectMap.put("userInfo", map);
-        log.info("请求参数:{}", stringObjectMap);
+//        log.info("请求参数:{}", stringObjectMap);
 
         R aiResponse = CrmCustomerAiTagUtil.callAiService(stringObjectMap, logId,OTHER_KEY);
 //        System.out.println(aiResponse);
-        JSONObject root = JSON.parseObject(JSONUtil.toJsonStr(aiResponse));
+        String result = "";
+        try {
 
-// 获取 data.responseData
-        JSONArray responseData = root.getJSONObject("data").getJSONArray("responseData");
+            JsonNode rootS = mapper.readTree(JSONUtil.toJsonStr(aiResponse));
+            JsonNode choices = rootS.path("data").path("choices");
+
+            if (choices.isArray() && choices.size() > 0) {
+                JsonNode contentNode = choices.get(0).path("message").path("content");
 
-        JSONObject summary = null;
+                if (contentNode.isTextual()) {
+                    String contentStr = contentNode.asText();
+                    // 将content字符串解析为JsonNode
+                    JsonNode contentArray = mapper.readTree(contentStr);
 
-// 遍历 responseData
-        for (int i = 0; i < responseData.size(); i++) {
-            JSONObject node = responseData.getJSONObject(i);
-            JSONArray historyPreview = node.getJSONArray("historyPreview");
+                    if (contentArray.isArray() && contentArray.size() > 1) {
+                        JsonNode secondElement = contentArray.get(1);
+                        JsonNode textNode = secondElement.path("text");
 
-            if (historyPreview != null) {
-                for (int j = 0; j < historyPreview.size(); j++) {
-                    JSONObject historyItem = historyPreview.getJSONObject(j);
+                        if (!textNode.isMissingNode()) {
+                            JsonNode contentInnerNode = textNode.path("content");
 
-                    // 找到 obj 为 "AI" 的项
-                    if ("AI".equals(historyItem.getString("obj"))) {
-                        String valueStr = historyItem.getString("value");
-                        JSONObject valueObj = JSON.parseObject(valueStr);
-                        summary = valueObj.getJSONObject("userInfo");
-                        break;
+                            if (contentInnerNode.isTextual()) {
+                                String innerJsonStr = contentInnerNode.asText();
+                                JsonNode innerJson = mapper.readTree(innerJsonStr);
+                                JsonNode userInfo = innerJson.path("userInfo");
+                                result = userInfo.get("客户关注点").asText();
+                            }
+                        }
                     }
                 }
             }
-            if (summary != null && !summary.isEmpty()) break;
-        }
-        if (summary != null && !summary.isEmpty()){
-            CrmCustomerAnalyze crmCustomerAnalyze = new CrmCustomerAnalyze();
-            crmCustomerAnalyze.setCustomerId(customerId);
-            crmCustomerAnalyze.setCustomerFocusJson(summary.getString("客户关注点"));
-            baseMapper.updateCustomerPortrait(crmCustomerAnalyze);
+
+        } catch (Exception e) {
+            e.printStackTrace();
         }
+        return result;
     }
 
     @Override
@@ -368,75 +379,93 @@ public class CrmCustomerAnalyzeServiceImpl extends ServiceImpl<CrmCustomerAnalyz
         requestParam.put("aiContent", "");
         requestParam.put("likeRatio", "");
         R aiResponse = CrmCustomerAiTagUtil.callAiService(requestParam, Long.valueOf(param.getChatId()),OTHER_KEY);
-//        System.out.println(aiResponse);
-        JSONObject root = JSON.parseObject(JSONUtil.toJsonStr(aiResponse));
-        JSONArray responseData = root.getJSONObject("data").getJSONArray("responseData");
-
-// 遍历 responseData
-        for (int i = 0; i < responseData.size(); i++) {
-            JSONObject node = responseData.getJSONObject(i);
-            JSONArray historyPreview = node.getJSONArray("historyPreview");
-
-            if (historyPreview != null) {
-                for (int j = 0; j < historyPreview.size(); j++) {
-                    JSONObject historyItem = historyPreview.getJSONObject(j);
-
-                    // 找到 obj 为 "AI" 的项
-                    if ("AI".equals(historyItem.getString("obj"))) {
-                        String valueStr = historyItem.getString("value");
-                        JSONObject valueObj = JSON.parseObject(valueStr);
-                        return valueObj.getString("aiContent");
+        System.out.println(aiResponse);
+        String result = "";
+        try {
+
+            JsonNode rootS = mapper.readTree(JSONUtil.toJsonStr(aiResponse));
+            JsonNode choices = rootS.path("data").path("choices");
+
+            if (choices.isArray() && choices.size() > 0) {
+                JsonNode contentNode = choices.get(0).path("message").path("content");
+
+                if (contentNode.isTextual()) {
+                    String contentStr = contentNode.asText();
+                    // 将content字符串解析为JsonNode
+                    JsonNode contentArray = mapper.readTree(contentStr);
+
+                    if (contentArray.isArray() && contentArray.size() > 1) {
+                        JsonNode secondElement = contentArray.get(1);
+                        JsonNode textNode = secondElement.path("text");
+
+                        if (!textNode.isMissingNode()) {
+                            JsonNode contentInnerNode = textNode.path("content");
+
+                            if (contentInnerNode.isTextual()) {
+                                String innerJsonStr = contentInnerNode.asText();
+//                                JsonNode innerJson = mapper.readTree(innerJsonStr);
+//                                JsonNode userInfo1 = innerJson.path("aiContent");
+//                               = userInfo1.asText();
+                                result = innerJsonStr;
+                            }
+                        }
                     }
                 }
             }
+
+        } catch (Exception e) {
+            e.printStackTrace();
         }
-        return "";
+        return result;
     }
 
     @Override
-    public void aiIntentionDegree(Long customerId, String dataJson, Long logId) {
+    public String aiIntentionDegree(Long customerId, String dataJson, Long logId) {
         Map<String, Object> stringObjectMap = buildRequestParam(customerId, dataJson);
         stringObjectMap.put("modelType","客户意向度");
         stringObjectMap.remove("userInfo");
         HashMap<String, String> map = MapUtil.of("客户意向度", "");
         stringObjectMap.put("userInfo", map);
-        log.info("请求参数:{}", stringObjectMap);
+//        log.info("请求参数:{}", stringObjectMap);
 
         R aiResponse = CrmCustomerAiTagUtil.callAiService(stringObjectMap, logId,OTHER_KEY);
 //        System.out.println(aiResponse);
-        JSONObject root = JSON.parseObject(JSONUtil.toJsonStr(aiResponse));
+        String result = "";
+        try {
 
-// 获取 data.responseData
-        JSONArray responseData = root.getJSONObject("data").getJSONArray("responseData");
+            JsonNode rootS = mapper.readTree(JSONUtil.toJsonStr(aiResponse));
+            JsonNode choices = rootS.path("data").path("choices");
+
+            if (choices.isArray() && choices.size() > 0) {
+                JsonNode contentNode = choices.get(0).path("message").path("content");
 
-        JSONObject summary = null;
+                if (contentNode.isTextual()) {
+                    String contentStr = contentNode.asText();
+                    // 将content字符串解析为JsonNode
+                    JsonNode contentArray = mapper.readTree(contentStr);
 
-// 遍历 responseData
-        for (int i = 0; i < responseData.size(); i++) {
-            JSONObject node = responseData.getJSONObject(i);
-            JSONArray historyPreview = node.getJSONArray("historyPreview");
+                    if (contentArray.isArray() && contentArray.size() > 1) {
+                        JsonNode secondElement = contentArray.get(1);
+                        JsonNode textNode = secondElement.path("text");
 
-            if (historyPreview != null) {
-                for (int j = 0; j < historyPreview.size(); j++) {
-                    JSONObject historyItem = historyPreview.getJSONObject(j);
+                        if (!textNode.isMissingNode()) {
+                            JsonNode contentInnerNode = textNode.path("content");
 
-                    // 找到 obj 为 "AI" 的项
-                    if ("AI".equals(historyItem.getString("obj"))) {
-                        String valueStr = historyItem.getString("value");
-                        JSONObject valueObj = JSON.parseObject(valueStr);
-                        summary = valueObj.getJSONObject("userInfo");
-                        break;
+                            if (contentInnerNode.isTextual()) {
+                                String innerJsonStr = contentInnerNode.asText();
+                                JsonNode innerJson = mapper.readTree(innerJsonStr);
+                                JsonNode userInfo = innerJson.path("userInfo");
+                                result = userInfo.get("客户意向度").asText();
+                            }
+                        }
                     }
                 }
             }
-            if (summary != null && !summary.isEmpty()) break;
-        }
-        if (summary != null && !summary.isEmpty()) {
-            CrmCustomerAnalyze crmCustomerAnalyze = new CrmCustomerAnalyze();
-            crmCustomerAnalyze.setCustomerId(customerId);
-            crmCustomerAnalyze.setIntentionDegree(summary.getString("客户意向度"));
-            baseMapper.updateCustomerPortrait(crmCustomerAnalyze);
+
+        } catch (Exception e) {
+            e.printStackTrace();
         }
+        return result;
     }
 
     @Override
@@ -475,40 +504,53 @@ public class CrmCustomerAnalyzeServiceImpl extends ServiceImpl<CrmCustomerAnalyz
         requestParam.put("aiContent", "");
         requestParam.put("likeRatio", "");
         requestParam.put("modelType","客户意向度");
-        log.info("请求参数:{}", requestParam);
+//        log.info("请求参数:{}", requestParam);
 
         R aiResponse = CrmCustomerAiTagUtil.callAiService(requestParam, chatId,OTHER_KEY);
         JSONObject root = JSON.parseObject(JSONUtil.toJsonStr(aiResponse));
 //        System.out.println(aiResponse);
 // 获取 data.responseData
-        JSONArray responseData = root.getJSONObject("data").getJSONArray("responseData");
+        String result = "";
+        try {
 
-        String summary = null;
+            JsonNode rootS = mapper.readTree(JSONUtil.toJsonStr(aiResponse));
+            JsonNode choices = rootS.path("data").path("choices");
 
-// 遍历 responseData
-        for (int i = 0; i < responseData.size(); i++) {
-            JSONObject node = responseData.getJSONObject(i);
-            JSONArray historyPreview = node.getJSONArray("historyPreview");
+            if (choices.isArray() && choices.size() > 0) {
+                JsonNode contentNode = choices.get(0).path("message").path("content");
 
-            if (historyPreview != null) {
-                for (int j = 0; j < historyPreview.size(); j++) {
-                    JSONObject historyItem = historyPreview.getJSONObject(j);
+                if (contentNode.isTextual()) {
+                    String contentStr = contentNode.asText();
+                    // 将content字符串解析为JsonNode
+                    JsonNode contentArray = mapper.readTree(contentStr);
 
-                    // 找到 obj 为 "AI" 的项
-                    if ("AI".equals(historyItem.getString("obj"))) {
-                        String valueStr = historyItem.getString("value");
-                        JSONObject valueObj = JSON.parseObject(valueStr);
-                        summary = valueObj.getString("userIntent");
-                        break;
+                    if (contentArray.isArray() && contentArray.size() > 1) {
+                        JsonNode secondElement = contentArray.get(1);
+                        JsonNode textNode = secondElement.path("text");
+
+                        if (!textNode.isMissingNode()) {
+                            JsonNode contentInnerNode = textNode.path("content");
+
+                            if (contentInnerNode.isTextual()) {
+                                String innerJsonStr = contentInnerNode.asText();
+                                JsonNode innerJson = mapper.readTree(innerJsonStr);
+                                JsonNode userInfo = innerJson.path("userIntent");
+                                result = userInfo.asText();
+                            }
+                        }
                     }
                 }
             }
-            if (summary != null && !summary.isEmpty()) break;
-        }
-        if (summary != null && !summary.isEmpty()) {
-            return summary;
+
+        } catch (Exception e) {
+            e.printStackTrace();
         }
-        return null;
+        return result;
+    }
+
+    @Override
+    public int updateCrmCustomerAnalyzeByCustomerId(CrmCustomerAnalyze crmCustomerAnalyze) {
+        return baseMapper.updateCustomerPortrait(crmCustomerAnalyze);
     }
 
     private Map<String, Object> buildRequestParam(Long customerId,
@@ -545,7 +587,7 @@ public class CrmCustomerAnalyzeServiceImpl extends ServiceImpl<CrmCustomerAnalyz
         List<String> dictValue = portraits.stream().map(SysDictData::getDictValue).collect(Collectors.toList());
 //        Map<String, String> portraitMap = portraits.stream().collect(Collectors.toMap(SysDictData::getDictValue, SysDictData::getDictLabel));
 
-        if (crmCustomerAnalyze.getCustomerPortraitJson() != null){
+        if (!crmCustomerAnalyze.getCustomerPortraitJson().isEmpty()){
             Map<String, String> portraitList = JSON.parseObject(
                     crmCustomerAnalyze.getCustomerPortraitJson(),
                     new TypeReference<Map<String, String>>() {}

+ 37 - 36
fs-service/src/main/java/com/fs/crm/utils/CrmCustomerAiTagUtil.java

@@ -45,7 +45,7 @@ public class CrmCustomerAiTagUtil {
 
     //行业字典名称
     private static final String TRADE_TYPE = "trade_type";
-    @Value("${crm.customer.ai.key:mygpt-oPG2ifhnq0ODGioOBMUvMfOZGrtCykqw3oMeYLchdUDK5He6iNiactrhFWA0sID}")
+    @Value("${crm.customer.ai.key:mygpt-iTUua2CHVd4WGrBbQQGl1HHjyyBAD1KuXARsxHj5eHpLYv5CfnOh8iwVU}")
     private String appKey;
     private static final String CRM_AI_REDIS_KEY = "crm:AI:data:processing";
     private static final String AI_PORTRAIT = "crm_ai_portrait";
@@ -80,7 +80,7 @@ public class CrmCustomerAiTagUtil {
 
         // 3. 调用AI服务
         R aiResponse = callAiService(requestParam, logId,APP_KEY);
-//        System.out.println(aiResponse);
+        System.out.println(aiResponse);
         // 4. 解析响应并保存
         List<CrmCustomerAiTagVo> results = parseAiResponse(aiResponse, customerId);
 
@@ -204,46 +204,46 @@ public class CrmCustomerAiTagUtil {
      */
     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 对话#7".equals(moduleName)) {
-                    // 获取 historyPreview 数组
-                    JsonNode historyPreview = node.path("historyPreview");
+            List<Map<String, String>> result = new ArrayList<>();
+            ObjectMapper mapper = new ObjectMapper();
 
-                    // 查找 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>>>() {
-                                            });
+            JsonNode root = mapper.readTree(jsonStr);
+            JsonNode choices = root.path("data").path("choices");
+
+            if (choices.isArray() && choices.size() > 0) {
+                JsonNode contentNode = choices.get(0).path("message").path("content");
+
+                if (contentNode.isTextual()) {
+                    String contentStr = contentNode.asText();
+                    // 将content字符串解析为JsonNode
+                    JsonNode contentArray = mapper.readTree(contentStr);
+
+                    if (contentArray.isArray() && contentArray.size() > 1) {
+                        JsonNode secondElement = contentArray.get(1);
+                        JsonNode textNode = secondElement.path("text");
+
+                        if (!textNode.isMissingNode()) {
+                            JsonNode contentInnerNode = textNode.path("content");
+
+                            if (contentInnerNode.isTextual()) {
+                                String innerJsonStr = contentInnerNode.asText();
+                                JsonNode innerJson = mapper.readTree(innerJsonStr);
+                                JsonNode tagInfosArray = innerJson.path("tagInfos");
+
+                                for (JsonNode tag : tagInfosArray) {
+                                    Map<String, String> tagMap = new HashMap<>();
+                                    tagMap.put("id", tag.path("id").asText());
+                                    tagMap.put("name", tag.path("name").asText());
+                                    tagMap.put("value", tag.path("value").asText());
+                                    result.add(tagMap);
                                 }
                             }
                         }
                     }
                 }
             }
+
+            return result;
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -356,11 +356,12 @@ public class CrmCustomerAiTagUtil {
         HashMap<String, String> userInfo = new HashMap<String, String>();
         List<SysDictData> portraits = SpringUtils.getBean(SysDictDataMapper.class).selectDictDataByType(AI_PORTRAIT);
         List<String> dictValue = portraits.stream().map(SysDictData::getDictValue).collect(Collectors.toList());
-        if (crmCustomerAnalyze.getCustomerPortraitJson() != null){
+        if (!crmCustomerAnalyze.getCustomerPortraitJson().isEmpty()){
             Map<String, String> portraitList = JSON.parseObject(
                     crmCustomerAnalyze.getCustomerPortraitJson(),
                     new cn.hutool.core.lang.TypeReference<Map<String, String>>() {}
             );
+            portraitList.keySet().removeIf(k -> k.matches(".*[a-zA-Z].*"));
             userInfo.putAll(portraitList);
 
         }else {

+ 11 - 0
fs-service/src/main/java/com/fs/his/domain/FsIntegralCart.java

@@ -4,13 +4,20 @@ import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 @TableName("fs_integral_cart")
 @Data
+@Builder
+@NoArgsConstructor      // 无参构造
+@AllArgsConstructor
 public class FsIntegralCart {
     /**
      * 主键ID
@@ -51,4 +58,8 @@ public class FsIntegralCart {
      */
     @TableField(exist = false)
     private Boolean addToExist;
+
+    @ApiModelProperty("是否选中:1是,0否")
+    @TableField("is_selected")
+    private Boolean isSelected;
 }

+ 6 - 0
fs-service/src/main/java/com/fs/his/mapper/FsIntegralCartMapper.java

@@ -2,7 +2,9 @@ package com.fs.his.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.his.domain.FsIntegralCart;
+import com.fs.his.param.GetFsIntegralCartListParam;
 import com.fs.his.vo.FsIntegralCartVO;
+import com.fs.his.vo.GetFsIntegralCartListVo;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Update;
 
@@ -39,4 +41,8 @@ public interface FsIntegralCartMapper extends BaseMapper<FsIntegralCart> {
 
     @Update("update fs_integral_cart set integral = #{integral} where goods_id = #{goodsId}")
     void updateIntegralByGoodsId(@Param("goodsId")Long goodsId,@Param("integral") Long integral);
+
+    List<GetFsIntegralCartListVo> getFsIntegralCartList(@Param("param") GetFsIntegralCartListParam param, @Param("aLong") Long aLong, @Param("cartId") List<Long> cartId);
+
+    int updateQuantityAtomically(@Param("userId") Long userId, @Param("goodsId") Long goodsId, @Param("addQuantity") Integer addQuantity, @Param("maxQuantity") int maxQuantity);
 }

+ 26 - 0
fs-service/src/main/java/com/fs/his/param/AddGoodsIntoCartParam.java

@@ -0,0 +1,26 @@
+package com.fs.his.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+@Data
+public class AddGoodsIntoCartParam {
+
+    @NotNull(message = "积分商品ID不能为空")
+    @ApiModelProperty("积分商品ID")
+    private Long goodsId;
+
+    @Max(value = 99, message = "单商品最多添加99件")
+    @Min(value = 1, message = "商品数量至少为1")
+    @NotNull(message = "商品数量不能为空")
+    @ApiModelProperty("商品数量")
+    private Integer quantity;
+
+    @NotNull(message = "是否选中:1是,0否,不能为空")
+    @ApiModelProperty("是否选中:1是,0否")
+    private Boolean isSelected;
+}

+ 17 - 0
fs-service/src/main/java/com/fs/his/param/GetFsIntegralCartDetailsParm.java

@@ -0,0 +1,17 @@
+package com.fs.his.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Data
+public class GetFsIntegralCartDetailsParm {
+    @ApiModelProperty("收货地址ID")
+    private Long addressId;
+
+    @NotNull(message = "购物车ID不能为空")
+    @ApiModelProperty("购物车ID")
+    private List<Long> cartId;
+}

+ 14 - 0
fs-service/src/main/java/com/fs/his/param/GetFsIntegralCartListParam.java

@@ -0,0 +1,14 @@
+package com.fs.his.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class GetFsIntegralCartListParam {
+    @ApiModelProperty("商品名称")
+    private String goodsName;
+    @ApiModelProperty(value = "当前页,默认为1")
+    private Integer pageNum = 1;
+    @ApiModelProperty(value = "页容量,默认为10")
+    private Integer pageSize = 10;
+}

+ 12 - 0
fs-service/src/main/java/com/fs/his/service/IFsIntegralCartService.java

@@ -2,7 +2,12 @@ package com.fs.his.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.his.domain.FsIntegralCart;
+import com.fs.his.param.AddGoodsIntoCartParam;
+import com.fs.his.param.GetFsIntegralCartDetailsParm;
+import com.fs.his.param.GetFsIntegralCartListParam;
 import com.fs.his.vo.FsIntegralCartVO;
+import com.fs.his.vo.GetFsIntegralCartDetailsVo;
+import com.fs.his.vo.GetFsIntegralCartListVo;
 
 import javax.validation.constraints.NotNull;
 import java.util.List;
@@ -48,4 +53,11 @@ public interface IFsIntegralCartService extends IService<FsIntegralCart> {
      * @return  list
      */
     List<FsIntegralCartVO> getCartByIds(Long userId, List<Long> ids);
+
+    List<GetFsIntegralCartListVo> getFsIntegralCartList(GetFsIntegralCartListParam param, Long aLong);
+
+    GetFsIntegralCartDetailsVo getFsIntegralCartDetails(GetFsIntegralCartDetailsParm param, Long userId);
+
+    Boolean addGoodsIntoCart(AddGoodsIntoCartParam param, Long userId);
+
 }

+ 106 - 4
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralCartServiceImpl.java

@@ -5,25 +5,35 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
+import com.fs.common.exception.ServiceException;
 import com.fs.his.domain.FsIntegralCart;
 import com.fs.his.domain.FsIntegralGoods;
 import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserAddress;
 import com.fs.his.mapper.FsIntegralCartMapper;
 import com.fs.his.mapper.FsIntegralGoodsMapper;
+import com.fs.his.mapper.FsUserAddressMapper;
 import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.param.AddGoodsIntoCartParam;
+import com.fs.his.param.GetFsIntegralCartDetailsParm;
+import com.fs.his.param.GetFsIntegralCartListParam;
 import com.fs.his.service.IFsIntegralCartService;
 import com.fs.his.vo.FsIntegralCartVO;
+import com.fs.his.vo.GetCartGoodsDetailsVo;
+import com.fs.his.vo.GetFsIntegralCartDetailsVo;
+import com.fs.his.vo.GetFsIntegralCartListVo;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import javax.validation.constraints.NotNull;
 import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 
 @Slf4j
 @Service
@@ -34,6 +44,12 @@ public class FsIntegralCartServiceImpl extends ServiceImpl<FsIntegralCartMapper,
     @Resource
     private FsUserMapper userMapper;
 
+    @Autowired
+    private FsUserAddressMapper fsUserAddressMapper;
+
+    @Autowired
+    private FsIntegralGoodsMapper fsIntegralGoodsMapper;
+
     /**
      * 添加或修改购物车
      *
@@ -128,4 +144,90 @@ public class FsIntegralCartServiceImpl extends ServiceImpl<FsIntegralCartMapper,
         params.put("ids", ids);
         return baseMapper.getCartsByMap(params);
     }
+
+    @Override
+    public List<GetFsIntegralCartListVo> getFsIntegralCartList(GetFsIntegralCartListParam param, Long aLong) {
+        return baseMapper.getFsIntegralCartList(param, aLong, null);
+    }
+
+    /**
+     * 获取用户购物车详情页
+     *
+     * @param param
+     * @param userId
+     * @return
+     */
+    @Override
+    public GetFsIntegralCartDetailsVo getFsIntegralCartDetails(GetFsIntegralCartDetailsParm param, Long userId) {
+        List<GetFsIntegralCartListVo> cartListVoList = baseMapper.getFsIntegralCartList(new GetFsIntegralCartListParam(), userId, param.getCartId());
+        GetFsIntegralCartDetailsVo vos = new GetFsIntegralCartDetailsVo();
+        ArrayList<GetCartGoodsDetailsVo> goodsDetailsVos = new ArrayList<>();
+        for (GetFsIntegralCartListVo listVo : cartListVoList) {
+            GetCartGoodsDetailsVo cartListVo = new GetCartGoodsDetailsVo();
+            BeanUtils.copyProperties(listVo, cartListVo);
+            cartListVo.setGoodsIntegralTotal(listVo.getGoodsIntegral() * listVo.getQuantity());
+            goodsDetailsVos.add(cartListVo);
+        }
+        if (ObjectUtils.isNotEmpty(param.getAddressId())) {
+            FsUserAddress address = fsUserAddressMapper.selectFsUserAddressByAddressId(param.getAddressId());
+            vos.setUserName(address.getRealName());
+            vos.setUserAddress(address.getProvince() + address.getCity() + address.getDistrict() + address.getDetail());
+            vos.setUserPhone(address.getPhone());
+            vos.setAddressId(address.getAddressId());
+        }
+        vos.setUserId(cartListVoList.get(0).getUserId());
+        vos.setUserIntegral(cartListVoList.get(0).getUserIntegral());
+        vos.setGoodsIntegralTotal(goodsDetailsVos.stream().filter(n -> ObjectUtils.isNotEmpty(n.getGoodsIntegralTotal())).mapToLong(GetCartGoodsDetailsVo::getGoodsIntegralTotal).sum());
+        vos.setGoodsDetailsVoList(goodsDetailsVos);
+        return vos;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean addGoodsIntoCart(AddGoodsIntoCartParam param, Long userId) {
+        // 查询现有购物车项
+        FsIntegralCart existingCart = baseMapper.selectOne(Wrappers.<FsIntegralCart>lambdaQuery().eq(FsIntegralCart::getUserId, userId).eq(FsIntegralCart::getGoodsId, param.getGoodsId()));
+        int finalQuantity;
+        // 查询商品最新状态
+        FsIntegralGoods freshGoods = fsIntegralGoodsMapper.selectFsIntegralGoodsByGoodsId(param.getGoodsId());
+        if (freshGoods == null || freshGoods.getStatus() != 1) {
+            try {
+                int deleteCount = baseMapper.delete(Wrappers.<FsIntegralCart>lambdaQuery().eq(FsIntegralCart::getUserId, userId).eq(FsIntegralCart::getGoodsId, param.getGoodsId()));
+                log.info("清除下架商品,userId:{}, goodsId:{},清除数量:{}", userId, param.getGoodsId(), deleteCount);
+            } catch (Exception e) {
+                log.error("清除下架商品失败,userId:{}, goodsId:{}", userId, param.getGoodsId(), e);
+            }
+            throw new ServiceException(String.format("商品[名称:%d]不存在或已下架", freshGoods.getGoodsName()));
+        }
+        int maxAllowed = Math.min((int) freshGoods.getStock().longValue(), 99);
+        if (existingCart != null) {
+            // 先做校验
+            finalQuantity = existingCart.getCartNum() + param.getQuantity();
+            if (finalQuantity > maxAllowed) {
+                if (existingCart.getCartNum() >= maxAllowed) {
+                    throw new ServiceException(String.format("商品已达最大购买数量%d件", maxAllowed));
+                }
+                int canAdd = maxAllowed - existingCart.getCartNum();
+                throw new ServiceException(String.format("最多还能购买%d件", canAdd));
+            }
+            int affectedRows = baseMapper.updateQuantityAtomically(userId, param.getGoodsId(), param.getQuantity(), maxAllowed);
+            if (affectedRows == 0) {
+                throw new ServiceException("添加失败,请重试");
+            }
+            return true;
+        } else {
+            // 新增购物车
+            if (param.getQuantity() > maxAllowed && param.getQuantity() > freshGoods.getStock()) {
+                throw new ServiceException(String.format("最多可购买%d件", freshGoods.getStock()));
+            }
+            try {
+                FsIntegralCart newCart = FsIntegralCart.builder().userId(userId).goodsId(param.getGoodsId()).cartNum(param.getQuantity()).isSelected(param.getIsSelected()).build();
+                return this.save(newCart);
+            } catch (DuplicateKeyException e) {
+                log.info("购物车并发插入,转为更新,userId:{}, goodsId:{}", userId, param.getGoodsId());
+                int affectedRows = baseMapper.updateQuantityAtomically(userId, param.getGoodsId(), param.getQuantity(), maxAllowed);
+                return affectedRows > 0;
+            }
+        }
+    }
 }

+ 43 - 0
fs-service/src/main/java/com/fs/his/vo/GetCartGoodsDetailsVo.java

@@ -0,0 +1,43 @@
+package com.fs.his.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class GetCartGoodsDetailsVo {
+    @ApiModelProperty("购物车ID")
+    private Long cartId;
+
+    @ApiModelProperty("商品id")
+    private Long goodsId;
+
+    @ApiModelProperty("封面图")
+    private String imgUrl;
+
+    @ApiModelProperty("组图")
+    private String images;
+
+    @ApiModelProperty("商品名称")
+    private String goodsName;
+
+    @ApiModelProperty("状态")
+    private Long status;
+
+    @ApiModelProperty("商品所需积分")
+    private Long goodsIntegral;
+
+    @ApiModelProperty("详情")
+    private String descs;
+
+    @ApiModelProperty("商品数量")
+    private Integer quantity;
+
+    @ApiModelProperty("是否选中:1是,0否")
+    private Integer isSelected;
+
+    @ApiModelProperty("创建时间(加入购物车时间)")
+    private String createTime;
+
+    @ApiModelProperty("商品所需积分之和")
+    private Long goodsIntegralTotal;
+}

+ 37 - 0
fs-service/src/main/java/com/fs/his/vo/GetFsIntegralCartDetailsVo.java

@@ -0,0 +1,37 @@
+package com.fs.his.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Data
+public class GetFsIntegralCartDetailsVo {
+
+    @ApiModelProperty("地址id")
+    private Long addressId;
+
+    @ApiModelProperty("用户id")
+    private Long userId;
+
+    @ApiModelProperty("用户名称")
+    private String userName;
+
+    @ApiModelProperty("用户电话")
+    private String userPhone;
+
+    @ApiModelProperty("用户地址")
+    private String userAddress;
+
+    @ApiModelProperty("所有商品所需积分之和")
+    private Long goodsIntegralTotal;
+
+    @ApiModelProperty("用户的芳华币总数")
+    private BigDecimal userIntegral;
+
+    @ApiModelProperty("商品信息")
+    private List<GetCartGoodsDetailsVo> goodsDetailsVoList;
+
+
+}

+ 63 - 0
fs-service/src/main/java/com/fs/his/vo/GetFsIntegralCartListVo.java

@@ -0,0 +1,63 @@
+package com.fs.his.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class GetFsIntegralCartListVo {
+    @ApiModelProperty("主键ID")
+    private Long cartId;
+
+    @ApiModelProperty("用户ID")
+    private Long userId;
+
+    @ApiModelProperty("积分商品ID")
+    private Long goodsId;
+
+    @ApiModelProperty("封面图")
+    private String imgUrl;
+
+    @ApiModelProperty("组图")
+    private String images;
+
+    @ApiModelProperty("商品名称")
+    private String goodsName;
+
+    @ApiModelProperty("原价")
+    private BigDecimal otPrice;
+
+    @ApiModelProperty("商品分类")
+    private Long goodsType;
+
+    @ApiModelProperty("状态")
+    private Long status;
+
+    @ApiModelProperty("商品所需积分")
+    private Long goodsIntegral;
+
+    @ApiModelProperty("库存")
+    private Long stock;
+
+    @ApiModelProperty("详情")
+    private String descs;
+
+    @ApiModelProperty("产品编码")
+    private String barCode;
+
+    @ApiModelProperty("商品数量")
+    private Integer quantity;
+
+    @ApiModelProperty("用户的芳华币总数")
+    private BigDecimal userIntegral;
+
+    @ApiModelProperty("是否选中:1是,0否")
+    private Integer isSelected;
+
+    @ApiModelProperty("创建时间(加入购物车时间)")
+    private String createTime;
+
+    @ApiModelProperty("更新时间")
+    private String updateTime;
+}

+ 27 - 0
fs-service/src/main/resources/mapper/his/FsIntegralCartMapper.xml

@@ -81,6 +81,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
     </select>
 
+    <select id="getFsIntegralCartList" resultType="com.fs.his.vo.GetFsIntegralCartListVo">
+        select fc.*,fg.*,fg.integral as goodsIntegral,fu.integral as userIntegral
+        from fs_integral_cart fc
+        left join fs_integral_goods fg on fc.goods_id = fg.goods_id
+        left join fs_user fu on fu.user_id = fc.user_id
+        <where>
+            <if test="aLong != null">
+                fc.user_id = #{aLong}
+            </if>
+            <if test="param.goodsName != null and param.goodsName != ''">
+                and fg.goods_name like concat('%', #{param.goodsName}, '%')
+            </if>
+            <if test="cartId != null and cartId.size() > 0">
+                and fc.cart_id in
+                <foreach collection="cartId" item="cartId" open="(" close=")" separator=",">#{cartId}</foreach>
+            </if>
+        </where>
+    </select>
+
+
+    <update id="updateQuantityAtomically">
+        UPDATE fs_integral_cart
+        SET quantity = LEAST(quantity + #{addQuantity}, #{maxQuantity}),
+            is_selected = 1,
+            update_time = NOW()
+        WHERE user_id = #{userId} AND goods_id = #{goodsId} AND quantity <![CDATA[<]]> #{maxQuantity}
+    </update>
     <delete id="deleteCartByGoodsId">
         delete from fs_integral_cart where goods_id = #{goodsId}
     </delete>

+ 8 - 8
fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java

@@ -759,14 +759,14 @@ public class AppLoginController extends AppBaseController{
     @PostMapping("/sendCode")
     public R sendCode(@RequestBody Map<String, String> body){
         String phone = body.get("phone");
-        String encryptPhone = encryptPhone(phone);
-        List<FsUser> user = userService.selectFsUserListByPhone(encryptPhone);
-        if(CollectionUtil.isEmpty(user)){
-            user = userService.selectFsUserListByPhone(encryptPhoneOldKey(phone));
-        }
-        if (CollectionUtil.isEmpty(user)){
-            return R.error("此电话号码未绑定用户");
-        }
+//        String encryptPhone = encryptPhone(phone);
+//        List<FsUser> user = userService.selectFsUserListByPhone(encryptPhone);
+//        if(CollectionUtil.isEmpty(user)){
+//            user = userService.selectFsUserListByPhone(encryptPhoneOldKey(phone));
+//        }
+//        if (CollectionUtil.isEmpty(user)){
+//            return R.error("此电话号码未绑定用户");
+//        }
 
         // 验证码 key(3分钟有效)
         String smsCodeKey = "sms:code:" + phone;

+ 87 - 0
fs-user-app/src/main/java/com/fs/app/controller/FsIntegralCartController.java

@@ -0,0 +1,87 @@
+package com.fs.app.controller;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.fs.app.annotation.Login;
+import com.fs.common.core.domain.R;
+import com.fs.his.domain.FsIntegralCart;
+import com.fs.his.param.AddGoodsIntoCartParam;
+import com.fs.his.param.GetFsIntegralCartDetailsParm;
+import com.fs.his.param.GetFsIntegralCartListParam;
+import com.fs.his.service.IFsIntegralCartService;
+import com.fs.his.vo.GetFsIntegralCartDetailsVo;
+import com.fs.his.vo.GetFsIntegralCartListVo;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 积分购物车表 前端控制器
+ * </p>
+ *
+ * @author chenshiyi
+ * @since 2026-01-13
+ */
+@RestController
+@RequestMapping("/app/integralCart")
+public class FsIntegralCartController extends AppBaseController {
+
+    @Autowired
+    private IFsIntegralCartService fsIntegralCartService;
+
+
+    @Login
+    @ApiOperation("获取用户购物车列表")
+    @PostMapping("/getFsIntegralCartList")
+    public R getFsIntegralCartList(@RequestBody GetFsIntegralCartListParam param) {
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        Long aLong = Long.valueOf(getUserId());
+        List<GetFsIntegralCartListVo> list = fsIntegralCartService.getFsIntegralCartList(param, aLong);
+        PageInfo<GetFsIntegralCartListVo> listPageInfo = new PageInfo<>(list);
+        return R.ok().put("data", listPageInfo);
+    }
+
+    @Login
+    @ApiOperation("获取用户购物车详情页")
+    @PostMapping("/getFsIntegralCartDetails")
+    public R getFsIntegralCartDetails(@RequestBody GetFsIntegralCartDetailsParm param) {
+        Long userId = Long.valueOf(getUserId());
+        GetFsIntegralCartDetailsVo list = fsIntegralCartService.getFsIntegralCartDetails(param, userId);
+        return R.ok().put("data", list);
+    }
+
+    @Login
+    @ApiOperation("添加商品到购物车")
+    @PostMapping("/addGoodsIntoCart")
+    public R addGoodsIntoCart(@RequestBody AddGoodsIntoCartParam param) {
+        Long aLong = Long.valueOf(getUserId());
+        Boolean b = fsIntegralCartService.addGoodsIntoCart(param, aLong);
+        return b ? R.ok("添加商品成功") : R.error("添加商品失败");
+    }
+
+
+    @Login
+    @ApiOperation("修改购物车商品数量")
+    @PutMapping("/putGoodsQuantityFromCart")
+    public R putGoodsQuantityFromCart(@RequestParam Integer quantity, @RequestParam Long cartId) {
+        Long aLong = Long.valueOf(getUserId());
+        FsIntegralCart cart = FsIntegralCart.builder().id(cartId).cartNum(quantity).build();
+        Boolean b = fsIntegralCartService.update(cart, Wrappers.<FsIntegralCart>lambdaQuery().eq(FsIntegralCart::getUserId, aLong).eq(FsIntegralCart::getId, cartId));
+        return b ? R.ok("修改商品成功") : R.error("修改商品失败");
+    }
+
+
+    @Login
+    @ApiOperation("删除购物车商品")
+    @DeleteMapping("/deleteGoodsFromCart/{cartIds}")
+    public R deleteGoodsFromCart(@PathVariable List<Long> cartIds) {
+        Long aLong = Long.valueOf(getUserId());
+        Boolean b = fsIntegralCartService.remove(Wrappers.<FsIntegralCart>lambdaQuery().eq(FsIntegralCart::getUserId, aLong).in(FsIntegralCart::getId, cartIds));
+        return b ? R.ok("删除商品成功") : R.error("删除商品失败");
+    }
+
+}