Parcourir la source

cid-企微加个微成功回调

三七 il y a 4 semaines
Parent
commit
d08b8c7839

+ 16 - 0
fs-common/src/main/java/com/fs/common/constant/Constants.java

@@ -220,4 +220,20 @@ public class Constants
      * Value: 超时时间戳
      */
     public static final String WORKFLOW_QW_ADD_WX_TIMEOUT = "workflow:qwaddwx:timeout:";
+
+
+    /**
+     * CID 企微加个微 searchContact user_id 待匹配 Key 前缀(需拼接 tenantId 与 userId,避免跨租户冲突)
+     * 完整业务 Key:cid:qwAddWx:searchUserId:{tenantId}:{userId}
+     */
+    public static final String CID_QW_ADD_WX_SEARCH_USER_ID = "cid:qwAddWx:searchUserId:";
+    /**
+     * 构建 CID 企微加微 searchUserId 的 Redis 业务 Key(不含 Redis 序列化层的 tenantid 前缀)
+     */
+    public static String cidQwAddWxSearchUserIdKey(Long userId) {
+        if ( userId == null) {
+            return null;
+        }
+        return CID_QW_ADD_WX_SEARCH_USER_ID  + ":" + userId;
+    }
 }

+ 53 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyWxServiceImpl.java

@@ -12,6 +12,7 @@ import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.service.*;
 import com.fs.company.service.impl.call.node.AiAddWxTaskNewNode;
+import com.fs.company.service.impl.call.node.AiQwAddWxTaskNode;
 import com.fs.enums.ExecutionStatusEnum;
 import com.fs.enums.NodeTypeEnum;
 import com.fs.wxcid.domain.CidIpadServer;
@@ -649,4 +650,56 @@ public class CompanyWxServiceImpl extends ServiceImpl<CompanyWxAccountMapper, Co
             log.error("加微成功回调触发工作流异常 - wxClientId: {}", wxClientId, ex);
         }
     }
+
+    /**
+     * 企微加个微成功回调,触发 CID 工作流继续执行
+     */
+    public void triggerQwAddWxWorkflowOnSuccess(Long wxClientId) {
+        try {
+            CompanyAiWorkflowExec waitingExec = companyAiWorkflowExecMapper.selectWaitingAddWxWorkflowByWxClientId(
+                    wxClientId,
+                    ExecutionStatusEnum.WAITING.getValue(),
+                    NodeTypeEnum.AI_QW_ADD_WX_TASK.getValue());
+            if (waitingExec == null) {
+                log.info("未找到等待中的企微加微工作流实例 - wxClientId: {}", wxClientId);
+                return;
+            }
+
+            CompanyAiWorkflowExecLog queryP = new CompanyAiWorkflowExecLog();
+            queryP.setWorkflowInstanceId(waitingExec.getWorkflowInstanceId());
+            queryP.setNodeType(NodeTypeEnum.AI_QW_ADD_WX_TASK.getValue());
+            queryP.setStatus(ExecutionStatusEnum.WAITING.getValue());
+            List<CompanyAiWorkflowExecLog> execLogs = companyAiWorkflowExecLogMapper.selectCompanyAiWorkflowExecLogList(queryP);
+            execLogs.forEach(execLog -> {
+                execLog.setStatus(ExecutionStatusEnum.SUCCESS.getValue());
+                companyAiWorkflowExecLogMapper.updateById(execLog);
+            });
+
+            String workflowInstanceId = waitingExec.getWorkflowInstanceId();
+            String currentNodeKey = waitingExec.getCurrentNodeKey();
+
+            log.info("企微加微成功回调,尝试触发工作流继续执行 - workflowInstanceId: {}, nodeKey: {}, wxClientId: {}",
+                    workflowInstanceId, currentNodeKey, wxClientId);
+
+            if (!AiQwAddWxTaskNode.tryMarkAsExecuted(workflowInstanceId, wxClientId)) {
+                log.info("企微加微工作流已被其他路径执行,跳过 - workflowInstanceId: {}, wxClientId: {}",
+                        workflowInstanceId, wxClientId);
+                return;
+            }
+
+            AiQwAddWxTaskNode.clearTimeoutKey(workflowInstanceId, wxClientId);
+
+            Map<String, Object> inputData = new HashMap<>();
+            inputData.put("addWxSuccess", true);
+            inputData.put("wxClientId", wxClientId);
+            inputData.put("triggerType", "callback");
+
+            companyWorkflowEngine.resumeFromBlockingNode(workflowInstanceId, currentNodeKey, inputData);
+
+            log.info("企微加微成功回调触发工作流继续执行完成 - workflowInstanceId: {}, wxClientId: {}",
+                    workflowInstanceId, wxClientId);
+        } catch (Exception ex) {
+            log.error("企微加微成功回调触发工作流异常 - wxClientId: {}", wxClientId, ex);
+        }
+    }
 }

