فهرست منبع

1、有关qw-api的接口请求迁移到qw-api迁移一部分

yys 1 ماه پیش
والد
کامیت
3abe2d97cd

+ 0 - 3
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -65,7 +65,6 @@ import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.*;
-import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.domain.QwSopTempVoice;
 import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.system.domain.SysConfig;
@@ -164,8 +163,6 @@ public class Task {
     FsInquiryOrderMapper fsInquiryOrderMapper;
     @Autowired
     FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
-    @Autowired
-    QwApiService qwApiService;
     //每天执行一次
     @Autowired
     IQwAppContactWayService qwAppContactWayService;

+ 33 - 1
fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java

@@ -2,12 +2,17 @@ package com.fs.qw.controller;
 
 import java.util.List;
 import java.util.Objects;
+import java.net.SocketTimeoutException;
 import java.util.stream.Collectors;
 
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import com.alibaba.fastjson.JSON;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.qw.param.QwExternalContactParam;
 import com.fs.qw.param.QwTagSearchParam;
@@ -21,6 +26,7 @@ import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import org.apache.commons.collections.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -36,8 +42,10 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.enums.BusinessType;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qwApi.config.OpenQwConfig;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.core.page.TableDataInfo;
+import lombok.extern.slf4j.Slf4j;
 
 /**
  * 企业微信客户Controller
@@ -45,6 +53,7 @@ import com.fs.common.core.page.TableDataInfo;
  * @author fs
  * @date 2025-06-13
  */
+@Slf4j
 @RestController
 @RequestMapping("/qw/externalContact")
 public class QwExternalContactController extends BaseController
@@ -55,6 +64,10 @@ public class QwExternalContactController extends BaseController
     @Autowired
     private IQwExternalContactInfoService qwExternalContactInfoService;
 
+    /** HTTP调用超时时间(秒) */
+    @Value("${qw.api.timeout:30}")
+    private int apiTimeout;
+
     QwExternalContactController(IQwExternalContactService qwExternalContactService,IQwTagService iQwTagService){
         this.qwExternalContactService=qwExternalContactService;
         this.iQwTagService=iQwTagService;
@@ -187,7 +200,26 @@ public class QwExternalContactController extends BaseController
     @PostMapping("/addUnassigned")
     public R addUnassigned(@RequestBody QwExternalContact qwExternalContact)
     {
-        return qwExternalContactService.syncQwExternalContactUnassigned(qwExternalContact.getCorpId());
+        // 从当前登录用户获取租户ID
+        Long tenantId = SecurityUtils.getTenantId();
+        String url = OpenQwConfig.api + "/qw/externalContact/addUnassigned?corpId=" + qwExternalContact.getCorpId() + "&tenantId=" + tenantId;
+        try {
+            HttpResponse response = HttpRequest.post(url)
+                    .timeout(apiTimeout * 1000)
+                    .execute();
+            if (response.getStatus() == 200) {
+                return JSON.parseObject(response.body(), R.class);
+            } else {
+                log.error("同步待转接客户失败,HTTP状态码: {}", response.getStatus());
+                return R.error("同步待转接客户失败,服务返回状态码: " + response.getStatus());
+            }
+        } catch (Exception e) {
+            log.error("同步待转接客户异常, url={}", url, e);
+            if (e.getCause() instanceof SocketTimeoutException) {
+                return R.error("同步待转接客户超时,请稍后重试");
+            }
+            return R.error("同步待转接客户失败: " + e.getMessage());
+        }
     }
     @PreAuthorize("@ss.hasPermi('qw:externalContact:transfer')")
     @Log(title = "企业微信客户", businessType = BusinessType.UPDATE)

+ 40 - 91
fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java

@@ -1,6 +1,8 @@
 package com.fs.qw.controller;
 
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
@@ -14,6 +16,7 @@ import com.fs.common.enums.BusinessType;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.exception.user.UserPasswordNotMatchException;
 import com.fs.common.utils.MessageUtils;
+import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyUserMapper;
@@ -23,8 +26,6 @@ import com.fs.fastGpt.domain.FastGptRole;
 import com.fs.fastGpt.mapper.FastGptRoleMapper;
 import com.fs.framework.manager.AsyncManager;
 import com.fs.framework.manager.factory.AsyncFactory;
-import com.fs.qw.domain.QwCompany;
-import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwCompanyMapper;
 import com.fs.qw.mapper.QwExternalContactMapper;
@@ -35,16 +36,13 @@ import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwUserVO;
 import com.fs.qw.vo.UpdateSendTypeVo;
-import com.fs.qwApi.domain.QwExternalContactAllListResult;
-import com.fs.qwApi.domain.inner.ExternalContact;
-import com.fs.qwApi.domain.inner.ExternalContactInfo;
-import com.fs.qwApi.domain.inner.FollowInfo;
-import com.fs.qwApi.param.QwExternalListParam;
-import com.fs.qwApi.service.QwApiService;
+import com.fs.qwApi.config.OpenQwConfig;
 import com.fs.voice.utils.StringUtil;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
@@ -53,6 +51,7 @@ import org.springframework.security.core.Authentication;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.net.SocketTimeoutException;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -62,6 +61,7 @@ import java.util.stream.Collectors;
  * @author fs
  * @date 2024-06-20
  */
+@Slf4j
 @RestController
 @RequestMapping("/qw/user")
 public class QwUserController extends BaseController {
@@ -77,9 +77,6 @@ public class QwUserController extends BaseController {
     @Autowired
     private CompanyUserMapper companyUserMapper;
 
-    @Autowired
-    private QwApiService qwApiService;
-
     @Autowired
     private QwCompanyMapper qwCompanyMapper;
 
@@ -97,6 +94,10 @@ public class QwUserController extends BaseController {
     @Autowired
     private FastGptRoleMapper fastGptRoleMapper;
 
+    /** HTTP调用超时时间(秒) */
+    @Value("${qw.api.timeout:120}")
+    private int apiTimeout;
+
     /**
      * 查询企微员工列表
      */
@@ -874,88 +875,36 @@ public class QwUserController extends BaseController {
     }
 
     public R  syncMyQwExternalContact(QwUser qwUser,String getNextCursor )  {
-
-        String qwUserId = qwUser.getQwUserId();
-        String corpId = qwUser.getCorpId();
-        Long companyId = qwUser.getCompanyId();
-
-        QwExternalListParam param = new QwExternalListParam();
-        param.setLimit(100);
-        param.setUserid_list(Arrays.asList(qwUserId));
-        param.setCursor(getNextCursor);
-        QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(corpId);
-        QwExternalContactAllListResult list = qwApiService.getAllExternalcontactList(param, corpId,qwCompany);
-
-        logger.info("批量获取客户详情" + list);
-
-        if (list.getErrcode() == 0) {
-            List<ExternalContactInfo> externalContactList = list.getExternal_contact_list();
-            for (ExternalContactInfo externalContactInfo : externalContactList) {
-
-                ExternalContact externalContact = externalContactInfo.getExternal_contact();
-                FollowInfo followInfo = externalContactInfo.getFollow_info();
-                QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactUserIdAndExternalIdAndCompanyId(externalContact.getExternal_userid(), qwUserId, corpId);
-                logger.info("客户详情" + qwExternalContact);
-
-                if (qwExternalContact == null) {
-                    qwExternalContact = new QwExternalContact();
-                    qwExternalContact.setUserId(qwUserId); // 设置属于用户ID
-                    qwExternalContact.setExternalUserId(externalContact.getExternal_userid()); // 设置外部联系人ID
-
-
-                    qwExternalContact.setCorpId(corpId); // 设置企业ID
-                    qwExternalContact.setCompanyId(companyId); // 设置公司ID
-                }
-
-
-                // 设置公共属性
-                qwExternalContact.setCompanyUserId(qwUser.getCompanyUserId());
-                qwExternalContact.setQwUserId(qwUser.getId());
-                qwExternalContact.setName(externalContact.getName()); // 设置名称
-                qwExternalContact.setAvatar(externalContact.getAvatar()); // 设置头像
-                qwExternalContact.setType(externalContact.getType()); // 设置外部联系人类型(1微信用户,2企业微信用户)
-                qwExternalContact.setGender(externalContact.getGender()); // 设置性别 (0-未知, 1-男性, 2-女性)
-                qwExternalContact.setDescription(followInfo.getDescription()); // 设置描述信息
-                qwExternalContact.setRemark(followInfo.getRemark());
-
-//                        if (followInfo.getTag_id() != null && !followInfo.getTag_id().isEmpty()) {
-                qwExternalContact.setTagIds(JSON.toJSONString(followInfo.getTag_id())); // 设置标签ID
-//                        }
-
-//                        if (followInfo.getRemark_mobiles() != null && !followInfo.getRemark_mobiles().isEmpty()) {
-                qwExternalContact.setRemarkMobiles(JSON.toJSONString(followInfo.getRemark_mobiles())); // 设置备注电话号码
-//                        }
-
-                qwExternalContact.setState(followInfo.getState());
-
-                if (followInfo.getState() != null && !followInfo.getState().isEmpty()) {
-                    String s = "way:" + corpId + ":";
-                    if (followInfo.getState().contains(s)) {
-                        String wayId = followInfo.getState().substring(followInfo.getState().indexOf(s) + s.length());
-                        qwExternalContact.setWayId(Long.parseLong(wayId));
-                    }
-                }
-
-                qwExternalContact.setRemarkCorpName(followInfo.getRemark_corp_name()); // 设置备注企业名称
-                qwExternalContact.setAddWay(followInfo.getAdd_way()); // 设置来源
-                qwExternalContact.setOperUserid(followInfo.getOper_userid()); // 设置oper用户ID
-
-                // 根据是否存在记录进行更新或插入
-                if (qwExternalContact.getId() != null) {
-                    qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
-                } else {
-                    qwExternalContactMapper.insertQwExternalContact(qwExternalContact);
-                }
-            }
-
-        }else {
-            return R.error("同步失败:"+list.getErrmsg());
+        // 从当前登录用户获取租户ID
+        Long tenantId = SecurityUtils.getTenantId();
+        String encodedQwUser;
+        try {
+            encodedQwUser = java.net.URLEncoder.encode(JSON.toJSONString(qwUser), "UTF-8");
+        } catch (java.io.UnsupportedEncodingException e) {
+            encodedQwUser = JSON.toJSONString(qwUser);
         }
-
-        if (!StringUtil.strIsNullOrEmpty(list.getNext_cursor())){
-            syncMyQwExternalContact(qwUser,list.getNext_cursor());
+        String url = OpenQwConfig.api + "/qw/externalContact/syncExternalContact?tenantId=" + tenantId
+                + "&qwUser=" + encodedQwUser;
+        if (getNextCursor != null && !getNextCursor.isEmpty()) {
+            url += "&cursor=" + getNextCursor;
+        }
+        try {
+            HttpResponse response = HttpRequest.post(url)
+                    .timeout(apiTimeout * 1000)
+                    .execute();
+            if (response.getStatus() == 200) {
+                return JSON.parseObject(response.body(), R.class);
+            } else {
+                log.error("同步外部联系人失败,HTTP状态码: {}", response.getStatus());
+                return R.error("同步外部联系人失败,服务返回状态码: " + response.getStatus());
+            }
+        } catch (Exception e) {
+            log.error("同步外部联系人异常, url={}", url, e);
+            if (e.getCause() instanceof SocketTimeoutException) {
+                return R.error("同步外部联系人超时,请稍后重试");
+            }
+            return R.error("同步外部联系人失败: " + e.getMessage());
         }
-        return R.ok();
     }
 
     //    /**

+ 33 - 44
fs-admin/src/main/java/com/fs/qw/controller/QwWorkTaskNewController.java

@@ -1,31 +1,31 @@
 package com.fs.qw.controller;
 
-import com.fs.common.annotation.Log;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import com.alibaba.fastjson.JSON;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.annotation.Log;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.service.impl.CompanyDeptServiceImpl;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
-//import com.fs.framework.security.LoginUser;
-//import com.fs.framework.service.TokenService;
-import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwWorkTask;
-import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.param.QwWorkTaskListParam;
-import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwWorkTaskService;
 import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
-import com.fs.qwApi.domain.QwExternalContactRemarkResult;
-import com.fs.qwApi.param.QwExternalContactRemarkParam;
-import com.fs.qwApi.service.QwApiService;
+import com.fs.qwApi.config.OpenQwConfig;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
+import java.net.SocketTimeoutException;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -36,14 +36,13 @@ import java.util.List;
  * @author fs
  * @date 2025-03-25
  */
+@Slf4j
 @RestController
 @RequestMapping("/qw/QwWorkTaskNew")
 public class QwWorkTaskNewController extends BaseController
 {
     @Autowired
     private IQwWorkTaskService qwWorkTaskService;
-//    @Autowired
-//    private TokenService tokenService;
     @Autowired
     private FsCourseWatchLogMapper fsCourseWatchLogMapper;
 
@@ -58,9 +57,6 @@ public class QwWorkTaskNewController extends BaseController
     public TableDataInfo list(QwWorkTaskListParam qwWorkTask)
     {
         startPage();
-//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-//        qwWorkTask.setCompanyId(loginUser.getCompany().getCompanyId());
-//        qwWorkTask.setCompanyUserId(loginUser.getUser().getUserId());
         List<QwWorkTaskListVO> list = qwWorkTaskService.selectQwWorkTaskListVO(qwWorkTask);
 
         for (QwWorkTaskListVO qwWorkTaskListVO : list) {
@@ -163,12 +159,10 @@ public class QwWorkTaskNewController extends BaseController
     {
         return toAjax(qwWorkTaskService.insertQwWorkTask(qwWorkTask));
     }
-    @Autowired
-    QwApiService qwApiService;
-    @Autowired
-    IQwExternalContactService qwExternalContactService;
-    @Autowired
-    QwExternalContactMapper qwExternalContactMapper;
+    /** HTTP调用超时时间(秒) */
+    @Value("${qw.api.timeout:30}")
+    private int apiTimeout;
+
     /**
      * 修改企微任务看板
      */
@@ -177,32 +171,27 @@ public class QwWorkTaskNewController extends BaseController
     @PutMapping
     public AjaxResult edit(@RequestBody QwWorkTask qwWorkTask)
     {
-        QwWorkTask task = new QwWorkTask();
-        task.setId(qwWorkTask.getId());
-        task.setStatus(1);
-        task.setTrackType(qwWorkTask.getTrackType());
-        task.setDescription(qwWorkTask.getDescription());
-        task.setUpdateTime(new Date());
-        if (task.getDescription()!=null&& !task.getDescription().isEmpty()){
-
-            QwExternalContact qwExternalContact = qwExternalContactService.selectQwExternalContactById(qwWorkTask.getExtId());
-            if (qwExternalContact!=null){
-                QwExternalContactRemarkParam param = new QwExternalContactRemarkParam();
-                param.setUserid(qwExternalContact.getUserId());
-                param.setExternal_userid(qwExternalContact.getExternalUserId());
-                param.setDescription(task.getDescription());
-
-                QwExternalContactRemarkResult qwExternalContactRemarkResult = qwApiService.externalcontactRemark(param, qwExternalContact.getCorpId());
-                logger.info("QwExternalContactRemarkResult206:" + qwExternalContactRemarkResult);
-                if (qwExternalContactRemarkResult.getErrcode() == 0) {
-                    QwExternalContact ext = new QwExternalContact();
-                    ext.setId(qwExternalContact.getId());
-                    ext.setDescription(task.getDescription());
-                    qwExternalContactMapper.updateQwExternalContact(ext);
-                }
+        // 从当前登录用户获取租户ID
+        Long tenantId = SecurityUtils.getTenantId();
+        String url = OpenQwConfig.api + "/qw/QwWorkTaskNew/edit?tenantId=" + tenantId;
+        try {
+            HttpResponse response = HttpRequest.post(url)
+                    .body(JSON.toJSONString(qwWorkTask))
+                    .timeout(apiTimeout * 1000)
+                    .execute();
+            if (response.getStatus() == 200) {
+                return JSON.parseObject(response.body(), AjaxResult.class);
+            } else {
+                log.error("修改任务看板失败,HTTP状态码: {}", response.getStatus());
+                return AjaxResult.error("修改任务看板失败,服务返回状态码: " + response.getStatus());
             }
+        } catch (Exception e) {
+            log.error("修改任务看板异常, url={}", url, e);
+            if (e.getCause() instanceof SocketTimeoutException) {
+                return AjaxResult.error("修改任务看板超时,请稍后重试");
+            }
+            return AjaxResult.error("修改任务看板失败: " + e.getMessage());
         }
-        return toAjax(qwWorkTaskService.updateQwWorkTask(task));
     }
     @PreAuthorize("@ss.hasPermi('qw:QwWorkTaskNew:edit')")
     @Log(title = "企微任务看板处理", businessType = BusinessType.UPDATE)

+ 2 - 1
fs-common/src/main/java/com/fs/common/constant/FsConstants.java

@@ -9,7 +9,8 @@ public interface FsConstants {
     String REDIS_CHAT_NEXTCURSOR = "chat:nextcursor:";
     String REDIS_QW_appKey_Active = "qwActive:";
 
-    String FRIEND_WELCOME_VIDEO_KEY = "friend:welcome:";
+    String FRIEND_WELCOME_VIDEO_KEY = "friend:welcome:";  // 旧格式,已弃用
+    String FRIEND_WELCOME_VIDEO_KEY_V2 = "friend:welcome:v2:";  // 新格式: friend:welcome:v2:{tenantId}:{id}
     String REDIS_INTEGRAL_ORDER_UNPAY = "integral:order:unpay:";
 
     // 公司余额redis key "company:money:" + company.getCompanyId()

+ 0 - 3
fs-company/src/main/java/com/fs/company/controller/common/CommonController.java

@@ -24,7 +24,6 @@ import com.fs.his.service.IFsExportTaskService;
 import com.fs.im.dto.OpenImResponseDTO;
 import com.fs.im.service.OpenIMService;
 import com.fs.qw.service.IQwWorkTaskService;
-import com.fs.qwApi.service.QwApiService;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
 import com.fs.system.service.ISysConfigService;
@@ -62,8 +61,6 @@ public class CommonController
     @Autowired
     private ServerConfig serverConfig;
 
-    @Autowired
-    private QwApiService qwApiService;
     @Autowired
     private ITencentCloudCosService tencentCloudCosService;
 

+ 0 - 3
fs-company/src/main/java/com/fs/company/controller/common/Test.java

@@ -35,7 +35,6 @@ import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwExternalContactTransferLogService;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.AdUploadVo;
-import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.service.IQwSopTempContentService;
 import com.fs.sop.service.IQwSopTempDayService;
 import com.fs.sop.service.IQwSopTempRulesService;
@@ -138,8 +137,6 @@ public class Test {
     CompanyUserMapper companyUserMapper;
     @Autowired
     FsInquiryOrderMapper fsInquiryOrderMapper;
-    @Autowired
-    QwApiService qwApiService;
     //每天执行一次
     @Autowired
     IQwAppContactWayService qwAppContactWayService;

+ 27 - 1
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -14,6 +14,7 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.DictUtils;
 import com.fs.common.utils.PubFun;
+import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
@@ -51,11 +52,13 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections.CollectionUtils;
 import org.codehaus.jettison.json.JSONException;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
 import java.io.IOException;
+import java.net.SocketTimeoutException;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -105,6 +108,10 @@ public class QwExternalContactController extends BaseController
     @Autowired
     private IFsUserCompanyBindService fsUserCompanyBindService;
 
+    /** HTTP调用超时时间(秒) */
+    @Value("${qw.api.timeout:30}")
+    private int apiTimeout;
+
     /**
      * 查询企业微信客户列表
      */
@@ -461,7 +468,26 @@ public class QwExternalContactController extends BaseController
     @PostMapping("/addUnassigned")
     public R addUnassigned(@RequestBody QwExternalContact qwExternalContact)
     {
-        return qwExternalContactService.syncQwExternalContactUnassigned(qwExternalContact.getCorpId());
+        // 从当前登录用户获取租户ID
+        Long tenantId = SecurityUtils.getTenantId();
+        String url = OpenQwConfig.api + "/qw/externalContact/addUnassigned?corpId=" + qwExternalContact.getCorpId() + "&tenantId=" + tenantId;
+        try {
+            HttpResponse response = HttpRequest.post(url)
+                    .timeout(apiTimeout * 1000)
+                    .execute();
+            if (response.getStatus() == 200) {
+                return JSON.parseObject(response.body(), R.class);
+            } else {
+                log.error("同步待转接客户失败,HTTP状态码: {}", response.getStatus());
+                return R.error("同步待转接客户失败,服务返回状态码: " + response.getStatus());
+            }
+        } catch (Exception e) {
+            log.error("同步待转接客户异常, url={}", url, e);
+            if (e.getCause() instanceof SocketTimeoutException) {
+                return R.error("同步待转接客户超时,请稍后重试");
+            }
+            return R.error("同步待转接客户失败: " + e.getMessage());
+        }
     }
 
     @PreAuthorize("@ss.hasPermi('qw:externalContact:addTag') or @ss.hasPermi('qw:externalContact:myAddTag') or @ss.hasPermi('qw:externalContact:deptAddTag')")

+ 40 - 94
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -1,7 +1,8 @@
 package com.fs.company.controller.qw;
 
 import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.http.HttpUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
@@ -14,6 +15,7 @@ import com.fs.common.enums.BusinessType;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.exception.user.UserPasswordNotMatchException;
 import com.fs.common.utils.MessageUtils;
+import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.Company;
@@ -27,8 +29,6 @@ import com.fs.framework.manager.AsyncManager;
 import com.fs.framework.manager.factory.AsyncFactory;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
-import com.fs.qw.domain.QwCompany;
-import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwCompanyMapper;
 import com.fs.qw.mapper.QwExternalContactMapper;
@@ -40,17 +40,13 @@ import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwUserVO;
 import com.fs.qw.vo.UpdateSendTypeVo;
 import com.fs.qwApi.config.OpenQwConfig;
-import com.fs.qwApi.domain.QwExternalContactAllListResult;
-import com.fs.qwApi.domain.inner.ExternalContact;
-import com.fs.qwApi.domain.inner.ExternalContactInfo;
-import com.fs.qwApi.domain.inner.FollowInfo;
-import com.fs.qwApi.param.QwExternalListParam;
-import com.fs.qwApi.service.QwApiService;
+import cn.hutool.http.HttpUtil;
 import com.fs.voice.utils.StringUtil;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
-import org.slf4j.LoggerFactory;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
@@ -59,6 +55,7 @@ import org.springframework.security.core.Authentication;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.net.SocketTimeoutException;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -68,11 +65,11 @@ import java.util.stream.Collectors;
  * @author fs
  * @date 2024-06-20
  */
+@Slf4j
 @RestController
 @RequestMapping("/qw/user")
 public class QwUserController extends BaseController
 {
-    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(QwUserController.class);
 
     @Autowired
     private IQwUserService qwUserService;
@@ -101,15 +98,16 @@ public class QwUserController extends BaseController
     @Autowired
     private CompanyUserMapper companyUserMapper;
 
-    @Autowired
-    private QwApiService qwApiService;
-
     @Resource
     private AuthenticationManager authenticationManager;
 
     @Autowired
     private CompanyDeptServiceImpl companyDeptService;
 
+    /** HTTP调用超时时间(秒) */
+    @Value("${qw.api.timeout:120}")
+    private int apiTimeout;
+
     /**
      * 查询企微员工列表
      */
@@ -856,88 +854,36 @@ public class QwUserController extends BaseController
     }
 
     public R  syncMyQwExternalContact(QwUser qwUser,String getNextCursor )  {
-
-        String qwUserId = qwUser.getQwUserId();
-        String corpId = qwUser.getCorpId();
-        Long companyId = qwUser.getCompanyId();
-
-        QwExternalListParam param = new QwExternalListParam();
-        param.setLimit(100);
-        param.setUserid_list(Arrays.asList(qwUserId));
-        param.setCursor(getNextCursor);
-        QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(corpId);
-        QwExternalContactAllListResult list = qwApiService.getAllExternalcontactList(param, corpId,qwCompany);
-
-        logger.info("批量获取客户详情" + list);
-
-        if (list.getErrcode() == 0) {
-            List<ExternalContactInfo> externalContactList = list.getExternal_contact_list();
-            for (ExternalContactInfo externalContactInfo : externalContactList) {
-
-                ExternalContact externalContact = externalContactInfo.getExternal_contact();
-                FollowInfo followInfo = externalContactInfo.getFollow_info();
-                QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactUserIdAndExternalIdAndCompanyId(externalContact.getExternal_userid(), qwUserId, corpId);
-                logger.info("客户详情" + qwExternalContact);
-
-                if (qwExternalContact == null) {
-                    qwExternalContact = new QwExternalContact();
-                    qwExternalContact.setUserId(qwUserId); // 设置属于用户ID
-                    qwExternalContact.setExternalUserId(externalContact.getExternal_userid()); // 设置外部联系人ID
-
-
-                    qwExternalContact.setCorpId(corpId); // 设置企业ID
-                    qwExternalContact.setCompanyId(companyId); // 设置公司ID
-                }
-
-
-                // 设置公共属性
-                qwExternalContact.setCompanyUserId(qwUser.getCompanyUserId());
-                qwExternalContact.setQwUserId(qwUser.getId());
-                qwExternalContact.setName(externalContact.getName()); // 设置名称
-                qwExternalContact.setAvatar(externalContact.getAvatar()); // 设置头像
-                qwExternalContact.setType(externalContact.getType()); // 设置外部联系人类型(1微信用户,2企业微信用户)
-                qwExternalContact.setGender(externalContact.getGender()); // 设置性别 (0-未知, 1-男性, 2-女性)
-                qwExternalContact.setDescription(followInfo.getDescription()); // 设置描述信息
-                qwExternalContact.setRemark(followInfo.getRemark());
-
-//                        if (followInfo.getTag_id() != null && !followInfo.getTag_id().isEmpty()) {
-                qwExternalContact.setTagIds(JSON.toJSONString(followInfo.getTag_id())); // 设置标签ID
-//                        }
-
-//                        if (followInfo.getRemark_mobiles() != null && !followInfo.getRemark_mobiles().isEmpty()) {
-                qwExternalContact.setRemarkMobiles(JSON.toJSONString(followInfo.getRemark_mobiles())); // 设置备注电话号码
-//                        }
-
-                qwExternalContact.setState(followInfo.getState());
-
-                if (followInfo.getState() != null && !followInfo.getState().isEmpty()) {
-                    String s = "way:" + corpId + ":";
-                    if (followInfo.getState().contains(s)) {
-                        String wayId = followInfo.getState().substring(followInfo.getState().indexOf(s) + s.length());
-                        qwExternalContact.setWayId(Long.parseLong(wayId));
-                    }
-                }
-
-                qwExternalContact.setRemarkCorpName(followInfo.getRemark_corp_name()); // 设置备注企业名称
-                qwExternalContact.setAddWay(followInfo.getAdd_way()); // 设置来源
-                qwExternalContact.setOperUserid(followInfo.getOper_userid()); // 设置oper用户ID
-
-                // 根据是否存在记录进行更新或插入
-                if (qwExternalContact.getId() != null) {
-                    qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
-                } else {
-                    qwExternalContactMapper.insertQwExternalContact(qwExternalContact);
-                }
+        // 从当前登录用户获取租户ID
+        Long tenantId = SecurityUtils.getTenantId();
+        String encodedQwUser;
+        try {
+            encodedQwUser = java.net.URLEncoder.encode(JSON.toJSONString(qwUser), "UTF-8");
+        } catch (java.io.UnsupportedEncodingException e) {
+            encodedQwUser = JSON.toJSONString(qwUser);
+        }
+        String url = OpenQwConfig.api + "/qw/externalContact/syncExternalContact?tenantId=" + tenantId
+                + "&qwUser=" + encodedQwUser;
+        if (getNextCursor != null && !getNextCursor.isEmpty()) {
+            url += "&cursor=" + getNextCursor;
+        }
+        try {
+            HttpResponse response = HttpRequest.post(url)
+                    .timeout(apiTimeout * 1000)
+                    .execute();
+            if (response.getStatus() == 200) {
+                return JSON.parseObject(response.body(), R.class);
+            } else {
+                log.error("同步外部联系人失败,HTTP状态码: {}", response.getStatus());
+                return R.error("同步外部联系人失败,服务返回状态码: " + response.getStatus());
             }
-
-        }else {
-            return R.error("同步失败:"+list.getErrmsg());
+        } catch (Exception e) {
+            log.error("同步外部联系人异常, url={}", url, e);
+            if (e.getCause() instanceof SocketTimeoutException) {
+                return R.error("同步外部联系人超时,请稍后重试");
+            }
+            return R.error("同步外部联系人失败: " + e.getMessage());
         }
-
-       if (!StringUtil.strIsNullOrEmpty(list.getNext_cursor())){
-           syncMyQwExternalContact(qwUser,list.getNext_cursor());
-       }
-        return R.ok();
     }
 
 //    /**

+ 33 - 36
fs-company/src/main/java/com/fs/company/controller/qw/QwWorkTaskNewController.java

@@ -11,21 +11,24 @@ import com.fs.company.service.impl.CompanyDeptServiceImpl;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
-import com.fs.qw.domain.QwExternalContact;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import com.alibaba.fastjson.JSON;
+import com.fs.common.utils.SecurityUtils;
 import com.fs.qw.domain.QwWorkTask;
-import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.param.QwWorkTaskListParam;
-import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwWorkTaskService;
 import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
-import com.fs.qwApi.domain.QwExternalContactRemarkResult;
-import com.fs.qwApi.param.QwExternalContactRemarkParam;
-import com.fs.qwApi.service.QwApiService;
+import com.fs.qwApi.config.OpenQwConfig;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
+import java.net.SocketTimeoutException;
+
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -36,6 +39,7 @@ import java.util.List;
  * @author fs
  * @date 2025-03-25
  */
+@Slf4j
 @RestController
 @RequestMapping("/qw/QwWorkTaskNew")
 public class QwWorkTaskNewController extends BaseController
@@ -163,12 +167,10 @@ public class QwWorkTaskNewController extends BaseController
     {
         return toAjax(qwWorkTaskService.insertQwWorkTask(qwWorkTask));
     }
-    @Autowired
-    QwApiService qwApiService;
-    @Autowired
-    IQwExternalContactService qwExternalContactService;
-    @Autowired
-    QwExternalContactMapper qwExternalContactMapper;
+    /** HTTP调用超时时间(秒) */
+    @Value("${qw.api.timeout:30}")
+    private int apiTimeout;
+
     /**
      * 修改企微任务看板
      */
@@ -177,32 +179,27 @@ public class QwWorkTaskNewController extends BaseController
     @PutMapping
     public AjaxResult edit(@RequestBody QwWorkTask qwWorkTask)
     {
-        QwWorkTask task = new QwWorkTask();
-        task.setId(qwWorkTask.getId());
-        task.setStatus(1);
-        task.setTrackType(qwWorkTask.getTrackType());
-        task.setDescription(qwWorkTask.getDescription());
-        task.setUpdateTime(new Date());
-        if (task.getDescription()!=null&& !task.getDescription().isEmpty()){
-
-            QwExternalContact qwExternalContact = qwExternalContactService.selectQwExternalContactById(qwWorkTask.getExtId());
-            if (qwExternalContact!=null){
-                QwExternalContactRemarkParam param = new QwExternalContactRemarkParam();
-                param.setUserid(qwExternalContact.getUserId());
-                param.setExternal_userid(qwExternalContact.getExternalUserId());
-                param.setDescription(task.getDescription());
-
-                QwExternalContactRemarkResult qwExternalContactRemarkResult = qwApiService.externalcontactRemark(param, qwExternalContact.getCorpId());
-                logger.info("QwExternalContactRemarkResult206:" + qwExternalContactRemarkResult);
-                if (qwExternalContactRemarkResult.getErrcode() == 0) {
-                    QwExternalContact ext = new QwExternalContact();
-                    ext.setId(qwExternalContact.getId());
-                    ext.setDescription(task.getDescription());
-                    qwExternalContactMapper.updateQwExternalContact(ext);
-                }
+        // 从当前登录用户获取租户ID
+        Long tenantId = SecurityUtils.getTenantId();
+        String url = OpenQwConfig.api + "/qw/QwWorkTaskNew/edit?tenantId=" + tenantId;
+        try {
+            HttpResponse response = HttpRequest.post(url)
+                    .body(JSON.toJSONString(qwWorkTask))
+                    .timeout(apiTimeout * 1000)
+                    .execute();
+            if (response.getStatus() == 200) {
+                return JSON.parseObject(response.body(), AjaxResult.class);
+            } else {
+                log.error("修改任务看板失败,HTTP状态码: {}", response.getStatus());
+                return AjaxResult.error("修改任务看板失败,服务返回状态码: " + response.getStatus());
             }
+        } catch (Exception e) {
+            log.error("修改任务看板异常, url={}", url, e);
+            if (e.getCause() instanceof SocketTimeoutException) {
+                return AjaxResult.error("修改任务看板超时,请稍后重试");
+            }
+            return AjaxResult.error("修改任务看板失败: " + e.getMessage());
         }
-        return toAjax(qwWorkTaskService.updateQwWorkTask(task));
     }
     @PreAuthorize("@ss.hasPermi('qw:QwWorkTaskNew:edit')")
     @Log(title = "企微任务看板处理", businessType = BusinessType.UPDATE)

+ 44 - 31
fs-company/src/main/java/com/fs/framework/redis/RedisKeyExpirationListener.java

@@ -1,31 +1,25 @@
 package com.fs.framework.redis;
 
-import com.alibaba.fastjson.JSON;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
 import com.fs.common.constant.FsConstants;
-import com.fs.qw.service.IQwFriendWelcomeService;
-import com.fs.qw.service.impl.QwFriendWelcomeServiceImpl;
-import com.fs.qw.vo.QwFriendWelcomeDayPart;
-import com.fs.qw.vo.QwFriendWelcomeVO;
-import com.fs.qwApi.param.SendWelcomeMsgParam;
-import com.fs.qwApi.service.QwApiService;
+import com.fs.qwApi.config.OpenQwConfig;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.connection.Message;
 import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
 import org.springframework.stereotype.Component;
 
+import java.net.SocketTimeoutException;
 import java.nio.charset.StandardCharsets;
-import java.util.List;
 
 @Slf4j
 @Component
 public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
 
-    @Autowired
-    private IQwFriendWelcomeService qwFriendWelcomeService;
-    @Autowired
-    private QwApiService qwApiService;
+    @Value("${qw.api.timeout:30}")
+    private int apiTimeout;
 
     public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
         super(listenerContainer);
@@ -40,28 +34,47 @@ public class RedisKeyExpirationListener extends KeyExpirationEventMessageListene
     public void onMessage(Message message, byte[] pattern) {
         //过期的key
         String key = new String(message.getBody(),StandardCharsets.UTF_8);
-        // 好友欢迎语过期判断
-        if(key.contains(FsConstants.FRIEND_WELCOME_VIDEO_KEY)) {
-            key = key.replace(FsConstants.FRIEND_WELCOME_VIDEO_KEY, "");
-            Long id = Long.parseLong(key.split(":")[0]);
-            QwFriendWelcomeVO vo = qwFriendWelcomeService.selectQwFriendWelcomeById(id);
-            List<SendWelcomeMsgParam.Attachment> list = JSON.parseArray(vo.getAttachments(), SendWelcomeMsgParam.Attachment.class);
-            list.forEach(e -> this.build(e, vo));
-            if (vo.getIsDayparting().equals("1")) {
-                List<QwFriendWelcomeDayPart.Schedule> schedules = JSON.parseArray(vo.getDaypartingItemlist(), QwFriendWelcomeDayPart.Schedule.class);
-                schedules.forEach(schedule -> {
-                    schedule.getAttachments().forEach(e -> this.build(e, vo));
-                });
+
+        // 新格式: friend:welcome:v2:{tenantId}:{id}
+        if(key.contains(FsConstants.FRIEND_WELCOME_VIDEO_KEY_V2)) {
+            try {
+                String remain = key.replace(FsConstants.FRIEND_WELCOME_VIDEO_KEY_V2, "");
+                String[] parts = remain.split(":");
+                if (parts.length >= 2) {
+                    Long tenantId = Long.parseLong(parts[0]);
+                    Long id = Long.parseLong(parts[1]);
+                    refreshWelcomeVideo(id, tenantId);
+                } else {
+                    log.warn("[RedisKeyExpiration] 欢迎语视频key格式异常,无法解析tenantId和id,key={}", key);
+                }
+            } catch (Exception e) {
+                log.error("[RedisKeyExpiration] 解析欢迎语视频key异常,key={}", key, e);
             }
         }
+        // 兼容旧格式: friend:welcome:{id}(旧key不含tenantId,无法远程调用,记录警告)
+        else if(key.contains(FsConstants.FRIEND_WELCOME_VIDEO_KEY)) {
+            log.warn("[RedisKeyExpiration] 收到旧格式欢迎语视频key过期,无法获取tenantId进行远程调用,key={}。请确保所有key已迁移到新格式。", key);
+        }
     }
 
-    public void build(SendWelcomeMsgParam.Attachment e, QwFriendWelcomeVO vo){
-        if(e.getMsgtype().equals("video")) {
-            try {
-                QwFriendWelcomeServiceImpl.uploadVideo(qwApiService, e, vo.getCorpId());
-            } catch (Exception ex) {
-                log.error("欢迎语视频重新上传失败", ex);
+    /**
+     * 通过HTTP远程调用刷新欢迎语视频
+     */
+    private void refreshWelcomeVideo(Long id, Long tenantId) {
+        String url = OpenQwConfig.api + "/qw/friendWelcome/refreshWelcomeVideo?id=" + id + "&tenantId=" + tenantId;
+        try {
+            HttpResponse response = HttpRequest.post(url)
+                    .timeout(apiTimeout * 1000)
+                    .execute();
+            if (response.getStatus() == 200) {
+                log.info("[RedisKeyExpiration] 刷新欢迎语视频成功,tenantId={}, id={}", tenantId, id);
+            } else {
+                log.error("[RedisKeyExpiration] 刷新欢迎语视频失败,HTTP状态码: {}, tenantId={}, id={}", response.getStatus(), tenantId, id);
+            }
+        } catch (Exception e) {
+            log.error("[RedisKeyExpiration] 刷新欢迎语视频异常, tenantId={}, id={}", tenantId, id, e);
+            if (e.getCause() instanceof SocketTimeoutException) {
+                log.error("[RedisKeyExpiration] 刷新欢迎语视频超时,tenantId={}, id={}", tenantId, id);
             }
         }
     }

+ 63 - 0
fs-qw-api/src/main/java/com/fs/app/controller/QwFriendWelcomeController.java

@@ -1,5 +1,6 @@
 package com.fs.app.controller;
 
+import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -9,10 +10,17 @@ import com.fs.framework.datasource.TenantDataSourceUtil;
 import com.fs.qw.domain.QwFriendWelcome;
 import com.fs.qw.param.QwFriendWelcomeParam;
 import com.fs.qw.service.IQwFriendWelcomeService;
+import com.fs.qw.service.impl.QwFriendWelcomeServiceImpl;
+import com.fs.qw.vo.QwFriendWelcomeDayPart;
+import com.fs.qw.vo.QwFriendWelcomeVO;
+import com.fs.qwApi.param.SendWelcomeMsgParam;
+import com.fs.qwApi.service.QwApiService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
+
 
 /**
  * 好友欢迎语Controller
@@ -31,6 +39,9 @@ public class QwFriendWelcomeController extends BaseController
     @Autowired
     private TenantDataSourceUtil tenantDataSourceUtil;
 
+    @Autowired
+    private QwApiService qwApiService;
+
     /**
      * 新增好友欢迎语
      * SaaS 多租户支持:通过 tenantId 切换租户数据源
@@ -100,4 +111,56 @@ public class QwFriendWelcomeController extends BaseController
             return AjaxResult.error("删除好友欢迎语失败: " + e.getMessage());
         }
     }
+
+    /**
+     * 刷新好友欢迎语视频(Redis key 过期触发)
+     * 当视频临时素材过期时,重新上传视频获取新的 media_id
+     * SaaS 多租户支持:通过 tenantId 切换租户数据源
+     */
+    @PostMapping("/refreshWelcomeVideo")
+    public R refreshWelcomeVideo(@RequestParam("id") Long id, @RequestParam("tenantId") Long tenantId)
+    {
+        try {
+            log.info("[QwFriendWelcome] 刷新欢迎语视频,tenantId={}, id={}", tenantId, id);
+            return tenantDataSourceUtil.executeWithResult(tenantId, () -> {
+                QwFriendWelcomeVO vo = qwFriendWelcomeService.selectQwFriendWelcomeById(id);
+                if (vo == null) {
+                    log.warn("[QwFriendWelcome] 欢迎语不存在,id={}", id);
+                    return R.error("欢迎语不存在");
+                }
+                List<SendWelcomeMsgParam.Attachment> list = JSON.parseArray(vo.getAttachments(), SendWelcomeMsgParam.Attachment.class);
+                if (list != null) {
+                    list.forEach(e -> build(e, vo.getCorpId()));
+                }
+                if ("1".equals(vo.getIsDayparting())) {
+                    List<QwFriendWelcomeDayPart.Schedule> schedules = JSON.parseArray(vo.getDaypartingItemlist(), QwFriendWelcomeDayPart.Schedule.class);
+                    if (schedules != null) {
+                        schedules.forEach(schedule -> {
+                            schedule.getAttachments().forEach(e -> build(e, vo.getCorpId()));
+                        });
+                    }
+                }
+                return R.ok();
+            });
+        } catch (IllegalArgumentException e) {
+            log.error("[QwFriendWelcome] 刷新欢迎语视频失败,租户不存在或已禁用,tenantId={}", tenantId, e);
+            return R.error("租户不存在或已禁用");
+        } catch (Exception e) {
+            log.error("[QwFriendWelcome] 刷新欢迎语视频异常,tenantId={}, id={}", tenantId, id, e);
+            return R.error("刷新欢迎语视频失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 对视频类型的附件重新上传,刷新 media_id
+     */
+    private void build(SendWelcomeMsgParam.Attachment e, String corpId) {
+        if ("video".equals(e.getMsgtype())) {
+            try {
+                QwFriendWelcomeServiceImpl.uploadVideo(qwApiService, e, corpId);
+            } catch (Exception ex) {
+                log.error("欢迎语视频重新上传失败", ex);
+            }
+        }
+    }
 }

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

@@ -1101,7 +1101,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
                 QwExternalContact qwExternalContact = new QwExternalContact();
                 qwExternalContact.setExternalUserId(infoEntry.getExternal_userid());
-                qwExternalContact.setUserId(infoEntry.getHandover_userid());
+                qwExternalContact.setQwOpenUserId(infoEntry.getHandover_userid());
                 qwExternalContact.setStatus(0);
                 List<QwExternalContact> qwExternalContacts = qwExternalContactMapper.selectQwExternalContactList(qwExternalContact);
                 if (qwExternalContacts != null && qwExternalContacts.size() > 0) {

+ 6 - 2
fs-service/src/main/java/com/fs/qw/service/impl/QwFriendWelcomeServiceImpl.java

@@ -5,6 +5,7 @@ import com.fs.common.constant.FsConstants;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCacheT;
 import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.Company;
 import com.fs.company.mapper.CompanyMapper;
@@ -225,7 +226,9 @@ public class QwFriendWelcomeServiceImpl implements IQwFriendWelcomeService {
         }
         int i = qwFriendWelcomeMapper.insertQwFriendWelcomeVO(qwFriendWelcomeParam);
         if(video){
-            redisCache.setCacheObject(FsConstants.FRIEND_WELCOME_VIDEO_KEY + qwFriendWelcomeParam.getId(), qwFriendWelcomeParam.getId(), 60, TimeUnit.HOURS);
+            Long tenantId = SecurityUtils.getTenantId();
+            String key = FsConstants.FRIEND_WELCOME_VIDEO_KEY_V2 + tenantId + ":" + qwFriendWelcomeParam.getId();
+            redisCache.setCacheObject(key, qwFriendWelcomeParam.getId(), 60, TimeUnit.HOURS);
         }
         if (i > 0) {
             return R.ok("新增成功");
@@ -362,7 +365,8 @@ public class QwFriendWelcomeServiceImpl implements IQwFriendWelcomeService {
             }
             qwFriendWelcome.setDaypartingItemlist(JSON.toJSONString(schedules));
         }
-        String key = FsConstants.FRIEND_WELCOME_VIDEO_KEY + qwFriendWelcome.getId();
+        Long tenantId = SecurityUtils.getTenantId();
+        String key = FsConstants.FRIEND_WELCOME_VIDEO_KEY_V2 + tenantId + ":" + qwFriendWelcome.getId();
         redisCache.deleteObject(key);
         if(video){
             redisCache.setCacheObject(key, qwFriendWelcome.getId(), 60, TimeUnit.HOURS);

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

@@ -60,6 +60,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectQwExternalContactVo"/>
         <where>
             <if test="userId != null  and userId != ''"> and user_id = #{userId}</if>
+            <if test="qwOpenUserId != null  and qwOpenUserId != ''"> and qw_open_user_id = #{qwOpenUserId}</if>
             <if test="corpId != null  and corpId != ''"> and corp_id = #{corpId}</if>
             <if test="externalUserId != null  and externalUserId != ''"> and external_user_id = #{externalUserId}</if>
             <if test="name != null  and name != ''"> and name like concat('%', #{name}, '%')</if>