Prechádzať zdrojové kódy

1、优化im调试方式

yys 1 deň pred
rodič
commit
25190a47f4

+ 437 - 133
fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java

@@ -68,6 +68,17 @@ import java.util.stream.Collectors;
 @Service
 @Slf4j
 public class OpenIMServiceImpl implements OpenIMService {
+    /** IM账号注册状态缓存key前缀,已注册=1,缓存5分钟 */
+    private static final String IM_ACCOUNT_REGISTERED_KEY = "im:account:registered:";
+    /** IM账号注册状态缓存时间(秒) */
+    private static final int IM_ACCOUNT_CACHE_SECONDS = 300;
+    /** IM好友关系缓存key前缀 */
+    private static final String IM_FRIEND_STATUS_KEY = "im:friend:status:";
+    /** IM好友关系缓存时间(秒) */
+    private static final int IM_FRIEND_CACHE_SECONDS = 300;
+    /** HTTP请求超时时间(毫秒) */
+    private static final int HTTP_TIMEOUT_MS = 10000;
+
     @Autowired
     IMConfig imConfig;
     @Autowired
@@ -104,6 +115,20 @@ public class OpenIMServiceImpl implements OpenIMService {
     @Autowired
     private FsCourseWatchLogMapper fsCourseWatchLogMapper;
 
+    /**
+     * 通用IM HTTP POST请求封装,统一超时和header设置
+     */
+    private String imPost(String path, String bodyJson) {
+        String adminToken = getAdminToken();
+        return HttpRequest.post(IMConfig.URL + path)
+                .header("operationID", String.valueOf(System.currentTimeMillis()))
+                .header("token", adminToken)
+                .timeout(HTTP_TIMEOUT_MS)
+                .body(bodyJson)
+                .execute()
+                .body();
+    }
+
 
 //    @Value("${openIM.prefix}")
 //    private String openImPrefix;
@@ -123,6 +148,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         try {
             String response = HttpRequest.post(IMConfig.URL+"/auth/get_admin_token")
                     .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .timeout(HTTP_TIMEOUT_MS)
                     .body(requestBody.toString())
                     .execute()
                     .body();
@@ -229,7 +255,7 @@ public class OpenIMServiceImpl implements OpenIMService {
                 openImConversationDTO.setUserID(sendID);
                 openImEditConversationDTO.setConversation(openImConversationDTO);
                 OpenImResponseDTO openImResponseDTO1 = editConversation(openImEditConversationDTO);
-                log.info("修改回话返回参数:{}", openImResponseDTO1);
+                log.debug("修改回话返回参数:{}", openImResponseDTO1);
             }
             return null;
         } catch (Exception e) {
@@ -277,7 +303,7 @@ public class OpenIMServiceImpl implements OpenIMService {
                 }
                 currentConversation.setConversation(openImConversationDTO);
                 OpenImResponseDTO openImResponseDTO1 = editConversation(currentConversation);
-                log.info("修改回话返回参数:{}", openImResponseDTO1);
+                log.debug("修改回话返回参数:{}", openImResponseDTO1);
                 return true;
             }
             //③正常i情况 直接返回
@@ -289,53 +315,87 @@ public class OpenIMServiceImpl implements OpenIMService {
     }
 
     private  String getIMList( String recvID,String  conversationId)  {
-        int pageNumber = 1;
-        int pageSize = 20;
-        int maxPages = 10; // 最大搜索页数,防止无限循环
+        // 优化:直接查询指定会话,而非分页遍历全部会话
         String adminToken = getAdminToken();
         try {
-            while (pageNumber <= maxPages) {
-                JSONObject requestBody = new JSONObject();
-                requestBody.put("userID", recvID);
-                JSONObject pagination = new JSONObject();
-                pagination.put("pageNumber", pageNumber);
-                pagination.put("showNumber", pageSize);
-                requestBody.put("pagination", pagination);
-                String body = HttpRequest.post("https://webim.zkhj6.com/api/conversation/get_owner_conversation")
-                        .header("operationID", String.valueOf(System.currentTimeMillis()))
-                        .header("token", adminToken)
-                        .body(requestBody.toString())
-                        .execute()
-                        .body();
-                JSONObject jsonResponse = new JSONObject(body);
-                if (jsonResponse.getInt("errCode") == 0) {
-                    JSONObject data = jsonResponse.getJSONObject("data");
-                    int total = data.getInt("total");
-                    if (total > 0) {
-                        JSONArray conversations = data.getJSONArray("conversations");
-                        // 遍历当前页的所有会话
-                        for (int i = 0; i < conversations.length(); i++) {
-                            JSONObject conversation = conversations.getJSONObject(i);
-                            String convId = conversation.getString("conversationID");
-                            // 如果找到目标会话,返回其ex字段
-                            if (conversationId.equals(convId)) {
-                                return conversation.getString("ex");
-                            }
-                        }
-                        // 如果当前页没有找到且已经是最后一页,则退出循环
-                        if (conversations.length() < pageSize) {
-                            break;
+            // 先尝试通过 get_conversations 接口直接获取指定会话
+            JSONObject requestBody = new JSONObject();
+            requestBody.put("userID", recvID);
+            requestBody.put("conversationIDs", Collections.singletonList(conversationId));
+            String body = HttpRequest.post(IMConfig.URL + "/conversation/get_conversations")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .header("token", adminToken)
+                    .timeout(HTTP_TIMEOUT_MS)
+                    .body(requestBody.toString())
+                    .execute()
+                    .body();
+            JSONObject jsonResponse = new JSONObject(body);
+            if (jsonResponse.getInt("errCode") == 0) {
+                JSONObject data = jsonResponse.optJSONObject("data");
+                if (data != null) {
+                    JSONArray conversations = data.optJSONArray("conversations");
+                    if (conversations != null && conversations.length() > 0) {
+                        return conversations.getJSONObject(0).optString("ex", null);
+                    }
+                }
+            }
+            // 备用方案:如果上面的接口不可用,回退到分页查询但增大每页数量
+            log.warn("get_conversations 接口调用失败,回退到分页查询, recvID={}, conversationId={}", recvID, conversationId);
+            return getIMListFallback(recvID, conversationId);
+        } catch (Exception e) {
+            log.error("getIMList 异常,回退到分页查询, recvID={}, conversationId={}", recvID, conversationId, e);
+            try {
+                return getIMListFallback(recvID, conversationId);
+            } catch (Exception ex) {
+                log.error("getIMListFallback 异常", ex);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * getIMList 的备用分页查询方案(仅在 get_conversations 接口不可用时使用)
+     */
+    private String getIMListFallback(String recvID, String conversationId) {
+        int pageNumber = 1;
+        int pageSize = 100; // 增大每页数量,减少请求次数
+        int maxPages = 5;
+        String adminToken = getAdminToken();
+        while (pageNumber <= maxPages) {
+            JSONObject requestBody = new JSONObject();
+            requestBody.put("userID", recvID);
+            JSONObject pagination = new JSONObject();
+            pagination.put("pageNumber", pageNumber);
+            pagination.put("showNumber", pageSize);
+            requestBody.put("pagination", pagination);
+            String body = HttpRequest.post("https://webim.zkhj6.com/api/conversation/get_owner_conversation")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .header("token", adminToken)
+                    .timeout(HTTP_TIMEOUT_MS)
+                    .body(requestBody.toString())
+                    .execute()
+                    .body();
+            JSONObject jsonResponse = new JSONObject(body);
+            if (jsonResponse.getInt("errCode") == 0) {
+                JSONObject data = jsonResponse.getJSONObject("data");
+                int total = data.getInt("total");
+                if (total > 0) {
+                    JSONArray conversations = data.getJSONArray("conversations");
+                    for (int i = 0; i < conversations.length(); i++) {
+                        JSONObject conversation = conversations.getJSONObject(i);
+                        String convId = conversation.getString("conversationID");
+                        if (conversationId.equals(convId)) {
+                            return conversation.getString("ex");
                         }
-                    } else {
-                        // 如果没有数据,直接退出循环
+                    }
+                    if (conversations.length() < pageSize) {
                         break;
                     }
+                } else {
+                    break;
                 }
-                // 移动到下一页
-                pageNumber++;
             }
-        } catch (Exception e) {
-            e.printStackTrace();
+            pageNumber++;
         }
         return null;
     }
@@ -446,6 +506,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         String result1 = HttpRequest.post(IMConfig.URL+"/user/get_users_info")
                 .header("operationID", String.valueOf(time))
                 .header("token", adminToken)
+                .timeout(HTTP_TIMEOUT_MS)
                 .body(jsonBody)
                 .execute()
                 .body();
@@ -468,6 +529,7 @@ public class OpenIMServiceImpl implements OpenIMService {
             String result2 = HttpRequest.post(IMConfig.URL+"/user/update_user_info_ex")
                     .header("operationID", String.valueOf(System.currentTimeMillis()))
                     .header("token", adminToken)
+                    .timeout(HTTP_TIMEOUT_MS)
                     .body(jsonBody1)
                     .execute()
                     .body();
@@ -490,6 +552,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         String result1 = HttpRequest.post(IMConfig.URL+"/friend/delete_friend")
                 .header("operationID", String.valueOf(System.currentTimeMillis()))
                 .header("token", adminToken)
+                .timeout(HTTP_TIMEOUT_MS)
                 .body(jsonBody1)
                 .execute()
                 .body();
@@ -502,6 +565,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         String result2 = HttpRequest.post(IMConfig.URL+"/friend/delete_friend")
                 .header("operationID", String.valueOf(System.currentTimeMillis()))
                 .header("token", adminToken)
+                .timeout(HTTP_TIMEOUT_MS)
                 .body(jsonBody2)
                 .execute()
                 .body();
@@ -514,6 +578,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         String result2 = HttpRequest.post(IMConfig.URL+"/friend/add_black")
                 .header("operationID", String.valueOf(System.currentTimeMillis()))
                 .header("token", adminToken)
+                .timeout(HTTP_TIMEOUT_MS)
                 .body(jsonBody2)
                 .execute()
                 .body();*/
@@ -572,24 +637,25 @@ public class OpenIMServiceImpl implements OpenIMService {
 
     @Override
     public OpenImResponseDTO openIMSendMsg(OpenImMsgDTO openImMsgDTO) {
-        log.info("进入发消息的方法");
+        log.debug("进入发消息的方法");
         String adminToken = getAdminToken();
         JSONObject jsonObject = new JSONObject(openImMsgDTO);
         String url = IMConfig.URL+"/msg/send_msg";
         log.info("请求url: {}",url);
-        log.info("发送消息的请求体:\n{}", jsonObject.toString());
+        log.debug("发送消息的请求体:\n{}", jsonObject.toString());
         long time = new Date().getTime();
 
-        log.info("请求header: token={},operationID={}",adminToken,time);
+        log.debug("请求header: token={},operationID={}",adminToken,time);
         String result = HttpRequest.post(url)
                 .header("operationID", time + "")
                 .header("token",adminToken)
+                .timeout(HTTP_TIMEOUT_MS)
                 .body(jsonObject.toString())
                 .execute()
                 .body();
 
         OpenImResponseDTO responseDTO= JSONUtil.toBean(result,OpenImResponseDTO.class);
-        log.info("发送消息返回内容:\n{}", result);
+        log.debug("发送消息返回内容:\n{}", result);
         return responseDTO;
     }
     @Override
@@ -628,7 +694,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         openImMsgDTO.setContentType(110);
         openImMsgDTO.setSessionType(1);
         // 输出格式化JSON日志
-        log.info("课程消息: {}", JSON.toJSONString(openImMsgDTO));
+        log.info("课程消息发送, sendID={}, recvID={}", openImMsgDTO.getSendID(), openImMsgDTO.getRecvID());
         OpenImResponseDTO openImResponseDTO = openIMSendMsg(openImMsgDTO);
         openImMsgDTO = null;
         content = null;
@@ -782,7 +848,7 @@ public class OpenIMServiceImpl implements OpenIMService {
                 openImEditConversationDTO.setConversation(openImConversationDTO);
 
                 OpenImResponseDTO openImResponseDTO1 = editConversation(openImEditConversationDTO);
-                log.info("修改回话返回参数:{}",openImResponseDTO1);
+                log.debug("修改回话返回参数:{}",openImResponseDTO1);
 
             }
             return openImResponseDTO;
@@ -870,7 +936,7 @@ public class OpenIMServiceImpl implements OpenIMService {
                 openImEditConversationDTO.setConversation(openImConversationDTO);
 
                 OpenImResponseDTO openImResponseDTO1 = editConversation(openImEditConversationDTO);
-                log.info("修改回话返回参数:{}",openImResponseDTO1);
+                log.debug("修改回话返回参数:{}",openImResponseDTO1);
 
             }
             return openImResponseDTO;
@@ -898,6 +964,7 @@ public class OpenIMServiceImpl implements OpenIMService {
                 String body = HttpRequest.post(IMConfig.URL+"/conversation/set_conversations")
                         .header("operationID", String.valueOf(System.currentTimeMillis()))
                         .header("token", adminToken)
+                        .timeout(HTTP_TIMEOUT_MS)
                         .body(jsonObject.toString())
                         .execute()
                         .body();
@@ -930,6 +997,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         String body = HttpRequest.post(IMConfig.URL+"/friend/is_friend")
                 .header("operationID", String.valueOf(System.currentTimeMillis()))
                 .header("token", adminToken)
+                .timeout(HTTP_TIMEOUT_MS)
                 .body(jsonObject.toString())
                 .execute()
                 .body();
@@ -937,40 +1005,84 @@ public class OpenIMServiceImpl implements OpenIMService {
         return responseDTO;
     }
     /**
-     * 添加好友
+     * 添加好友(优化:带Redis缓存,减少isFriend请求次数)
      */
     @Override
     public OpenImResponseDTO importFriend(String ownerUserID, List<String> friendUserIDs) {
-        //先检查用户是否存在好友关系
-        List<String> newFriendIds = new ArrayList<>();
+        // 过滤掉已缓存为好友关系的用户
+        List<String> needCheckIds = new ArrayList<>();
         for (String friendUserID : friendUserIDs) {
-            OpenImResponseDTO friend = isFriend(ownerUserID,friendUserID);
-            if (friend.getErrCode()==0){
-                Object data = friend.getData();
-                cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(data);
-                Boolean inUser1Friends = (Boolean)jsonObject.get("inUser1Friends");
-                Boolean inUser2Friends = (Boolean)jsonObject.get("inUser2Friends");
-                //如果不存在好友关系
-                if (!inUser1Friends&&!inUser2Friends){
+            String friendCacheKey = IM_FRIEND_STATUS_KEY + ownerUserID + ":" + friendUserID;
+            Object cached = redisCache.getCacheObject(friendCacheKey);
+            if (cached != null && "1".equals(cached.toString())) {
+                // 已是好友,跳过
+                continue;
+            }
+            needCheckIds.add(friendUserID);
+        }
+
+        if (needCheckIds.isEmpty()) {
+            return null;
+        }
+
+        // 批量检查好友关系(每次最多50个,避免单次请求过大)
+        List<String> newFriendIds = new ArrayList<>();
+        int checkBatchSize = 50;
+        for (int i = 0; i < needCheckIds.size(); i += checkBatchSize) {
+            List<String> batch = needCheckIds.subList(i, Math.min(i + checkBatchSize, needCheckIds.size()));
+            for (String friendUserID : batch) {
+                try {
+                    OpenImResponseDTO friend = isFriend(ownerUserID, friendUserID);
+                    if (friend.getErrCode() == 0) {
+                        Object data = friend.getData();
+                        cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(data);
+                        Boolean inUser1Friends = (Boolean) jsonObject.get("inUser1Friends");
+                        Boolean inUser2Friends = (Boolean) jsonObject.get("inUser2Friends");
+                        if (inUser1Friends && inUser2Friends) {
+                            // 已是好友,缓存结果
+                            String friendCacheKey = IM_FRIEND_STATUS_KEY + ownerUserID + ":" + friendUserID;
+                            redisCache.setCacheObject(friendCacheKey, "1", IM_FRIEND_CACHE_SECONDS, TimeUnit.SECONDS);
+                        } else if (!inUser1Friends && !inUser2Friends) {
+                            // 不存在好友关系
+                            newFriendIds.add(friendUserID);
+                        } else {
+                            // 单向好友,也需要添加
+                            newFriendIds.add(friendUserID);
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("检查好友关系异常 ownerUserID={}, friendUserID={}", ownerUserID, friendUserID, e);
+                    // 异常时仍然尝试添加好友
                     newFriendIds.add(friendUserID);
-                    //friendUserIDs.remove(friendUserID);
                 }
             }
         }
-        if (newFriendIds.size()<=0){
+
+        if (newFriendIds.isEmpty()) {
             return null;
         }
+
         String adminToken = getAdminToken();
         JSONObject jsonObject = new JSONObject();
-        jsonObject.put("ownerUserID",ownerUserID);
-        jsonObject.put("friendUserIDs",newFriendIds);
-        String body = HttpRequest.post(IMConfig.URL+"/friend/import_friend")
+        jsonObject.put("ownerUserID", ownerUserID);
+        jsonObject.put("friendUserIDs", newFriendIds);
+        String body = HttpRequest.post(IMConfig.URL + "/friend/import_friend")
                 .header("operationID", String.valueOf(System.currentTimeMillis()))
                 .header("token", adminToken)
+                .timeout(HTTP_TIMEOUT_MS)
                 .body(jsonObject.toString())
                 .execute()
                 .body();
-        OpenImResponseDTO responseDTO= JSONUtil.toBean(body,OpenImResponseDTO.class);
+        OpenImResponseDTO responseDTO = JSONUtil.toBean(body, OpenImResponseDTO.class);
+
+        // 添加好友成功后缓存好友关系
+        if (responseDTO.getErrCode() == 0) {
+            for (String friendId : newFriendIds) {
+                String friendCacheKey = IM_FRIEND_STATUS_KEY + ownerUserID + ":" + friendId;
+                redisCache.setCacheObject(friendCacheKey, "1", IM_FRIEND_CACHE_SECONDS, TimeUnit.SECONDS);
+            }
+        }
+
         return responseDTO;
     }
 
@@ -981,22 +1093,18 @@ public class OpenIMServiceImpl implements OpenIMService {
      * @return
      */@Override
     public R accountCheck(String userId,String type){
+        // 优化:先查Redis缓存,已注册的用户直接跳过HTTP检查
+        String cacheKey = IM_ACCOUNT_REGISTERED_KEY + userId;
+        Object cached = redisCache.getCacheObject(cacheKey);
+        if (cached != null) {
+            // 已注册,直接获取token
+            return getUserToken(userId);
+        }
+
         String adminToken = getAdminToken();
         JSONObject requestBody = new JSONObject();
         // 解析响应
         if (StringUtil.isNotEmpty(adminToken)) {
-            //查询用户是否注册
-            /*switch (type){
-                case "1":
-                    userId = "U"+userId;
-                    break;
-                case "2":
-                    userId = userId;
-                    break;
-                case "3":
-                    userId = userId;
-                    break;
-            }*/
             ArrayList<String> userIds = new ArrayList<>();
             requestBody = new JSONObject();
             userIds.add(userId);
@@ -1004,6 +1112,7 @@ public class OpenIMServiceImpl implements OpenIMService {
             String body = HttpRequest.post(IMConfig.URL+"/user/account_check")
                     .header("operationID", String.valueOf(System.currentTimeMillis()))
                     .header("token", adminToken)
+                    .timeout(HTTP_TIMEOUT_MS)
                     .body(requestBody.toString())
                     .execute()
                     .body();
@@ -1022,13 +1131,11 @@ public class OpenIMServiceImpl implements OpenIMService {
                             s = userId.replaceFirst("^"+"C", "");
                             CompanyUser companyUser = companyUserMapper.selectCompanyUserByCompanyUserId(Long.parseLong(s));
                             if (null==companyUser){
-//                                return R.error("用户不存在");
                                 log.error("异步执行IM注册/添加好友失败,失败原因:{},userId:{},type:{}", "销售用户不存在",userId, type);
                                 throw new ServiceException("用户不存在");
                             }
                             Company company = companyMapper.selectCompanyById(companyUser.getCompanyId());
                             map.put("userID",userId);
-                            //map.put("nickname",companyUser.getImNickName());
                             map.put("nickname",companyUser.getNickName());
                             map.put("faceURL",companyUser.getAvatar());
                             break;
@@ -1036,7 +1143,6 @@ public class OpenIMServiceImpl implements OpenIMService {
                             s = userId.replaceFirst("^"+"U", "");
                             FsUser fsUser = fsUserMapper.selectFsUserByUserId(Long.parseLong(s));
                             if (null==fsUser){
-//                                return R.error("用户不存在");
                                 log.error("异步执行IM注册/添加好友失败,失败原因:{},userId:{},type:{}", "user用户不存在", userId, type);
                                 throw new ServiceException("用户不存在");
                             }
@@ -1048,7 +1154,6 @@ public class OpenIMServiceImpl implements OpenIMService {
                             s = userId.replaceFirst("^"+"D", "");
                             FsDoctor fsDoctor = fsDoctorMapper.selectFsDoctorByDoctorId(Long.parseLong(s));
                             if (null==fsDoctor){
-//                                return R.error("用户不存在");
                                 log.error("异步执行IM注册/添加好友失败,失败原因:{},userId:{},type:{}", "医生用户不存在", userId, type);
                                 throw new ServiceException("用户不存在");
                             }
@@ -1064,39 +1169,177 @@ public class OpenIMServiceImpl implements OpenIMService {
                     requestBody.put("users", users);
                     HttpRequest.post(IMConfig.URL+"/user/user_register")
                             .header("operationID", String.valueOf(System.currentTimeMillis()))
-                            .header("token", adminToken).body(requestBody.toString()).execute().body();
+                            .header("token", adminToken)
+                            .timeout(HTTP_TIMEOUT_MS)
+                            .body(requestBody.toString()).execute().body();
                 }
+                // 注册成功或已注册,写入缓存
+                redisCache.setCacheObject(cacheKey, "1", IM_ACCOUNT_CACHE_SECONDS, TimeUnit.SECONDS);
             } else {
-//                return R.error("返回结果为空");
                 log.error("异步执行IM注册/添加好友失败,失败原因:{},json结果:{}", "返回结果为空", jsonObject);
                 throw new ServiceException("返回结果为空");
             }
-           /* HashMap<String, String> tokenMap = new HashMap<>();
-            tokenMap.put("platformID","1");
-            tokenMap.put("userID",userId);*/
-            requestBody = new JSONObject();
-            requestBody.put("platformID",5);
-            requestBody.put("userID",userId);
-            String body1 = HttpRequest.post(IMConfig.URL+"/auth/get_user_token")
-                    .header("operationID", String.valueOf(System.currentTimeMillis()))
-                    .header("token", adminToken)
-                    .body(requestBody.toString()).execute().body();
-            JSONObject userJson = new JSONObject(body1);
-            Integer errCode = (Integer)userJson.get("errCode");
-            if (errCode==0){
-                JSONObject userData = userJson.getJSONObject("data");
-                String userToken = userData.getString("token");
-                return R.ok().put("token", userToken);
-            }
-//            return R.error("注册失败");
-            log.error("异步执行IM注册/添加好友失败,errCode:{}", errCode);
-            throw new ServiceException("注册失败");
+            return getUserToken(userId);
         } else {
-//            return R.error("获取管理员token失败");
             log.error("异步执行IM注册/添加好友失败,原因:{}, adminToken:{}", "获取管理员token失败", adminToken);
             throw new ServiceException("获取管理员token失败");
         }
     }
+
+    /**
+     * 批量账号检查与注册(优化:一次HTTP请求检查多个用户,减少请求次数)
+     * @param userIds 用户ID列表
+     * @param type 1用户,2销售,3医生
+     */
+    private void batchAccountCheck(List<String> userIds, String type) {
+        if (CollectionUtil.isEmpty(userIds)) {
+            return;
+        }
+        // 过滤掉已缓存的已注册用户
+        List<String> needCheckIds = userIds.stream()
+                .filter(id -> redisCache.getCacheObject(IM_ACCOUNT_REGISTERED_KEY + id) == null)
+                .distinct()
+                .collect(Collectors.toList());
+        if (needCheckIds.isEmpty()) {
+            return;
+        }
+
+        String adminToken = getAdminToken();
+        // 批量检查账号状态,每次最多100个
+        int batchSize = 100;
+        for (int i = 0; i < needCheckIds.size(); i += batchSize) {
+            List<String> batch = needCheckIds.subList(i, Math.min(i + batchSize, needCheckIds.size()));
+            JSONObject requestBody = new JSONObject();
+            requestBody.put("checkUserIDs", batch);
+            try {
+                String body = HttpRequest.post(IMConfig.URL + "/user/account_check")
+                        .header("operationID", String.valueOf(System.currentTimeMillis()))
+                        .header("token", adminToken)
+                        .timeout(HTTP_TIMEOUT_MS)
+                        .body(requestBody.toString())
+                        .execute()
+                        .body();
+                JSONObject jsonObject = new JSONObject(body);
+                JSONArray results = jsonObject.getJSONObject("data").getJSONArray("results");
+                List<HashMap<String, String>> toRegister = new ArrayList<>();
+                Set<String> registeredIds = new HashSet<>();
+
+                for (int j = 0; j < results.length(); j++) {
+                    JSONObject resultObj = results.getJSONObject(j);
+                    String uid = resultObj.getString("userID");
+                    int accountStatus = resultObj.getInt("accountStatus");
+                    if (accountStatus == 0) {
+                        // 未注册,需要注册
+                        HashMap<String, String> map = buildUserRegisterInfo(uid, type);
+                        if (map != null) {
+                            toRegister.add(map);
+                        }
+                    } else {
+                        registeredIds.add(uid);
+                    }
+                }
+
+                // 缓存已注册的用户
+                for (String uid : registeredIds) {
+                    redisCache.setCacheObject(IM_ACCOUNT_REGISTERED_KEY + uid, "1", IM_ACCOUNT_CACHE_SECONDS, TimeUnit.SECONDS);
+                }
+
+                // 批量注册未注册的用户
+                if (!toRegister.isEmpty()) {
+                    JSONObject regBody = new JSONObject();
+                    regBody.put("users", toRegister);
+                    String regResult = HttpRequest.post(IMConfig.URL + "/user/user_register")
+                            .header("operationID", String.valueOf(System.currentTimeMillis()))
+                            .header("token", adminToken)
+                            .timeout(HTTP_TIMEOUT_MS)
+                            .body(regBody.toString())
+                            .execute()
+                            .body();
+                    JSONObject regJson = new JSONObject(regResult);
+                    if (regJson.getInt("errCode") == 0) {
+                        // 注册成功,写入缓存
+                        for (HashMap<String, String> user : toRegister) {
+                            redisCache.setCacheObject(IM_ACCOUNT_REGISTERED_KEY + user.get("userID"), "1", IM_ACCOUNT_CACHE_SECONDS, TimeUnit.SECONDS);
+                        }
+                    } else {
+                        log.error("批量注册IM账号失败: {}", regResult);
+                    }
+                }
+            } catch (Exception e) {
+                log.error("批量检查IM账号异常", e);
+            }
+        }
+    }
+
+    /**
+     * 构建用户注册信息
+     */
+    private HashMap<String, String> buildUserRegisterInfo(String userId, String type) {
+        HashMap<String, String> map = new HashMap<>();
+        String s;
+        switch (type) {
+            case "2":
+                s = userId.replaceFirst("^C", "");
+                CompanyUser companyUser = companyUserMapper.selectCompanyUserByCompanyUserId(Long.parseLong(s));
+                if (null == companyUser) {
+                    log.error("构建IM注册信息失败,销售用户不存在,userId:{},type:{}", userId, type);
+                    return null;
+                }
+                map.put("userID", userId);
+                map.put("nickname", companyUser.getNickName());
+                map.put("faceURL", companyUser.getAvatar());
+                break;
+            case "1":
+                s = userId.replaceFirst("^U", "");
+                FsUser fsUser = fsUserMapper.selectFsUserByUserId(Long.parseLong(s));
+                if (null == fsUser) {
+                    log.error("构建IM注册信息失败,user用户不存在,userId:{},type:{}", userId, type);
+                    return null;
+                }
+                map.put("userID", userId);
+                map.put("nickname", StringUtils.isEmpty(fsUser.getNickName()) ? "微信用户" : fsUser.getNickName());
+                map.put("faceURL", fsUser.getAvatar());
+                break;
+            case "3":
+                s = userId.replaceFirst("^D", "");
+                FsDoctor fsDoctor = fsDoctorMapper.selectFsDoctorByDoctorId(Long.parseLong(s));
+                if (null == fsDoctor) {
+                    log.error("构建IM注册信息失败,医生用户不存在,userId:{},type:{}", userId, type);
+                    return null;
+                }
+                map.put("userID", userId);
+                map.put("nickname", fsDoctor.getDoctorName());
+                map.put("faceURL", fsDoctor.getAvatar());
+                break;
+            default:
+                return null;
+        }
+        return map;
+    }
+
+    /**
+     * 获取用户Token(从accountCheck中抽取)
+     */
+    private R getUserToken(String userId) {
+        String adminToken = getAdminToken();
+        JSONObject requestBody = new JSONObject();
+        requestBody.put("platformID", 5);
+        requestBody.put("userID", userId);
+        String body1 = HttpRequest.post(IMConfig.URL + "/auth/get_user_token")
+                .header("operationID", String.valueOf(System.currentTimeMillis()))
+                .header("token", adminToken)
+                .timeout(HTTP_TIMEOUT_MS)
+                .body(requestBody.toString()).execute().body();
+        JSONObject userJson = new JSONObject(body1);
+        Integer errCode = (Integer) userJson.get("errCode");
+        if (errCode == 0) {
+            JSONObject userData = userJson.getJSONObject("data");
+            String userToken = userData.getString("token");
+            return R.ok().put("token", userToken);
+        }
+        log.error("获取IM用户token失败,errCode:{}", errCode);
+        throw new ServiceException("注册失败");
+    }
 //    @Async
     @Override
     public void checkAndImportFriend(Long companyUserId,String fsUserId) {
@@ -1134,6 +1377,40 @@ public class OpenIMServiceImpl implements OpenIMService {
         }
     }
 
+    /**
+     * 批量注册并添加好友(优化:批量accountCheck + 批量importFriend,大幅减少HTTP请求次数)
+     * @param companyUserId 销售用户ID
+     * @param fsUserIds 用户ID列表(不带U前缀)
+     * @param cropId 企业ID
+     * @param isUpdate 是否更新好友信息
+     */
+    public void batchCheckAndImportFriend(Long companyUserId, List<String> fsUserIds, String cropId, boolean isUpdate) {
+        if (CollectionUtil.isEmpty(fsUserIds)) {
+            return;
+        }
+        try {
+            // 1. 先注册销售账号
+            accountCheck("C" + companyUserId, "2");
+
+            // 2. 批量注册用户账号
+            List<String> userIMIds = fsUserIds.stream().map(id -> "U" + id).collect(Collectors.toList());
+            batchAccountCheck(userIMIds, "1");
+
+            // 3. 批量导入好友关系
+            importFriend("C" + companyUserId, userIMIds);
+
+            // 4. 是否更新好友信息
+            if (isUpdate && !fsUserIds.isEmpty()) {
+                String firstUserId = "U" + fsUserIds.get(0);
+                ArrayList<String> userIds = new ArrayList<>();
+                userIds.add(firstUserId);
+                updateFriendByDianBo("C" + companyUserId, userIds, cropId);
+            }
+        } catch (Exception e) {
+            log.error("批量注册/添加好友失败, companyUserId={}", companyUserId, e);
+        }
+    }
+
 
     @Override
     @Transactional
@@ -1142,17 +1419,18 @@ public class OpenIMServiceImpl implements OpenIMService {
         String adminToken = getAdminToken();
         JSONObject jsonObject = new JSONObject(openImBatchMsgDTO);
         String url = IMConfig.URL+"/msg/batch_send_msg";
-        log.info("请求url: {},\n请求参数:{}", url, jsonObject);
+        log.info("请求url: {},接收人数:{}", url, openImBatchMsgDTO.getRecvIDs() != null ? openImBatchMsgDTO.getRecvIDs().size() : 0);
         long timestamp = System.currentTimeMillis();
-        log.info("请求header,operationID:{},token:{}", timestamp, adminToken);
+        log.debug("请求header,operationID:{},token:{}", timestamp, adminToken);
 
         String result = HttpRequest.post(url)
                 .header("operationID", timestamp + "")
                 .header("token",adminToken)
+                .timeout(HTTP_TIMEOUT_MS)
                 .body(jsonObject.toString())
                 .execute()
                 .body();
-        log.info("批量发送消息返回内容:\n{}", result);
+        log.debug("批量发送消息返回内容:\n{}", result);
         OpenImResponseDTO responseDTO=new OpenImResponseDTO();
         if (result != null && result.startsWith("<html")) {
             log.error("网关超时返回HTML报文: {}", result);
@@ -1173,11 +1451,9 @@ public class OpenIMServiceImpl implements OpenIMService {
         //获取需要发送的人
         List<String> userIds = this.getRecvIds(batchSendCourseDTO);
 
-        //注册和添加好友
-        for (String userId : userIds) {
-            String uId = userId.substring(1);
-            checkAndImportFriendByDianBo(batchSendCourseDTO.getCompanyUserId(), uId,null,false);
-        }
+        //优化:批量注册和添加好友(替代逐个循环调用checkAndImportFriendByDianBo)
+        List<String> fsUserIds = userIds.stream().map(id -> id.substring(1)).collect(Collectors.toList());
+        batchCheckAndImportFriend(batchSendCourseDTO.getCompanyUserId(), fsUserIds, null, false);
 
         //组装发课消息数据
         FsUserCourse fsUserCourse = fsUserCourseMapper.selectFsUserCourseByCourseId(batchSendCourseDTO.getCourseId());
@@ -1258,11 +1534,9 @@ public class OpenIMServiceImpl implements OpenIMService {
         //获取需要发送的人
         List<String> userIds = this.getRecvIds(batchSendCourseDTO);
 
-        //注册和添加好友
-        for (String userId : userIds) {
-            String uId = userId.substring(1);
-            checkAndImportFriendByDianBo(batchSendCourseDTO.getCompanyUserId(), uId,null,false);
-        }
+        //优化:批量注册和添加好友(替代逐个循环调用checkAndImportFriendByDianBo)
+        List<String> fsUserIds = userIds.stream().map(id -> id.substring(1)).collect(Collectors.toList());
+        batchCheckAndImportFriend(batchSendCourseDTO.getCompanyUserId(), fsUserIds, null, false);
 
         //组装发课消息数据
         FsUserCourse fsUserCourse = fsUserCourseMapper.selectFsUserCourseByCourseId(batchSendCourseDTO.getCourseId());
@@ -1462,7 +1736,8 @@ public class OpenIMServiceImpl implements OpenIMService {
     @Override
     @Transactional
     public OpenImResponseDTO batchSendCourseTask(BatchSendCourseDTO batchSendCourseDTO, OpenImBatchMsgDTO openImBatchMsgDTO, Long project, List<FsImMsgSendDetail> imMsgSendDetailList) {
-         log.info("批量发送课程消息: \n{}", JSON.toJSONString(openImBatchMsgDTO));
+         log.info("批量发送课程消息, 接收人数={}", openImBatchMsgDTO.getRecvIDs() != null ? openImBatchMsgDTO.getRecvIDs().size() : 0);
+        log.debug("批量发送课程消息详情: \n{}", JSON.toJSONString(openImBatchMsgDTO));
         OpenImResponseDTO openImResponseDTO = openIMBatchSendMsg(openImBatchMsgDTO);
 //        openImBatchMsgDTO = null;
 //        content = null;
@@ -1485,7 +1760,8 @@ public class OpenIMServiceImpl implements OpenIMService {
     @Override
     @Transactional
     public OpenImResponseDTO batchUrgeCourseTask(OpenImBatchMsgDTO openImBatchMsgDTO, List<FsImMsgSendDetail> imMsgSendDetailList) {
-        log.info("批量催课消息: \n{}", JSON.toJSONString(openImBatchMsgDTO));
+        log.info("批量催课消息, 接收人数={}", openImBatchMsgDTO.getRecvIDs() != null ? openImBatchMsgDTO.getRecvIDs().size() : 0);
+        log.debug("批量催课消息详情: \n{}", JSON.toJSONString(openImBatchMsgDTO));
         OpenImResponseDTO openImResponseDTO = openIMBatchSendMsg(openImBatchMsgDTO);
 
         // 修改发送记录
@@ -1527,7 +1803,8 @@ public class OpenIMServiceImpl implements OpenIMService {
              batchSendCourseDTO.setUrl(url);
              batchSendCourseDTO.setId(fsImMsgSendLog.getPeriodDaysId());
              OpenImBatchMsgDTO openImBatchMsgDTO = makeOpenImBatchMsgDTO(batchSendCourseDTO, courseUrl, objectMapper, userIds, System.currentTimeMillis(), "发课");
-             log.info("一键催课-发课-批量发送课程消息: \n{}", JSON.toJSONString(openImBatchMsgDTO));
+             log.info("一键催课-发课, 接收人数={}", openImBatchMsgDTO.getRecvIDs() != null ? openImBatchMsgDTO.getRecvIDs().size() : 0);
+             log.debug("一键催课-发课详情: \n{}", JSON.toJSONString(openImBatchMsgDTO));
              this.openIMBatchSendMsg(openImBatchMsgDTO);
 //            List<FsImMsgSendDetail> imMsgSendDetailList = createImMsgSendLog("发课",batchSendCourseDTO, System.currentTimeMillis(), 2, userIds, sendUnionId);
         }
@@ -1541,7 +1818,8 @@ public class OpenIMServiceImpl implements OpenIMService {
         batchSendCourseDTO.setIsUrgeCourse(true);
         batchSendCourseDTO.setSendType(2);
         OpenImBatchMsgDTO openImBatchMsgDTO = makeOpenImBatchMsgDTO(batchSendCourseDTO, courseUrl, objectMapper, userIds, System.currentTimeMillis(), "催课");
-        log.info("一键催课-催课-批量催课消息: \n{}", JSON.toJSONString(openImBatchMsgDTO));
+        log.info("一键催课-催课, 接收人数={}", openImBatchMsgDTO.getRecvIDs() != null ? openImBatchMsgDTO.getRecvIDs().size() : 0);
+        log.debug("一键催课-催课详情: \n{}", JSON.toJSONString(openImBatchMsgDTO));
         openImResponseDTO = this.openIMBatchSendMsg(openImBatchMsgDTO);
 
         List<FsImMsgSendDetail> imMsgSendDetailList = createImMsgSendLog("催课", batchSendCourseDTO, System.currentTimeMillis(), 2, userIds, fsImMsgSendLog.getSendUnionId());
@@ -1657,13 +1935,18 @@ public class OpenIMServiceImpl implements OpenIMService {
 
     private void updateImMsgSendLog(OpenImBatchMsgDTO openImBatchMsgDTO, List<FsImMsgSendDetail> imMsgSendDetailList, OpenImResponseDTO openImResponseDTO, String logType) {
         List<FsImMsgSendDetail> updateList = new ArrayList<>();
+        // 优化:提前序列化一次,避免在stream中重复序列化同一对象
+        final String batchMsgJson = JSON.toJSONString(openImBatchMsgDTO);
+        final String responseJson = StringUtils.substring(JSON.toJSONString(openImResponseDTO), 0, 3999);
+        final String errDltStr = StringUtils.substring(openImResponseDTO.getErrDlt(), 0, 1999);
+
         if(openImResponseDTO.getErrCode() != 0){
             // 所有都发送失败
             List<FsImMsgSendDetail> allFailedList = imMsgSendDetailList.stream().map(v -> {
-                v.setSendStatus(1).setParamJson(JSON.toJSONString(openImBatchMsgDTO))
+                v.setSendStatus(1).setParamJson(batchMsgJson)
                         .setStatus(1)
-                        .setResultMessage(StringUtils.substring(JSON.toJSONString(openImResponseDTO), 0, 3999))
-                        .setExceptionInfo(StringUtils.substring(openImResponseDTO.getErrDlt(), 0, 1999))
+                        .setResultMessage(responseJson)
+                        .setExceptionInfo(errDltStr)
                         .setUpdateTime(new Date());
                 return v;
             }).collect(Collectors.toList());
@@ -1683,13 +1966,15 @@ public class OpenIMServiceImpl implements OpenIMService {
             OpenImBatchResponseDataDTO openImBatchResponseDataDTO = JSON.parseObject(JSON.toJSONString(openImResponseDTO.getData()), OpenImBatchResponseDataDTO.class);
             if(openImBatchResponseDataDTO != null && openImBatchResponseDataDTO.getFailedUserIDs() != null) {
                 //发送失败的
+                final String batchResponseJson = JSON.toJSONString(openImBatchResponseDataDTO);
+                final String exceptionInfo = JSON.toJSONString(openImResponseDTO.getErrDlt());
                 String[] failedUserIds = openImBatchResponseDataDTO.getFailedUserIDs();
                 List<String> failedUserIdList = Arrays.asList(failedUserIds);
                 List<FsImMsgSendDetail> failedList = imMsgSendDetailList.stream().filter(v -> failedUserIdList.contains("U" + v.getUserId())).map(v -> {
-                    v.setSendStatus(1).setParamJson(JSON.toJSONString(openImBatchMsgDTO))
+                    v.setSendStatus(1).setParamJson(batchMsgJson)
                             .setStatus(1)
-                            .setResultMessage(JSON.toJSONString(openImBatchResponseDataDTO))
-                            .setExceptionInfo(JSON.toJSONString(openImResponseDTO.getErrDlt()))
+                            .setResultMessage(batchResponseJson)
+                            .setExceptionInfo(exceptionInfo)
                             .setUpdateTime(new Date());
                     return v;
                 }).collect(Collectors.toList());
@@ -1730,9 +2015,16 @@ public class OpenIMServiceImpl implements OpenIMService {
      * @return
      */
     public OpenImResponseDTO updateFriendByDianBo(String ownerUserID, List<String> friendUserIDs,String cropId) {
-        //先检查用户是否存在好友关系
-        //List<String> newFriendIds = new ArrayList<>();
+        // 优化:先查缓存判断好友关系,避免每次都调 isFriend HTTP 请求
+        boolean hasFriendRelation = false;
         for (String friendUserID : friendUserIDs) {
+            String friendCacheKey = IM_FRIEND_STATUS_KEY + ownerUserID + ":" + friendUserID;
+            Object cached = redisCache.getCacheObject(friendCacheKey);
+            if (cached != null && "1".equals(cached.toString())) {
+                hasFriendRelation = true;
+                break;
+            }
+            // 缓存没有,调HTTP检查
             OpenImResponseDTO friend = isFriend(ownerUserID,friendUserID);
             if (friend.getErrCode()==0){
                 Object data = friend.getData();
@@ -1742,10 +2034,16 @@ public class OpenIMServiceImpl implements OpenIMService {
                 //如果不存在好友关系,则不执行im修改好友信息
                 if (!inUser1Friends&&!inUser2Friends){
                     return null;
-                    //friendUserIDs.remove(friendUserID);
+                } else {
+                    // 存在好友关系,缓存
+                    redisCache.setCacheObject(friendCacheKey, "1", IM_FRIEND_CACHE_SECONDS, TimeUnit.SECONDS);
+                    hasFriendRelation = true;
                 }
             }
         }
+        if (!hasFriendRelation) {
+            return null;
+        }
         List<String> remark = qwExternalContactMapper.selectRemarkByCompanyUserAndFsUser(friendUserIDs.get(0).replaceFirst("^scrmU", ""), ownerUserID.replaceFirst("^scrmC", ""),cropId);
         if (CollectionUtils.isEmpty(remark)||remark.size()<=0){
             return null;
@@ -1758,6 +2056,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         String body = HttpRequest.post(IMConfig.URL+"/friend/update_friends")
                 .header("operationID", String.valueOf(System.currentTimeMillis()))
                 .header("token", adminToken)
+                .timeout(HTTP_TIMEOUT_MS)
                 .body(jsonObject.toString())
                 .execute()
                 .body();
@@ -1893,6 +2192,7 @@ public class OpenIMServiceImpl implements OpenIMService {
             String body = HttpRequest.post(IMConfig.URL+"/friend/get_friend_list")
                     .header("operationID", String.valueOf(System.currentTimeMillis()))
                     .header("token", adminToken)
+                    .timeout(HTTP_TIMEOUT_MS)
                     .body(jsonObject.toString())
                     .execute()
                     .body();
@@ -1902,6 +2202,7 @@ public class OpenIMServiceImpl implements OpenIMService {
             String body = HttpRequest.post(IMConfig.URL+"/friend/get_self_friend_apply_list")
                     .header("operationID", String.valueOf(System.currentTimeMillis()))
                     .header("token", adminToken)
+                    .timeout(HTTP_TIMEOUT_MS)
                     .body(jsonObject.toString())
                     .execute()
                     .body();
@@ -1911,6 +2212,7 @@ public class OpenIMServiceImpl implements OpenIMService {
             String body = HttpRequest.post(IMConfig.URL+"/friend/get_friend_apply_list")
                     .header("operationID", String.valueOf(System.currentTimeMillis()))
                     .header("token", adminToken)
+                    .timeout(HTTP_TIMEOUT_MS)
                     .body(jsonObject.toString())
                     .execute()
                     .body();
@@ -1972,6 +2274,7 @@ public class OpenIMServiceImpl implements OpenIMService {
             String body = HttpRequest.post(IMConfig.URL+"/user/get_users_info")
                     .header("operationID", String.valueOf(System.currentTimeMillis()))
                     .header("token", adminToken)
+                    .timeout(HTTP_TIMEOUT_MS)
                     .body(jsonObject.toString())
                     .execute()
                     .body();
@@ -1994,7 +2297,8 @@ public class OpenIMServiceImpl implements OpenIMService {
     public OpenImResponseDTO batchSendTextMessage(String senderId, List<String> receiverIds, String textContent,
                                                   String pushTitle, String pushDesc, String ex) {
         try {
-            log.info("批量发送文本消息 - 发送人: {}, 接收人数: {}, 内容: {}", senderId, receiverIds.size(), textContent);
+            log.info("批量发送文本消息 - 发送人: {}, 接收人数: {}", senderId, receiverIds.size());
+            log.debug("批量发送文本消息内容: {}", textContent);
 
             // 构建批量发送消息DTO
             OpenImBatchMsgDTO batchMsgDTO = new OpenImBatchMsgDTO();
@@ -2029,7 +2333,7 @@ public class OpenIMServiceImpl implements OpenIMService {
             batchMsgDTO.setNotOfflinePush(false);     // 允许离线推送
             batchMsgDTO.setSendTime(System.currentTimeMillis()); // 当前时间
 
-            log.info("批量发送文本消息请求: {}", JSON.toJSONString(batchMsgDTO));
+            log.debug("批量发送文本消息请求详情: {}", JSON.toJSONString(batchMsgDTO));
 
             // 调用批量发送接口
             OpenImResponseDTO response = this.openIMBatchSendMsg(batchMsgDTO);