+ 29 - 0
fs-service/src/main/java/com/fs/company/vo/CidQwAddWxPendingVO.java

@@ -0,0 +1,29 @@
+package com.fs.company.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * CID 企微主动加个微:searchContact 返回的 user_id 与待回调工作流上下文
+ */
+@Data
+public class CidQwAddWxPendingVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** company_wx_client.id */
+    private Long wxClientId;
+
+    /** 任务 ID */
+    private Long roboticId;
+
+    /** qw_user.id */
+    private Long accountId;
+
+    /** 客户手机号 */
+    private String mobile;
+
+    /** searchContact 返回的 user_id */
+    private Long searchUserId;
+}

+ 2 - 0
fs-service/src/main/java/com/fs/qw/domain/QwExternalContact.java

@@ -161,5 +161,7 @@ public class QwExternalContact extends BaseEntity
     //获客链接主键id
     private Long qwAcquisitionAssistantId;
 
+    /** iPad 侧 user_id(externalUserId 经 UserId2Vid 转换) */
+    private Long ipadUserId;
 
 }

+ 138 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -12,14 +12,21 @@ import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.ad.enums.AdUploadType;
 import com.fs.ad.service.IAdHtmlClickLogService;
+import com.fs.common.constant.Constants;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.CompanyVoiceRoboticCallLogAddwx;
 import com.fs.company.domain.CompanyWxAccount;
+import com.fs.company.domain.CompanyWxClient;
 import com.fs.company.mapper.CompanyWxAccountMapper;
+import com.fs.company.mapper.CompanyWxClientMapper;
 import com.fs.company.service.ICompanyConfigService;
+import com.fs.company.service.ICompanyVoiceRoboticCallLogAddwxService;
+import com.fs.company.service.impl.CompanyWxServiceImpl;
+import com.fs.company.vo.CidQwAddWxPendingVO;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.domain.FsCourseSop;
 import com.fs.course.domain.FsCourseSopLogs;
@@ -37,6 +44,8 @@ import com.fs.his.domain.FsUser;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.utils.PhoneUtil;
 import com.fs.hisStore.vo.FsStoreOrderScrmSidebarVO;
+import com.fs.ipad.IpadSendUtils;
+import com.fs.ipad.vo.BaseVo;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.*;
@@ -72,6 +81,10 @@ import com.fs.sop.service.ISopUserLogsService;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.ISysDictTypeService;
 import com.fs.voice.utils.StringUtil;
+import com.fs.wxwork.dto.WxLoginDTO;
+import com.fs.wxwork.dto.WxLoginResp;
+import com.fs.wxwork.dto.WxWorkResponseDTO;
+import com.fs.wxwork.service.WxWorkServiceNew;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import lombok.extern.slf4j.Slf4j;
@@ -225,9 +238,22 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
     @Autowired
     QwAcquisitionAssistantMapper acquisitionAssistantMapper;
 
+
     @Autowired
     private AsyncQwContactWayService asyncQwContactWayService;
 
+    @Autowired
+    private WxWorkServiceNew wxWorkServiceNew;
+
+    @Autowired
+    private IpadSendUtils ipadSendUtils;
+    @Autowired
+    private CompanyWxClientMapper companyWxClientMapper;
+    @Autowired
+    private ICompanyVoiceRoboticCallLogAddwxService companyVoiceRoboticCallLogAddwxService;
+    @Autowired
+    private CompanyWxServiceImpl companyWxServiceImpl;
+
     Logger logger = LoggerFactory.getLogger(getClass());
     @Autowired
     private CompanyWxAccountMapper companyWxAccountMapper;
@@ -3050,6 +3076,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         //上面存过了,这里就更新
         qwExternalContact.setId(contact.getId());
         qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
+        //cid 回调处理
+        handleCidQwAddWxAfterContactSaved(qwUser, contact.getId(), externalUserID);
+
         QwOpenidByExternalcontactParams externalcontactParams = new QwOpenidByExternalcontactParams();
         externalcontactParams.setExternal_userid(externalUserID);
         //录入单独的CRM客户信息  没啥用
@@ -3086,6 +3115,115 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         }
     }
 
+
+    /**
+     * 外部联系人入库后:解析 ipadUserId,匹配 CID 加微 Redis 并触发工作流
+     */
+    private void handleCidQwAddWxAfterContactSaved(QwUser qwUser, Long externalContactId, String externalUserId) {
+        if (qwUser == null || externalContactId == null || StringUtils.isBlank(externalUserId)) {
+            return;
+        }
+        try {
+
+            Long ipadUserId = resolveIpadUserIdByExternalUserId(qwUser, externalUserId);
+            if (ipadUserId == null) {
+                return;
+            }
+
+            QwExternalContact ipadUpdate = new QwExternalContact();
+            ipadUpdate.setId(externalContactId);
+            ipadUpdate.setIpadUserId(ipadUserId);
+            qwExternalContactMapper.updateQwExternalContact(ipadUpdate);
+
+            String redisKey = Constants.cidQwAddWxSearchUserIdKey(ipadUserId);
+            CidQwAddWxPendingVO pending = redisCache.getCacheObject(redisKey);
+            if (pending == null || pending.getWxClientId() == null) {
+                log.debug("CID加微未命中 Redis, ipadUserId={}, externalUserId={}",
+                        ipadUserId, externalUserId);
+                return;
+            }
+
+            if (pending.getAccountId() != null && !pending.getAccountId().equals(qwUser.getId())) {
+                log.info("CID加微 Redis 账号不匹配, pendingAccountId={}, qwUserId={}, ipadUserId={}",
+                        pending.getAccountId(), qwUser.getId(), ipadUserId);
+                return;
+            }
+
+            CompanyWxClient wxClient = companyWxClientMapper.selectById(pending.getWxClientId());
+            if (wxClient == null) {
+                log.info("CID加微待处理客户不存在, wxClientId={}", pending.getWxClientId());
+                redisCache.deleteObject(redisKey);
+                return;
+            }
+            if (Integer.valueOf(1).equals(wxClient.getIsAdd())) {
+                redisCache.deleteObject(redisKey);
+                return;
+            }
+
+            wxClient.setIsAdd(1);
+            wxClient.setSuccessAddTime(LocalDateTime.now());
+            companyWxClientMapper.updateById(wxClient);
+
+            companyVoiceRoboticCallLogAddwxService.lambdaUpdate()
+                    .eq(CompanyVoiceRoboticCallLogAddwx::getRoboticId, wxClient.getRoboticId())
+                    .eq(CompanyVoiceRoboticCallLogAddwx::getWxClientId, wxClient.getId())
+                    .eq(CompanyVoiceRoboticCallLogAddwx::getWxAccountId, wxClient.getAccountId())
+                    .eq(CompanyVoiceRoboticCallLogAddwx::getIsWeCom, 2)
+                    .set(CompanyVoiceRoboticCallLogAddwx::getStatus, 2)
+                    .update();
+
+            redisCache.deleteObject(redisKey);
+            redisCache.deleteObject(Constants.QW_ADD_WX + "_" + wxClient.getId());
+
+            companyWxServiceImpl.triggerQwAddWxWorkflowOnSuccess(wxClient.getId());
+            log.info("CID企微加微回调匹配成功, wxClientId={}, ipadUserId={}, externalUserId={}",
+                    wxClient.getId(), ipadUserId, externalUserId);
+        } catch (Exception e) {
+            log.info("CID企微加微回调处理异常, externalContactId={}, externalUserId={}", externalContactId, externalUserId, e);
+        }
+    }
+
+    /**
+     * 外部联系人 externalUserId 转 iPad 侧 user_id(复用 IpadSendUtils.userIds,含 appId 解析)
+     */
+    private Long resolveIpadUserIdByExternalUserId(QwUser qwUser, String externalUserId) {
+        if (qwUser.getServerId() == null || StringUtils.isBlank(qwUser.getUid()) || StringUtils.isBlank(qwUser.getCorpId())) {
+            return null;
+        }
+        try {
+            WxLoginDTO loginDto = new WxLoginDTO();
+            loginDto.setUuid(qwUser.getUid());
+            WxWorkResponseDTO<WxLoginResp> loginResult = wxWorkServiceNew.isLogin(loginDto, qwUser.getServerId());
+            if (loginResult == null || loginResult.getErrcode() != 0 || loginResult.getData() == null
+                    || loginResult.getData().getUser_info() == null
+                    || loginResult.getData().getUser_info().getObject() == null) {
+                log.warn("CID加微解析 ipadUserId 失败:企微 iPad 未登录, qwUserId={}", qwUser.getId());
+                return null;
+            }
+            String corpCode = loginResult.getData().getUser_info().getObject().getScorp_id();
+            QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(qwUser.getCorpId());
+            if (qwCompany == null || StringUtils.isBlank(qwCompany.getServerAgentId())) {
+                log.warn("CID加微解析 ipadUserId 失败:未配置 serverAgentId, corpId={}, qwUserId={}",
+                        qwUser.getCorpId(), qwUser.getId());
+                return null;
+            }
+
+            BaseVo baseVo = new BaseVo();
+            baseVo.setUuid(qwUser.getUid());
+            baseVo.setServerId(qwUser.getServerId());
+            baseVo.setCorpId(qwUser.getCorpId());
+            baseVo.setCorpCode(corpCode);
+            baseVo.setExId(externalUserId);
+            baseVo.setQwUserId(qwUser.getId());
+            return ipadSendUtils.userIds(baseVo);
+
+        } catch (Exception e) {
+            log.warn("CID加微 externalUserId 转 ipadUserId 失败, externalUserId={}, qwUserId={}, err={}",
+                    externalUserId, qwUser.getId(), e.getMessage());
+            return null;
+        }
+    }
+
     private long getSecondsUntilMidnight() {
         LocalDateTime now = LocalDateTime.now();
         LocalDateTime midnight = now.toLocalDate().plusDays(1).atStartOfDay();

+ 8 - 0
fs-service/src/main/resources/db/changelog/changes/20260613-live-user-add-is-del.sql

@@ -108,3 +108,11 @@ ALTER TABLE fs_course_coupon_user
 ALTER TABLE fs_course_coupon_user
     ADD COLUMN verify_time DATETIME DEFAULT NULL COMMENT '核销时间';
 --rollback ALTER TABLE fs_course_coupon_user DROP COLUMN verify_time;
+
+--changeset sgw:20260626-qw_external_contact-ipad_user_id
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'qw_external_contact'
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'qw_external_contact' AND column_name = 'ipad_user_id'
+ALTER TABLE qw_external_contact
+    ADD COLUMN ipad_user_id BIGINT DEFAULT NULL COMMENT 'iPad 侧 user_id';
+--rollback ALTER TABLE qw_external_contact DROP COLUMN ipad_user_id;

+ 1 - 0
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -372,6 +372,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="fsUserPhone != null">fs_user_phone = #{fsUserPhone},</if>
             <if test="addSourceType != null">add_source_type = #{addSourceType},</if>
             <if test="qwAcquisitionAssistantId != null">qw_acquisition_assistant_id = #{qwAcquisitionAssistantId},</if>
+            <if test="ipadUserId != null">ipad_user_id = #{ipadUserId},</if>
         </trim>
         where id = #{id}
     </update>

+ 46 - 8
fs-wx-task/src/main/java/com/fs/app/service/WxTaskService.java

@@ -21,6 +21,7 @@ import com.fs.company.service.impl.call.node.AiAddWxTaskNode;
 import com.fs.company.service.impl.call.node.AiQwAddWxTaskNode;
 import com.fs.company.service.impl.call.node.WorkflowNodeFactory;
 import com.fs.company.util.ObjectPlaceholderResolver;
+import com.fs.company.vo.CidQwAddWxPendingVO;
 import com.fs.company.vo.CompanyWxClient4WorkFlowVO;
 import com.fs.company.vo.SendMsgVo;
 import com.fs.course.config.CourseConfig;
@@ -120,6 +121,7 @@ public class WxTaskService {
     private final CompanyVoiceRoboticCallLogSendmsgServiceImpl companyVoiceRoboticCallLogSendmsgService;
     private final QwApiService qwApiService;
     private final RedisCache redisCache2;
+    private final RedisCacheT<CidQwAddWxPendingVO> redisCacheQwVo;
     private final ExecutorService cidExcutor = new ThreadPoolExecutor(
             32,
             64,
@@ -1121,7 +1123,10 @@ public class WxTaskService {
                 qwUser.getUid(),
                 qwUser.getServerId(),
                 qwUser.getVid(),
-                qwUser.getQwUserName()
+                qwUser.getQwUserName(),
+                client.getId(),
+                client.getRoboticId(),
+                qwUser.getId()
         );
 
         // 准备日志参数
@@ -1972,7 +1977,8 @@ public class WxTaskService {
      * @param serverId   服务器id
      * @return String 结果
      */
-    private WxWorkResponseDTO<String> qwAddWxInvokeIpad(String mobile, String qwUid, Long serverId,String vid,String qwUserName) {
+    private WxWorkResponseDTO<String> qwAddWxInvokeIpad(String mobile, String qwUid, Long serverId, String vid,
+                                                        String qwUserName, Long wxClientId, Long roboticId, Long accountId) {
         if (StringUtils.isBlank(mobile) || StringUtils.isBlank(qwUid) || serverId == null) {
             log.warn("企微申请加好友任务参数校验失败: mobile={}, qwUid={}, serverId={}", mobile, qwUid, serverId);
             return null;
@@ -1990,10 +1996,21 @@ public class WxTaskService {
 
 
             WxWorkResponseDTO<WxSearchContactResp> respWxWorkResponseDTO = wxWorkService.searchContact(contactDTO, serverId);
+            if (respWxWorkResponseDTO == null || respWxWorkResponseDTO.getErrcode() != 0
+                    || respWxWorkResponseDTO.getData() == null
+                    || respWxWorkResponseDTO.getData().getUserList() == null) {
+                log.warn("企微 searchContact 失败: mobile={}, resp={}", mobile, JSON.toJSONString(respWxWorkResponseDTO));
+                return null;
+            }
+
             WxSearchContactResp.UserList user = respWxWorkResponseDTO.getData().getUserList().stream()
-                    .filter(u -> u.getState().equals("2"))
+                    .filter(u -> "2".equals(u.getState()))
                     .findFirst()
-                    .orElse(null); // 或者 .orElseThrow(() -> new RuntimeException("未找到指定用户"))
+                    .orElse(null);
+            if (user == null) {
+                log.warn("企微 searchContact 未找到个微用户: mobile={}", mobile);
+                return null;
+            }
 
             wxAddSearchDTO.setOptionid(user.getOpenid());
             wxAddSearchDTO.setTicket(user.getTicket());
@@ -2004,18 +2021,39 @@ public class WxTaskService {
                     response != null ? response.getErrcode() : "null",
                     response != null ? response.getErrmsg() : "null");
 
+            if (response != null && response.getErrcode() == 0 && user.getUser_id() != null) {
+                cacheQwAddWxSearchUserId(user.getUser_id(), wxClientId, roboticId, accountId, mobile);
+            }
+
             return response;
 
-            // 测试代码
-//            WxWorkResponseDTO<String> response = new WxWorkResponseDTO<>();
-//            response.setErrcode(0);
-//            return response;
         } catch (Exception e) {
             log.error("企微申请加好友任务请求接口异常: mobile={}, qwUid={}, serverId={}", mobile, qwUid, serverId, e);
             return null;
         }
     }
 
+
+    /**
+     * 缓存 searchContact 返回的 user_id,供外部联系人回调匹配
+     */
+    private void cacheQwAddWxSearchUserId(Long searchUserId, Long wxClientId, Long roboticId, Long accountId, String mobile) {
+
+        String redisKey = Constants.cidQwAddWxSearchUserIdKey(searchUserId);
+        if (redisKey == null) {
+            return;
+        }
+        CidQwAddWxPendingVO pending = new CidQwAddWxPendingVO();
+        pending.setSearchUserId(searchUserId);
+        pending.setWxClientId(wxClientId);
+        pending.setRoboticId(roboticId);
+        pending.setAccountId(accountId);
+        pending.setMobile(mobile);
+        redisCacheQwVo.setCacheObject(redisKey, pending, 7, TimeUnit.DAYS);
+        log.info("CID企微加微缓存 searchUserId={}, wxClientId={}, roboticId={}",
+                searchUserId, wxClientId, roboticId);
+    }
+
     /**
      * 处理单个客户加微结果
      */