Parcourir la source

企微聊天图片消息接收、发送、修改包名 同步拉取企微会话存档代码

Long il y a 2 semaines
Parent
commit
7936badd73
60 fichiers modifiés avec 802 ajouts et 138 suppressions
  1. 6 1
      fs-qw-api-msg/pom.xml
  2. 27 3
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  3. 1 17
      fs-qw-api-msg/src/main/java/com/fs/app/controller/testTask.java
  4. BIN
      fs-qw-api-msg/src/main/resources/jniLibs/WeWorkFinanceSdk.dll
  5. BIN
      fs-qw-api-msg/src/main/resources/jniLibs/libcrypto-3-x64.dll
  6. BIN
      fs-qw-api-msg/src/main/resources/jniLibs/libcurl-x64.dll
  7. 17 0
      fs-qw-api/pom.xml
  8. 304 0
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  9. 3 3
      fs-qw-api/src/main/java/com/fs/core/aspectj/DataScopeAspect.java
  10. 2 2
      fs-qw-api/src/main/java/com/fs/core/aspectj/DataSourceAspect.java
  11. 5 5
      fs-qw-api/src/main/java/com/fs/core/aspectj/LogAspect.java
  12. 1 1
      fs-qw-api/src/main/java/com/fs/core/aspectj/RateLimiterAspect.java
  13. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/ApplicationConfig.java
  14. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/ArrayStringTypeHandler.java
  15. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/CaptchaConfig.java
  16. 2 2
      fs-qw-api/src/main/java/com/fs/core/config/DataSourceConfig.java
  17. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/FastJson2JsonRedisSerializer.java
  18. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/FilterConfig.java
  19. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/KaptchaTextCreator.java
  20. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/MyBatisConfig.java
  21. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/RedisConfig.java
  22. 2 2
      fs-qw-api/src/main/java/com/fs/core/config/ResourcesConfig.java
  23. 4 4
      fs-qw-api/src/main/java/com/fs/core/config/SecurityConfig.java
  24. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/ServerConfig.java
  25. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/SwaggerConfig.java
  26. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/ThreadPoolConfig.java
  27. 1 1
      fs-qw-api/src/main/java/com/fs/core/config/properties/DruidProperties.java
  28. 1 1
      fs-qw-api/src/main/java/com/fs/core/datasource/DynamicDataSource.java
  29. 1 1
      fs-qw-api/src/main/java/com/fs/core/datasource/DynamicDataSourceContextHolder.java
  30. 1 1
      fs-qw-api/src/main/java/com/fs/core/exception/GlobalExceptionHandler.java
  31. 1 1
      fs-qw-api/src/main/java/com/fs/core/interceptor/RepeatSubmitInterceptor.java
  32. 2 2
      fs-qw-api/src/main/java/com/fs/core/interceptor/impl/SameUrlDataInterceptor.java
  33. 1 1
      fs-qw-api/src/main/java/com/fs/core/manager/AsyncManager.java
  34. 1 1
      fs-qw-api/src/main/java/com/fs/core/manager/ShutdownManager.java
  35. 1 1
      fs-qw-api/src/main/java/com/fs/core/manager/factory/AsyncFactory.java
  36. 1 1
      fs-qw-api/src/main/java/com/fs/core/security/LoginBody.java
  37. 1 1
      fs-qw-api/src/main/java/com/fs/core/security/LoginUser.java
  38. 1 1
      fs-qw-api/src/main/java/com/fs/core/security/SecurityUtils.java
  39. 4 4
      fs-qw-api/src/main/java/com/fs/core/security/filter/JwtAuthenticationTokenFilter.java
  40. 1 1
      fs-qw-api/src/main/java/com/fs/core/security/handle/AuthenticationEntryPointImpl.java
  41. 5 5
      fs-qw-api/src/main/java/com/fs/core/security/handle/LogoutSuccessHandlerImpl.java
  42. 4 4
      fs-qw-api/src/main/java/com/fs/core/service/CompanyLoginService.java
  43. 1 1
      fs-qw-api/src/main/java/com/fs/core/service/CompanyPermissionService.java
  44. 2 2
      fs-qw-api/src/main/java/com/fs/core/service/PermissionService.java
  45. 2 2
      fs-qw-api/src/main/java/com/fs/core/service/TokenService.java
  46. 2 2
      fs-qw-api/src/main/java/com/fs/core/service/UserDetailsServiceImpl.java
  47. 128 0
      fs-qw-api/src/main/java/com/tencent/wework/Finance.java
  48. BIN
      fs-qw-api/src/main/resources/jniLibs/WeWorkFinanceSdk.dll
  49. BIN
      fs-qw-api/src/main/resources/jniLibs/libcrypto-3-x64.dll
  50. BIN
      fs-qw-api/src/main/resources/jniLibs/libcurl-x64.dll
  51. 1 1
      fs-qw-api/src/main/resources/mybatis/mybatis-config.xml
  52. 16 1
      fs-service-system/src/main/java/com/fs/fastGpt/service/AiHookService.java
  53. 31 3
      fs-service-system/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  54. 2 1
      fs-service-system/src/main/java/com/fs/qw/param/QwMsgSendParam.java
  55. 88 47
      fs-service-system/src/main/java/com/fs/qw/service/impl/QwMsgServiceImpl.java
  56. 15 0
      fs-service-system/src/main/java/com/fs/wxwork/dto/WxCdnUploadImgLinkDTO.java
  57. 30 0
      fs-service-system/src/main/java/com/fs/wxwork/dto/WxCdnUploadImgLinkResp.java
  58. 31 0
      fs-service-system/src/main/java/com/fs/wxwork/dto/WxwDownloadWeChatFileDTO.java
  59. 16 0
      fs-service-system/src/main/java/com/fs/wxwork/service/WxWorkService.java
  60. 25 0
      fs-service-system/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java

+ 6 - 1
fs-qw-api-msg/pom.xml

@@ -121,7 +121,12 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-websocket</artifactId>
         </dependency>
-
+        <dependency>
+            <groupId>com.fs</groupId>
+            <artifactId>fs-qw-api</artifactId>
+            <version>1.1.0</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 27 - 3
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -3,6 +3,7 @@ package com.fs.app.controller;
 import com.alibaba.fastjson.JSON;
 import com.fs.app.socket.QwImSocket;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.uuid.IdUtils;
 import com.fs.fastGpt.service.AiHookService;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
@@ -134,7 +135,7 @@ public class QwMsgController {
                 qwUserStatus(wxWorkMsgResp.getUuid(),0);
                 break;
             case 102000:
-                System.out.println(wxWorkMsgResp.getJson());
+
                 WxWorkMessageDTO wxWorkMessageDTO = JSON.parseObject(wxWorkMsgResp.getJson(), WxWorkMessageDTO.class);
                 if (wxWorkMessageDTO.getIs_room()!=0){
                     break;
@@ -165,14 +166,14 @@ public class QwMsgController {
                         aiHookService.qwHookNotifyAiReply(id,sender,content,wxWorkMsgResp.getUuid(),wxWorkMessageDTO.getMsgtype());
 
                         // 保存聊天消息
-                        QwMessageListVO message = aiHookService.saveQwMsg(id, sender, content, wxWorkMsgResp.getUuid(), 1, wxWorkMsgResp.getJson());
+                        QwMessageListVO message = aiHookService.saveQwMsg(id, sender, content, wxWorkMsgResp.getUuid(), 1, wxWorkMsgResp.getJson(), 1);
                         QwImSocket.broadcast(message);
                     }else {
                         System.out.println("销售发送");
                         aiHookService.qwHookNotifyAddMsg(id,receiver,content,wxWorkMsgResp.getUuid());
 
                         // 保存聊天消息
-                        QwMessageListVO message = aiHookService.saveQwMsg(id, receiver, content, wxWorkMsgResp.getUuid(), 2, wxWorkMsgResp.getJson());
+                        QwMessageListVO message = aiHookService.saveQwMsg(id, receiver, content, wxWorkMsgResp.getUuid(), 2, wxWorkMsgResp.getJson(), 1);
                         QwImSocket.broadcast(message);
                     }
 
@@ -216,12 +217,35 @@ public class QwMsgController {
                 }
                 // 图片消息
                 else if (wxWorkMessageDTO.getMsgtype() == 101){
+                    System.out.println(json);
+                    System.out.println(wxWorkMsgResp.getJson());
                     Long receiver = wxWorkMessageDTO.getReceiver();
+                    Long sender = wxWorkMessageDTO.getSender();
+
+                    long userId;
+                    int sendType;
                     if (2000000000000000L - receiver > 0){
                         System.out.println("客户发起");
+                        userId = sender;
+                        sendType= 1;
                     }else {
                         System.out.println("销售发起");
+                        userId = receiver;
+                        sendType= 2;
                     }
+
+                    String fileName = IdUtils.fastSimpleUUID() + ".jpg";
+                    WxWorkResponseDTO<String> fileUrlResp =
+                            aiHookService.getFileUrl(wxWorkMsgResp.getUuid(), wxWorkMessageDTO.getFile_id(), wxWorkMessageDTO.getAes_key(), wxWorkMessageDTO.getOpenim_cdn_authkey(), fileName, wxWorkMessageDTO.getFile_size(), serverId);
+                    if (fileUrlResp.getErrcode() != 0) {
+                        log.warn("获取图片地址失败: {}", fileUrlResp.getErrmsg());
+                        break;
+                    }
+
+                    String content = fileUrlResp.getData();
+                    // 保存聊天消息
+                    QwMessageListVO message = aiHookService.saveQwMsg(id, userId, content, wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 2);
+                    QwImSocket.broadcast(message);
                 }
 
                 break;

+ 1 - 17
fs-qw-api-msg/src/main/java/com/fs/app/controller/testTask.java

@@ -1,10 +1,7 @@
-package com.fs.app.controller;//package com.fs.app.controller;
+//package com.fs.app.controller;
 //
 //import com.alibaba.fastjson.JSON;
 //import com.fs.app.service.QwDataCallbackService;
-//import com.fs.common.core.redis.RedisCache;
-//import com.fs.fastGpt.service.AiNewService;
-//import com.fs.fastGpt.service.AiService;
 //import com.fs.qw.domain.QwMessageGather;
 //import org.springframework.beans.factory.annotation.Autowired;
 //import org.springframework.scheduling.annotation.Scheduled;
@@ -17,8 +14,6 @@ package com.fs.app.controller;//package com.fs.app.controller;
 //
 //    @Autowired
 //    QwDataCallbackService qwDataCallbackService;
-//    @Autowired
-//    private AiNewService aiService;
 //
 //    @Scheduled(fixedRate = 5000) // 每10执行一次
 //    public void getMsg(){
@@ -43,22 +38,11 @@ package com.fs.app.controller;//package com.fs.app.controller;
 //            String from = qwMessageGather.getFrom();
 //            if (from.startsWith("wo")||from.startsWith("wm")){
 //                System.out.println("用户发消息");
-//                aiService.qwHookNotifyAiReply(qwMessageGather,corpId);
 //            }else {
-//                aiService.qwHookNotifyAddMsg(qwMessageGather,corpId);
 //                System.out.println("客服发送消息");
 //            }
 //            QwMessageGather.TextContent text = qwMessageGather.getText();
 //            System.out.println(text.getContent());
 //        }
 //    }
-//
-//
-//
-//    //@Scheduled(cron = "0 0/30 * * * ?")// 每10执行一次
-//    public void expire(){
-//
-//        aiService.expireAiMsg();
-//    }
-//
 //}

BIN
fs-qw-api-msg/src/main/resources/jniLibs/WeWorkFinanceSdk.dll


BIN
fs-qw-api-msg/src/main/resources/jniLibs/libcrypto-3-x64.dll


BIN
fs-qw-api-msg/src/main/resources/jniLibs/libcurl-x64.dll


+ 17 - 0
fs-qw-api/pom.xml

@@ -99,7 +99,24 @@
             <groupId>com.fs</groupId>
             <artifactId>fs-service-system</artifactId>
         </dependency>
+        <!-- 获取音频信息 -->
+        <dependency>
+            <groupId>org</groupId>
+            <artifactId>jaudiotagger</artifactId>
+            <version>2.0.3</version>
+        </dependency>
 
+        <!-- 语音识别 -->
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna</artifactId>
+            <version>5.7.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alphacephei</groupId>
+            <artifactId>vosk</artifactId>
+            <version>0.3.32</version>
+        </dependency>
 
 
     </dependencies>

+ 304 - 0
fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java

@@ -1,9 +1,11 @@
 package com.fs.app.service;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.company.service.ICompanyConfigService;
 import com.fs.qw.domain.*;
+import com.fs.qw.mapper.QwCompanyMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.param.QwAutoRulesTagsParams;
 import com.fs.qw.service.*;
@@ -12,17 +14,33 @@ import com.fs.qwApi.Result.QwOpenidResult;
 import com.fs.qwApi.domain.QwResult;
 import com.fs.qwApi.param.QwEditUserTagParam;
 import com.fs.qwApi.param.QwOpenidByUserParams;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.tencent.wework.Finance;
+import lombok.extern.slf4j.Slf4j;
+import org.json.JSONObject;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.vosk.LibVosk;
+import org.vosk.LogLevel;
+import org.vosk.Model;
+import org.vosk.Recognizer;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 
+import javax.crypto.Cipher;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
 import java.text.SimpleDateFormat;
 import java.util.*;
 
+@Slf4j
 @Service
 public class QwDataCallbackService {
 
@@ -55,6 +73,10 @@ public class QwDataCallbackService {
 
     @Autowired
     private IQwDeptService qwDeptService;
+    @Autowired
+    private RedisCache redisCache;
+    @Autowired
+    private QwCompanyMapper qwCompanyMapper;
 
 
 
@@ -476,6 +498,288 @@ public class QwDataCallbackService {
         }
     }
 
+    /**
+     * 拉取存档内容
+     * @param
+     * @return 聊天内容列表
+     */
+    public List<String> getChatData(String corpId) {
+        QwCompany qwCompany= qwCompanyMapper.selectQwCompanyByCorpId(corpId);
+        // 初始化 SDK
+        long sdk = Finance.NewSdk();
+        long slice = Finance.NewSlice();
+        List<String> chatContents = new ArrayList<>();
+        try {
+            long seq = 0L;
+            if (redisCache.redisTemplate.hasKey("seq:"+corpId+":")) {
+                seq  = redisCache.getCacheObject("seq:"+corpId+":");
+            }
+            System.out.println("seq:"+seq);
+            // SDK 初始化参数
+            String secret = qwCompany.getMsgSecret();
+            long limit = 1000L;  // 一次最多拉取 1000 条
+            String proxy = "";
+            String passwd = "";
+            long timeout = 15L;
+            long ret = Finance.Init(sdk, corpId, secret);
+            if (ret != 0) {
+                Finance.DestroySdk(sdk);
+                System.out.println("初始化 SDK 失败,错误码1:" + ret);
+                return chatContents;
+            }
+            // 拉取会话存档数据
+            ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
+            if (ret != 0) {
+                System.out.println("拉取会话存档失败,错误码:" + ret);
+                return chatContents;
+            }
+            // 获取 JSON 内容
+            String jsonContent = Finance.GetContentFromSlice(slice);
+            if (jsonContent == null || jsonContent.isEmpty()) {
+                System.out.println("没有获取到有效的会话存档数据");
+                return chatContents;
+            }
+            // 解析 JSON 内容
+            JSONObject json = new JSONObject(jsonContent);
+            System.out.println("解析的内容"+json.toString());
+            if (json.has("chatdata")) {
+                for (Object item : json.getJSONArray("chatdata")) {
+                    JSONObject chatItem = (JSONObject) item;
+                    String encrypt_random_key = chatItem.getString("encrypt_random_key"); // 获取加密的随机密钥
+                    String encrypt_chat_msg = chatItem.getString("encrypt_chat_msg"); // 获取加密消息
+                    // 更新下一次拉取的 seq
+                    if (chatItem.has("seq")) {
+                        long nextSeq = chatItem.getLong("seq");
+                        System.out.println("将 seq 存入 Redis,key: seq:, value: " + nextSeq);
+                        redisCache.setCacheObject("seq:"+corpId+":", nextSeq); // 确保 `seq` 更新到缓存
+                    }
+                    // 使用私钥解密 encrypt_random_key 获取 encrypt_key
+                    String encrypt_key = decryptRandomKey(encrypt_random_key,qwCompany.getMsgPrivateKey());
+                    if (encrypt_key != null) {
+                        // 解密加密消息
+                        String decryptedMsg = decryptChatMessage(sdk, encrypt_key, encrypt_chat_msg);
+                        // 解析 JSON 字符串
+                        JsonObject jsonObject = JsonParser.parseString(decryptedMsg).getAsJsonObject();
+                        String plaintext = Finance.GetContentFromSlice(slice);
+                        // 获取 msgtype 的值
+                        String msgtype = jsonObject.get("msgtype").getAsString();
+                        // 处理媒体消息
+                        if (msgtype.equals("voice")) {
+                            getMedia(sdk,decryptedMsg,corpId,secret);
+                        }
+                        System.out.println("解密后的消息:" + decryptedMsg);
+                        chatContents.add(decryptedMsg);
+                    } else {
+                        System.out.println("解密随机密钥失败");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            Finance.FreeSlice(slice);
+            Finance.DestroySdk(sdk);
+        }
+        return chatContents;
+    }
+
+    /**
+     * 解密加密的随机密钥
+     * @param encryptedRandomKey 加密的随机密钥
+     * @return 解密后的随机密钥
+     */
+    public String decryptRandomKey(String encryptedRandomKey,String msgPrivateKey) {
+        try {
+            // 加载私钥
+            PrivateKey privateKey = loadPrivateKey(msgPrivateKey);
+
+            // 使用 RSA 解密
+            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+            cipher.init(Cipher.DECRYPT_MODE, privateKey);
+
+            // 解密数据
+            byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedRandomKey));
+            return new String(decryptedBytes, StandardCharsets.UTF_8);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 获取私钥
+     * @return 私钥
+     * @throws Exception 异常
+     */
+    public PrivateKey loadPrivateKey(String privateKey) throws Exception {
+        // 解析 PEM 格式的私钥
+        String privateKeyPEM = privateKey
+                .replace("-----BEGIN PRIVATE KEY-----", "")
+                .replace("-----END PRIVATE KEY-----", "")
+                .replaceAll("\\s", "");
+        byte[] decodedKey = Base64.getDecoder().decode(privateKeyPEM);
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        return keyFactory.generatePrivate(keySpec);
+
+    }
+
+    /**
+     * 解密会话消息
+     * @param sdk SDK
+     * @param encrypt_key 解密的密钥
+     * @param encrypt_chat_msg 加密的聊天消息
+     * @return 解密后的消息内容
+     */
+    public String decryptChatMessage(long sdk, String encrypt_key, String encrypt_chat_msg) {
+        String decryptedMsg = "";
+        long msg = Finance.NewSlice();
+        try {
+            // 解密消息
+            int ret = Finance.DecryptData(sdk, encrypt_key, encrypt_chat_msg, msg);
+            if (ret == 0) {
+                decryptedMsg = Finance.GetContentFromSlice(msg); // 获取解密后的消息内容
+                decryptedMsg = new String(decryptedMsg.getBytes(), StandardCharsets.UTF_8);
+            } else {
+                System.out.println("解密失败,错误码:" + ret);
+                return null;  // 返回 null,避免上游加入错误数据
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            Finance.FreeSlice(msg);
+        }
+        return decryptedMsg;
+    }
+
+    /**
+     * 下载语音文件
+     * @param sdk SDK实例
+     * @param decryptedMsg 解密后的JSON字符串
+     * @return 转换以后的文字数据
+     */
+    public static String getMedia(long sdk, String decryptedMsg,String coreId,String secret) {
+        JsonObject jsonObject = JsonParser.parseString(decryptedMsg).getAsJsonObject();
+        if (!jsonObject.has("voice")) {
+            log.error("JSON 数据中缺少 voice 字段");
+            return null;
+        }
+        JsonObject voiceObj = jsonObject.getAsJsonObject("voice");
+        String sdkFileId = voiceObj.get("sdkfileid").getAsString();
+        long mediaData = Finance.NewMediaData();
+        byte[] amrData = downloadVoice(sdk, sdkFileId, "", mediaData,coreId,secret);
+        Finance.FreeMediaData(mediaData);
+        if (amrData == null) {
+            return null;
+        }
+        // 直接转换 AMR 数据到文本
+        try {
+            String s = convertAmrToText(amrData);
+            log.info("转换成功的文字{}",s);
+            return s;
+        } catch (IOException e) {
+            log.error("语音转换失败: {}", e.getMessage(), e);
+            return null;
+        }
+    }
 
+    /**
+     * 递归下载语音文件
+     * @param sdk SDK实例
+     * @param sdkFileId 语音文件ID
+     * @param indexbuf 获取分片数据时的索引
+     * @param mediaData 存储语音数据的结构体
+     * @return 下载的语音文件字节
+     */
+    private static byte[] downloadVoice(long sdk, String sdkFileId, String indexbuf, long mediaData, String corpId, String secret) {
+        long ret = Finance.Init(sdk, corpId, secret);
+        if (ret != 0) {
+            Finance.DestroySdk(sdk);
+            System.out.println("初始化 SDK 失败,错误码:" + ret);
+            return null;
+        }
+        ret = Finance.GetMediaData(sdk, indexbuf, sdkFileId, "", "", 15L, mediaData);
+        if (ret != 0) {
+            log.error("获取语音数据失败: ret={}, sdkFileId={}", ret, sdkFileId);
+            return null;
+        }
+        boolean isFinish = Finance.IsMediaDataFinish(mediaData) == 1;
+        String nextIndexBuf = Finance.GetOutIndexBuf(mediaData);
+        byte[] data = Finance.GetData(mediaData);
+        if (!isFinish) {
+            byte[] nextData = downloadVoice(sdk, sdkFileId, nextIndexBuf, mediaData,corpId,secret);
+            if (nextData != null) {
+                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+                try {
+                    outputStream.write(data);
+                    outputStream.write(nextData);
+                    return outputStream.toByteArray();
+                } catch (IOException e) {
+                    log.error("合并语音数据失败: {}", e.getMessage(), e);
+                    return null;
+                }
+            }
+        }
+        return data;
+    }
+
+    /**
+     * 使用 ffmpeg 将 AMR 文件转换为 WAV 格式
+     * @return WAV 格式的音频数据
+     * @throws IOException
+     */
+    private static String convertAmrToText(byte[] amrData) throws IOException {
+        // 启动 ffmpeg 进行转换
+        ProcessBuilder processBuilder = new ProcessBuilder(
+                "ffmpeg",
+                "-i", "pipe:0",  // 从标准输入读取 AMR 数据
+                "-ac", "1",
+                "-ar", "16000",
+                "-sample_fmt", "s16",
+                "-f", "wav",
+                "pipe:1"
+        );
+        processBuilder.redirectErrorStream(true);
+        Process process = processBuilder.start();
+        // 向 ffmpeg 传输 AMR 数据
+        try (OutputStream outputStream = process.getOutputStream()) {
+            outputStream.write(amrData);
+        }
+        // 读取 ffmpeg 转换后的 WAV 数据
+        ByteArrayOutputStream wavOutput = new ByteArrayOutputStream();
+        try (InputStream inputStream = process.getInputStream()) {
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                wavOutput.write(buffer, 0, bytesRead);
+            }
+            int exitCode = process.waitFor();
+            if (exitCode != 0) {
+                throw new IOException("ffmpeg 转换失败,退出码:" + exitCode);
+            }
+        } catch (InterruptedException e) {
+            throw new IOException("ffmpeg 转换过程中被中断", e);
+        }
+        return recognizeSpeech(wavOutput.toByteArray());
+    }
+
+    /**
+     * 使用 Vosk 识别音频
+     */
+    private static String recognizeSpeech(byte[] wavData) throws IOException {
+        LibVosk.setLogLevel(LogLevel.DEBUG);
+        try (Model model = new Model("C:/vosk-model-small-cn-0.22");
+             //try (Model model = new Model("E:/vosk-model/vosk-model-small-cn-0.22");
+             InputStream ais = new ByteArrayInputStream(wavData);
+             Recognizer recognizer = new Recognizer(model, 16000)) {
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = ais.read(buffer)) >= 0) {
+                recognizer.acceptWaveForm(buffer, bytesRead);
+            }
+            return recognizer.getFinalResult();
+        }
+    }
 
 }

+ 3 - 3
fs-qw-api/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java → fs-qw-api/src/main/java/com/fs/core/aspectj/DataScopeAspect.java

@@ -1,4 +1,4 @@
-package com.fs.framework.aspectj;
+package com.fs.core.aspectj;
 
 import com.fs.common.annotation.DataScope;
 import com.fs.common.core.domain.BaseEntity;
@@ -7,8 +7,8 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.company.domain.CompanyRole;
 import com.fs.company.domain.CompanyUser;
-import com.fs.framework.security.LoginUser;
-import com.fs.framework.service.TokenService;
+import com.fs.core.security.LoginUser;
+import com.fs.core.service.TokenService;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.Signature;
 import org.aspectj.lang.annotation.Aspect;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/aspectj/DataSourceAspect.java → fs-qw-api/src/main/java/com/fs/core/aspectj/DataSourceAspect.java

@@ -1,8 +1,8 @@
-package com.fs.framework.aspectj;
+package com.fs.core.aspectj;
 
 import com.fs.common.annotation.DataSource;
 import com.fs.common.utils.StringUtils;
-import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.core.datasource.DynamicDataSourceContextHolder;
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;

+ 5 - 5
fs-qw-api/src/main/java/com/fs/framework/aspectj/LogAspect.java → fs-qw-api/src/main/java/com/fs/core/aspectj/LogAspect.java

@@ -1,4 +1,4 @@
-package com.fs.framework.aspectj;
+package com.fs.core.aspectj;
 
 import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.Log;
@@ -9,10 +9,10 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.company.domain.CompanyOperLog;
-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.core.manager.AsyncManager;
+import com.fs.core.manager.factory.AsyncFactory;
+import com.fs.core.security.LoginUser;
+import com.fs.core.service.TokenService;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.Signature;
 import org.aspectj.lang.annotation.AfterReturning;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/aspectj/RateLimiterAspect.java → fs-qw-api/src/main/java/com/fs/core/aspectj/RateLimiterAspect.java

@@ -1,4 +1,4 @@
-package com.fs.framework.aspectj;
+package com.fs.core.aspectj;
 
 import com.fs.common.annotation.RateLimiter;
 import com.fs.common.enums.LimitType;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/ApplicationConfig.java → fs-qw-api/src/main/java/com/fs/core/config/ApplicationConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/ArrayStringTypeHandler.java → fs-qw-api/src/main/java/com/fs/core/config/ArrayStringTypeHandler.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import org.apache.ibatis.type.BaseTypeHandler;
 import org.apache.ibatis.type.JdbcType;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/CaptchaConfig.java → fs-qw-api/src/main/java/com/fs/core/config/CaptchaConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.google.code.kaptcha.impl.DefaultKaptcha;
 import com.google.code.kaptcha.util.Config;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/config/DataSourceConfig.java → fs-qw-api/src/main/java/com/fs/core/config/DataSourceConfig.java

@@ -1,10 +1,10 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.alibaba.druid.pool.DruidDataSource;
 import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
 import com.alibaba.druid.util.Utils;
 import com.fs.common.enums.DataSourceType;
-import com.fs.framework.datasource.DynamicDataSource;
+import com.fs.core.datasource.DynamicDataSource;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.ConfigurationProperties;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java → fs-qw-api/src/main/java/com/fs/core/config/FastJson2JsonRedisSerializer.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.parser.ParserConfig;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/FilterConfig.java → fs-qw-api/src/main/java/com/fs/core/config/FilterConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.filter.RepeatableFilter;
 import com.fs.common.filter.XssFilter;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/KaptchaTextCreator.java → fs-qw-api/src/main/java/com/fs/core/config/KaptchaTextCreator.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.google.code.kaptcha.text.impl.DefaultTextCreator;
 

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/MyBatisConfig.java → fs-qw-api/src/main/java/com/fs/core/config/MyBatisConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
 import org.apache.ibatis.io.VFS;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/RedisConfig.java → fs-qw-api/src/main/java/com/fs/core/config/RedisConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/config/ResourcesConfig.java → fs-qw-api/src/main/java/com/fs/core/config/ResourcesConfig.java

@@ -1,8 +1,8 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.config.FSConfig;
 import com.fs.common.constant.Constants;
-import com.fs.framework.interceptor.RepeatSubmitInterceptor;
+import com.fs.core.interceptor.RepeatSubmitInterceptor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;

+ 4 - 4
fs-qw-api/src/main/java/com/fs/framework/config/SecurityConfig.java → fs-qw-api/src/main/java/com/fs/core/config/SecurityConfig.java

@@ -1,9 +1,9 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 
-import com.fs.framework.security.filter.JwtAuthenticationTokenFilter;
-import com.fs.framework.security.handle.AuthenticationEntryPointImpl;
-import com.fs.framework.security.handle.LogoutSuccessHandlerImpl;
+import com.fs.core.security.filter.JwtAuthenticationTokenFilter;
+import com.fs.core.security.handle.AuthenticationEntryPointImpl;
+import com.fs.core.security.handle.LogoutSuccessHandlerImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.http.HttpMethod;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/ServerConfig.java → fs-qw-api/src/main/java/com/fs/core/config/ServerConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.utils.ServletUtils;
 import org.springframework.stereotype.Component;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/SwaggerConfig.java → fs-qw-api/src/main/java/com/fs/core/config/SwaggerConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.config.FSConfig;
 import io.swagger.annotations.ApiOperation;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/ThreadPoolConfig.java → fs-qw-api/src/main/java/com/fs/core/config/ThreadPoolConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.utils.Threads;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/config/properties/DruidProperties.java → fs-qw-api/src/main/java/com/fs/core/config/properties/DruidProperties.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config.properties;
+package com.fs.core.config.properties;
 
 import com.alibaba.druid.pool.DruidDataSource;
 import org.springframework.beans.factory.annotation.Value;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/datasource/DynamicDataSource.java → fs-qw-api/src/main/java/com/fs/core/datasource/DynamicDataSource.java

@@ -1,4 +1,4 @@
-package com.fs.framework.datasource;
+package com.fs.core.datasource;
 
 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java → fs-qw-api/src/main/java/com/fs/core/datasource/DynamicDataSourceContextHolder.java

@@ -1,4 +1,4 @@
-package com.fs.framework.datasource;
+package com.fs.core.datasource;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/exception/GlobalExceptionHandler.java → fs-qw-api/src/main/java/com/fs/core/exception/GlobalExceptionHandler.java

@@ -1,4 +1,4 @@
-package com.fs.framework.exception;
+package com.fs.core.exception;
 
 import com.fs.common.constant.HttpStatus;
 import com.fs.common.core.domain.AjaxResult;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/interceptor/RepeatSubmitInterceptor.java → fs-qw-api/src/main/java/com/fs/core/interceptor/RepeatSubmitInterceptor.java

@@ -1,4 +1,4 @@
-package com.fs.framework.interceptor;
+package com.fs.core.interceptor;
 
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.RepeatSubmit;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/interceptor/impl/SameUrlDataInterceptor.java → fs-qw-api/src/main/java/com/fs/core/interceptor/impl/SameUrlDataInterceptor.java

@@ -1,4 +1,4 @@
-package com.fs.framework.interceptor.impl;
+package com.fs.core.interceptor.impl;
 
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.constant.Constants;
@@ -6,7 +6,7 @@ import com.fs.common.core.redis.RedisCache;
 import com.fs.common.filter.RepeatedlyRequestWrapper;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.http.HttpHelper;
-import com.fs.framework.interceptor.RepeatSubmitInterceptor;
+import com.fs.core.interceptor.RepeatSubmitInterceptor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/manager/AsyncManager.java → fs-qw-api/src/main/java/com/fs/core/manager/AsyncManager.java

@@ -1,4 +1,4 @@
-package com.fs.framework.manager;
+package com.fs.core.manager;
 
 import com.fs.common.utils.Threads;
 import com.fs.common.utils.spring.SpringUtils;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/manager/ShutdownManager.java → fs-qw-api/src/main/java/com/fs/core/manager/ShutdownManager.java

@@ -1,4 +1,4 @@
-package com.fs.framework.manager;
+package com.fs.core.manager;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java → fs-qw-api/src/main/java/com/fs/core/manager/factory/AsyncFactory.java

@@ -1,4 +1,4 @@
-package com.fs.framework.manager.factory;
+package com.fs.core.manager.factory;
 
 import com.fs.common.constant.Constants;
 import com.fs.common.utils.LogUtils;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/security/LoginBody.java → fs-qw-api/src/main/java/com/fs/core/security/LoginBody.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security;
+package com.fs.core.security;
 
 /**
  * 用户登录对象

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/security/LoginUser.java → fs-qw-api/src/main/java/com/fs/core/security/LoginUser.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security;
+package com.fs.core.security;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fs.company.domain.Company;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/security/SecurityUtils.java → fs-qw-api/src/main/java/com/fs/core/security/SecurityUtils.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security;
+package com.fs.core.security;
 
 import com.fs.common.constant.HttpStatus;
 import com.fs.common.exception.CustomException;

+ 4 - 4
fs-qw-api/src/main/java/com/fs/framework/security/filter/JwtAuthenticationTokenFilter.java → fs-qw-api/src/main/java/com/fs/core/security/filter/JwtAuthenticationTokenFilter.java

@@ -1,10 +1,10 @@
-package com.fs.framework.security.filter;
+package com.fs.core.security.filter;
 
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.StringUtils;
-import com.fs.framework.security.LoginUser;
-import com.fs.framework.security.SecurityUtils;
-import com.fs.framework.service.TokenService;
+import com.fs.core.security.LoginUser;
+import com.fs.core.security.SecurityUtils;
+import com.fs.core.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextHolder;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/security/handle/AuthenticationEntryPointImpl.java → fs-qw-api/src/main/java/com/fs/core/security/handle/AuthenticationEntryPointImpl.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security.handle;
+package com.fs.core.security.handle;
 
 import com.alibaba.fastjson.JSON;
 import com.fs.common.constant.HttpStatus;

+ 5 - 5
fs-qw-api/src/main/java/com/fs/framework/security/handle/LogoutSuccessHandlerImpl.java → fs-qw-api/src/main/java/com/fs/core/security/handle/LogoutSuccessHandlerImpl.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security.handle;
+package com.fs.core.security.handle;
 
 import com.alibaba.fastjson.JSON;
 import com.fs.common.constant.Constants;
@@ -6,10 +6,10 @@ import com.fs.common.constant.HttpStatus;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
-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.core.manager.AsyncManager;
+import com.fs.core.manager.factory.AsyncFactory;
+import com.fs.core.security.LoginUser;
+import com.fs.core.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.core.Authentication;

+ 4 - 4
fs-qw-api/src/main/java/com/fs/framework/service/CompanyLoginService.java → fs-qw-api/src/main/java/com/fs/core/service/CompanyLoginService.java

@@ -1,4 +1,4 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 import com.fs.common.constant.Constants;
 import com.fs.common.core.redis.RedisCache;
@@ -7,9 +7,9 @@ import com.fs.common.exception.user.CaptchaException;
 import com.fs.common.exception.user.CaptchaExpireException;
 import com.fs.common.exception.user.UserPasswordNotMatchException;
 import com.fs.common.utils.MessageUtils;
-import com.fs.framework.manager.AsyncManager;
-import com.fs.framework.manager.factory.AsyncFactory;
-import com.fs.framework.security.LoginUser;
+import com.fs.core.manager.AsyncManager;
+import com.fs.core.manager.factory.AsyncFactory;
+import com.fs.core.security.LoginUser;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;

+ 1 - 1
fs-qw-api/src/main/java/com/fs/framework/service/CompanyPermissionService.java → fs-qw-api/src/main/java/com/fs/core/service/CompanyPermissionService.java

@@ -1,4 +1,4 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyMenuService;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/service/PermissionService.java → fs-qw-api/src/main/java/com/fs/core/service/PermissionService.java

@@ -1,9 +1,9 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.CompanyRole;
-import com.fs.framework.security.LoginUser;
+import com.fs.core.security.LoginUser;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/service/TokenService.java → fs-qw-api/src/main/java/com/fs/core/service/TokenService.java

@@ -1,4 +1,4 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 import com.fs.common.constant.Constants;
 import com.fs.common.core.redis.RedisCache;
@@ -7,7 +7,7 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.ip.AddressUtils;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.common.utils.uuid.IdUtils;
-import com.fs.framework.security.LoginUser;
+import com.fs.core.security.LoginUser;
 import eu.bitwalker.useragentutils.UserAgent;
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.Jwts;

+ 2 - 2
fs-qw-api/src/main/java/com/fs/framework/service/UserDetailsServiceImpl.java → fs-qw-api/src/main/java/com/fs/core/service/UserDetailsServiceImpl.java

@@ -1,4 +1,4 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 
 import com.fs.common.enums.UserStatus;
@@ -8,7 +8,7 @@ import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
-import com.fs.framework.security.LoginUser;
+import com.fs.core.security.LoginUser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;

+ 128 - 0
fs-qw-api/src/main/java/com/tencent/wework/Finance.java

@@ -0,0 +1,128 @@
+package com.tencent.wework;
+
+/* sdk
+typedef struct Slice_t {
+    char* buf;
+    int len;
+} Slice_t;
+
+typedef struct MediaData {
+    char* outindexbuf;
+    int out_len;
+    char* data;    
+    int data_len;
+    int is_finish;
+} MediaData_t;
+*/
+
+public class Finance {
+    public native static long NewSdk();
+
+    /**
+     * ��ʼ������
+     * Returnֵ=0��ʾ��API���óɹ�
+     *
+     * @param [in] sdk			NewSdk���ص�sdkָ��
+     * @param [in] corpid      ������ҵ����ҵid�����磺wwd08c8exxxx5ab44d����������ҵ΢�Ź����--�ҵ���ҵ--��ҵ��Ϣ�鿴
+     * @param [in] secret		�������ݴ浵��Secret����������ҵ΢�Ź����--������--�������ݴ浵�鿴
+     * @return �����Ƿ��ʼ���ɹ�
+     * 0   - �ɹ�
+     * !=0 - ʧ��
+     */
+    public native static int Init(long sdk, String corpid, String secret);
+
+    /**
+     * ��ȡ�����¼����
+     * Returnֵ=0��ʾ��API���óɹ�
+     *
+     * @param [in]  sdk				NewSdk���ص�sdkָ��
+     * @param [in]  seq				��ָ����seq��ʼ��ȡ��Ϣ��ע����Ƿ��ص���Ϣ��seq+1��ʼ���أ�seqΪ֮ǰ�ӿڷ��ص����seqֵ���״�ʹ����ʹ��seq:0
+     * @param [in]  limit			һ����ȡ����Ϣ���������ֵ1000��������1000���᷵�ش���
+     * @param [in]  proxy			ʹ�ô����������Ҫ�����������ӡ��磺socks5://10.0.0.1:8081 ���� http://10.0.0.1:8081
+     * @param [in]  passwd			�����˺����룬��Ҫ���������˺����롣�� user_name:passwd_123
+     * @param [out] chatDatas		���ر�����ȡ��Ϣ�����ݣ�slice�ṹ��.���ݰ���errcode/errmsg���Լ�ÿ����Ϣ���ݡ�
+     * @return �����Ƿ���óɹ�
+     * 0   - �ɹ�
+     * !=0 - ʧ��
+     */
+    public native static int GetChatData(long sdk, long seq, long limit, String proxy, String passwd, long timeout, long chatData);
+
+    /**
+     * ��ȡý����Ϣ����
+     * Returnֵ=0��ʾ��API���óɹ�
+     *
+     * @param [in]  sdk				NewSdk���ص�sdkָ��
+     * @param [in]  sdkFileid		��GetChatData���ص�������Ϣ�У�ý����Ϣ������sdkfileid
+     * @param [in]  proxy			ʹ�ô����������Ҫ�����������ӡ��磺socks5://10.0.0.1:8081 ���� http://10.0.0.1:8081
+     * @param [in]  passwd			�����˺����룬��Ҫ���������˺����롣�� user_name:passwd_123
+     * @param [in]  indexbuf		ý����Ϣ��Ƭ��ȡ����Ҫ����ÿ����ȡ��������Ϣ���״β���Ҫ��д��Ĭ����ȡ512k������ÿ�ε���ֻ��Ҫ���ϴε��÷��ص�outindexbuf���뼴�ɡ�
+     * @param [out] media_data		���ر�����ȡ��ý������.MediaData�ṹ��.���ݰ���data(��������)/outindexbuf(�´�����)/is_finish(��ȡ��ɱ��)
+     * @return �����Ƿ���óɹ�
+     * 0   - �ɹ�
+     * !=0 - ʧ��
+     */
+    public native static int GetMediaData(long sdk, String indexbuf, String sdkField, String proxy, String passwd, long timeout, long mediaData);
+
+    /**
+     * @param [in]  encrypt_key, getchatdata���ص�encrypt_key
+     * @param [in]  encrypt_msg, getchatdata���ص�content
+     * @param [out] msg, ���ܵ���Ϣ����
+     * @return �����Ƿ���óɹ�
+     * 0   - �ɹ�
+     * !=0 - ʧ��
+     * @brief ��������
+     */
+    public native static int DecryptData(long sdk, String encrypt_key, String encrypt_msg, long msg);
+
+    public native static void DestroySdk(long sdk);
+
+    public native static long NewSlice();
+
+    /**
+     * @return
+     * @brief �ͷ�slice����NewSlice�ɶ�ʹ��
+     */
+    public native static void FreeSlice(long slice);
+
+    /**
+     * @return ����
+     * @brief ��ȡslice����
+     */
+    public native static String GetContentFromSlice(long slice);
+
+    /**
+     * @return ����
+     * @brief ��ȡslice���ݳ���
+     */
+    public native static int GetSliceLen(long slice);
+
+    public native static long NewMediaData();
+
+    public native static void FreeMediaData(long mediaData);
+
+    /**
+     * @return outindex
+     * @brief ��ȡmediadata outindex
+     */
+    public native static String GetOutIndexBuf(long mediaData);
+
+    /**
+     * @return data
+     * @brief ��ȡmediadata data����
+     */
+    public native static byte[] GetData(long mediaData);
+
+    public native static int GetIndexLen(long mediaData);
+
+    public native static int GetDataLen(long mediaData);
+
+    /**
+     * @return 1��ɡ�0δ���
+     * @brief �ж�mediadata�Ƿ����
+     */
+    public native static int IsMediaDataFinish(long mediaData);
+
+    static {
+        System.loadLibrary("WeWorkFinanceSdk");
+    }
+}

BIN
fs-qw-api/src/main/resources/jniLibs/WeWorkFinanceSdk.dll


BIN
fs-qw-api/src/main/resources/jniLibs/libcrypto-3-x64.dll


BIN
fs-qw-api/src/main/resources/jniLibs/libcurl-x64.dll


+ 1 - 1
fs-qw-api/src/main/resources/mybatis/mybatis-config.xml

@@ -13,7 +13,7 @@ PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 	</settings>
 
 	<typeHandlers>
-		<typeHandler handler="com.fs.framework.config.ArrayStringTypeHandler"/>
+		<typeHandler handler="com.fs.core.config.ArrayStringTypeHandler"/>
 	</typeHandlers>
 
 </configuration>

+ 16 - 1
fs-service-system/src/main/java/com/fs/fastGpt/service/AiHookService.java

@@ -3,6 +3,7 @@ package com.fs.fastGpt.service;
 import com.fs.common.core.domain.R;
 import com.fs.qw.vo.QwMessageListVO;
 import com.fs.qwHookApi.vo.QwHookVO;
+import com.fs.wxwork.dto.WxWorkResponseDTO;
 
 public interface AiHookService {
     /** 发送ai提醒 **/
@@ -27,6 +28,20 @@ public interface AiHookService {
      * @param uuid     UUID
      * @param sendType 发送者类型 1用户 2客服
      * @param json     消息json
+     * @param msgType  消息类型 1文本 2图片
      */
-    QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json);
+    QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json, int msgType);
+
+    /**
+     * 获取文件地址
+     * @param uuid      uuid
+     * @param fileId    fileId
+     * @param aesKey    aesKey
+     * @param authKey   authKey
+     * @param fileName  fileName
+     * @param fileSize  size
+     * @param serverId  serverId
+     * @return  WxWorkResponseDTO
+     */
+    WxWorkResponseDTO<String> getFileUrl(String uuid, String fileId, String aesKey, String authKey, String fileName, Integer fileSize, Long serverId);
 }

+ 31 - 3
fs-service-system/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -1269,10 +1269,11 @@ public class AiHookServiceImpl implements AiHookService {
      * @param uuid     UUID
      * @param sendType 发送者类型 1用户 2客服
      * @param json     消息json
+     * @param msgType  消息类型 1文本 2图片
      */
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json) {
+    public QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json, int msgType) {
         // 查询企微用户
         QwUser qwUser = qwUserService.selectQwUserById(qwUserId);
         if (Objects.isNull(qwUser)){
@@ -1318,7 +1319,7 @@ public class AiHookServiceImpl implements AiHookService {
         qwMsg.setSendType(sendType);
         qwMsg.setCompanyId(qwUser.getCompanyId());
         qwMsg.setCompanyUserId(qwUser.getCompanyUserId());
-        qwMsg.setMsgType(1);
+        qwMsg.setMsgType(msgType);
         qwMsg.setMsgJson(json);
         qwMsg.setStatus(0);
         qwMsg.setQwUserId(qwSession.getQwUserId());
@@ -1343,7 +1344,11 @@ public class AiHookServiceImpl implements AiHookService {
         }
 
         listVO.setCompanyId(qwUser.getCompanyId());
-        listVO.setType("text");
+        String type = "text";
+        if (msgType == 2) {
+            type = "image";
+        }
+        listVO.setType(type);
         listVO.setStatus("succeed");
         listVO.setFromUser(qwFromUser);
         listVO.setSendTime(qwMsg.getCreateTime().getTime());
@@ -1353,6 +1358,29 @@ public class AiHookServiceImpl implements AiHookService {
         return listVO;
     }
 
+    /**
+     * 获取文件地址
+     * @param fileId    fileId
+     * @param uuid      uuid
+     * @param aesKey    aesKey
+     * @param authKey   authKey
+     * @param fileName  fileName
+     * @param fileSize  size
+     * @param serverId  serverId
+     * @return  WxWorkResponseDTO
+     */
+    @Override
+    public WxWorkResponseDTO<String> getFileUrl(String uuid, String fileId, String aesKey, String authKey, String fileName, Integer fileSize, Long serverId) {
+        WxwDownloadWeChatFileDTO weChatFileDTO = new WxwDownloadWeChatFileDTO();
+        weChatFileDTO.setUuid(uuid);
+        weChatFileDTO.setUrl(fileId);
+        weChatFileDTO.setAes_key(aesKey);
+        weChatFileDTO.setAuth_key(authKey);
+        weChatFileDTO.setFile_name(fileName);
+        weChatFileDTO.setSize(fileSize);
+        return wxWorkService.downloadWeChatFile(weChatFileDTO, serverId);
+    }
+
     /**
      * 查询外部联系人
      * @param userId    用户ID

+ 2 - 1
fs-service-system/src/main/java/com/fs/qw/param/QwMsgSendParam.java

@@ -9,5 +9,6 @@ public class QwMsgSendParam implements Serializable {
     private Long sessionId;
     private String content;//内容
     private String appKey;
-
+    // 消息类型 1文本 2图片
+    private Integer msgType;
 }

+ 88 - 47
fs-service-system/src/main/java/com/fs/qw/service/impl/QwMsgServiceImpl.java

@@ -256,6 +256,10 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
             return R.error("会话ID不能为空");
         }
 
+        if (Objects.isNull(param.getMsgType())) {
+            return R.error("消息类型不能为空");
+        }
+
         // 查询会话
         QwSession qwSession = qwSessionMapper.selectQwSessionBySessionId(param.getSessionId());
         if (Objects.isNull(qwSession)) {
@@ -288,56 +292,89 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
         if (listWxWorkResponseDTO.getErrcode() != 0) {
             return R.error(listWxWorkResponseDTO.getErrmsg());
         }
-
-        // 发送消息
-        WxWorkSendTextMsgDTO textMsgDTO = new WxWorkSendTextMsgDTO();
-        textMsgDTO.setUuid(uuid);
-        textMsgDTO.setSend_userid(listWxWorkResponseDTO.getData().get(0).getUser_id());
-        textMsgDTO.setIsRoom(false);
-        textMsgDTO.setContent(param.getContent());
-        WxWorkResponseDTO<WxWorkSendTextMsgRespDTO> msgRespDTOWxWorkResponseDTO = wxWorkService.SendTextMsg(textMsgDTO, serverId);
-
-        if (msgRespDTOWxWorkResponseDTO.getErrcode() != 0) {
-            return R.error(msgRespDTOWxWorkResponseDTO.getErrmsg());
+        long sendUserId = listWxWorkResponseDTO.getData().get(0).getUser_id();
+        String msgJson;
+
+        // 发送消息  文本
+        if (param.getMsgType() == 1) {
+            WxWorkSendTextMsgDTO textMsgDTO = new WxWorkSendTextMsgDTO();
+            textMsgDTO.setUuid(uuid);
+            textMsgDTO.setSend_userid(sendUserId);
+            textMsgDTO.setIsRoom(false);
+            textMsgDTO.setContent(param.getContent());
+            WxWorkResponseDTO<WxWorkSendTextMsgRespDTO> msgRespDTOWxWorkResponseDTO = wxWorkService.SendTextMsg(textMsgDTO, serverId);
+
+            if (msgRespDTOWxWorkResponseDTO.getErrcode() != 0) {
+                return R.error(msgRespDTOWxWorkResponseDTO.getErrmsg());
+            }
+            msgJson = JSONObject.toJSONString(textMsgDTO);
         }
 
-        String msg = msgRespDTOWxWorkResponseDTO.getErrmsg();
-        if ("ok".equals(msg)) {
-            // 消息保存本地数据库
-            QwMsg qwMsg = new QwMsg();
-            qwMsg.setContent(param.getContent());
-            qwMsg.setSessionId(qwSession.getSessionId());
-            qwMsg.setSendType(2);
-            qwMsg.setCompanyId(qwUser.getCompanyId());
-            qwMsg.setCompanyUserId(qwUser.getCompanyUserId());
-            qwMsg.setMsgType(1);
-            qwMsg.setMsgJson(JSONObject.toJSONString(textMsgDTO));
-            qwMsg.setStatus(0);
-            qwMsg.setQwUserId(qwSession.getQwUserId());
-            qwMsg.setQwExtId(qwSession.getQwExtId());
-            qwMsg.setAvatar(qwExternalContact.getAvatar());
-            qwMsg.setNickName(qwExternalContact.getRemark());
-            qwMsg.setCreateTime(new Date());
-            qwMsgMapper.insertQwMsg(qwMsg);
-
-            // 组装返回消息结构
-            QwMessageListVO listVO = new QwMessageListVO();
-            QWFromUser qwFromUser = new QWFromUser();
-            qwFromUser.setId(Long.parseLong(qwMsg.getQwUserId()));
-            qwFromUser.setDisplayName(qwUser.getQwUserName());
-            qwFromUser.setAvatar("https://cos.his.cdwjyyh.com/fs/20241231/22a765a96da247d1b83ea94fef438a41.png");
-
-            listVO.setType("text");
-            listVO.setStatus("succeed");
-            listVO.setFromUser(qwFromUser);
-            listVO.setSendTime(qwMsg.getCreateTime().getTime());
-            listVO.setId(qwMsg.getMsgId().toString());
-            listVO.setContent(qwMsg.getContent());
-            listVO.setToContactId(String.valueOf(param.getSessionId()));
-            return R.ok().put("data", listVO);
+        // 图片
+        else if (param.getMsgType() == 2) {
+            WxCdnUploadImgLinkDTO linkDTO = new WxCdnUploadImgLinkDTO();
+            linkDTO.setUuid(uuid);
+            linkDTO.setUrl(param.getContent());
+            WxWorkResponseDTO<WxCdnUploadImgLinkResp> imgLinkResp = wxWorkService.cdnUploadImgLink(linkDTO, serverId);
+            if (imgLinkResp.getErrcode() != 0) {
+                return R.error(imgLinkResp.getErrmsg());
+            }
+            WxCdnUploadImgLinkResp data = imgLinkResp.getData();
+
+            // 发送图片消息
+            WxwSendCDNImgMsgDTO imgMsgDTO = new WxwSendCDNImgMsgDTO();
+            imgMsgDTO.setUuid(uuid);
+            imgMsgDTO.setSend_userid(sendUserId);
+            imgMsgDTO.setIsRoom(false);
+            imgMsgDTO.setCdnkey(data.getCdn_key());
+            imgMsgDTO.setAeskey(data.getAes_key());
+            imgMsgDTO.setMd5(data.getMd5());
+            imgMsgDTO.setFileSize(data.getSize());
+            WxWorkResponseDTO<WxwSendCDNImgMsgRespDTO> imgMsgResp = wxWorkService.SendCDNImgMsg(imgMsgDTO, serverId);
+            if (imgMsgResp.getErrcode() != 0) {
+                return R.error(imgMsgResp.getErrmsg());
+            }
+            msgJson = JSONObject.toJSONString(imgMsgDTO);
         } else {
-            return R.error(msg);
+            return R.error("暂不支持的消息类型");
+        }
+
+        // 消息保存本地数据库
+        QwMsg qwMsg = new QwMsg();
+        qwMsg.setContent(param.getContent());
+        qwMsg.setSessionId(qwSession.getSessionId());
+        qwMsg.setSendType(2);
+        qwMsg.setCompanyId(qwUser.getCompanyId());
+        qwMsg.setCompanyUserId(qwUser.getCompanyUserId());
+        qwMsg.setMsgType(param.getMsgType());
+        qwMsg.setMsgJson(msgJson);
+        qwMsg.setStatus(0);
+        qwMsg.setQwUserId(qwSession.getQwUserId());
+        qwMsg.setQwExtId(qwSession.getQwExtId());
+        qwMsg.setAvatar(qwExternalContact.getAvatar());
+        qwMsg.setNickName(qwExternalContact.getRemark());
+        qwMsg.setCreateTime(new Date());
+        qwMsgMapper.insertQwMsg(qwMsg);
+
+        // 组装返回消息结构
+        QwMessageListVO listVO = new QwMessageListVO();
+        QWFromUser qwFromUser = new QWFromUser();
+        qwFromUser.setId(Long.parseLong(qwMsg.getQwUserId()));
+        qwFromUser.setDisplayName(qwUser.getQwUserName());
+        qwFromUser.setAvatar("https://cos.his.cdwjyyh.com/fs/20241231/22a765a96da247d1b83ea94fef438a41.png");
+
+        String type = "text";
+        if (param.getMsgType() == 2) {
+            type = "image";
         }
+        listVO.setType(type);
+        listVO.setStatus("succeed");
+        listVO.setFromUser(qwFromUser);
+        listVO.setSendTime(qwMsg.getCreateTime().getTime());
+        listVO.setId(qwMsg.getMsgId().toString());
+        listVO.setContent(qwMsg.getContent());
+        listVO.setToContactId(String.valueOf(param.getSessionId()));
+        return R.ok().put("data", listVO);
     }
 
     @Override
@@ -430,7 +467,11 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
         List<QwMessageListVO> qwMessageVOS = new ArrayList<>();
         for (QwMsg record : list) {
             QwMessageListVO listVO = new QwMessageListVO();
-            listVO.setType("text");
+            String type = "text";
+            if (record.getMsgType() == 2) {
+                type = "image";
+            }
+            listVO.setType(type);
             listVO.setStatus("succeed");
             QWFromUser qwFromUser = new QWFromUser();
             //用户发送

+ 15 - 0
fs-service-system/src/main/java/com/fs/wxwork/dto/WxCdnUploadImgLinkDTO.java

@@ -0,0 +1,15 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxCdnUploadImgLinkDTO {
+    /**
+     * 用户UUID
+     */
+    private String uuid;
+    /**
+     * 图片地址
+     */
+    private String url;
+}

+ 30 - 0
fs-service-system/src/main/java/com/fs/wxwork/dto/WxCdnUploadImgLinkResp.java

@@ -0,0 +1,30 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxCdnUploadImgLinkResp {
+    private String cdn_key;
+    private String fileid;
+    //加密key
+    private String aes_key;
+    //md5
+    private String md5;
+    //宽度
+    private Integer width;
+    //高度
+    private Integer height;
+    //大小
+    private Integer size;
+    //中图大小
+    private Integer mid_image_size;
+    //缩略图宽度
+    private Integer thumb_image_width;
+    //缩略图高度
+    private Integer thumb_image_height;
+    //缩略图大小
+    private Integer thumb_file_size;
+    //缩略图md5
+    private String thumb_file_md5;
+    private String retcode;
+}

+ 31 - 0
fs-service-system/src/main/java/com/fs/wxwork/dto/WxwDownloadWeChatFileDTO.java

@@ -0,0 +1,31 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxwDownloadWeChatFileDTO {
+    /**
+     * 对应uuid
+     */
+    private String uuid;
+    /**
+     * 对应file_id
+     */
+    private String url;
+    /**
+     * 对应aes_key
+     */
+    private String aes_key;
+    /**
+     * 对应 openim_cdn_authkey
+     */
+    private String auth_key;
+    /**
+     * 文件名称
+     */
+    private String file_name;
+    /**
+     * 文件大小
+     */
+    private Integer size;
+}

+ 16 - 0
fs-service-system/src/main/java/com/fs/wxwork/service/WxWorkService.java

@@ -213,4 +213,20 @@ public interface WxWorkService {
     WxWorkResponseDTO<WxwUploadCdnLinkFileRespDTO>  uploadCdnLinkFile(WxwUploadCdnLinkFileDTO param, Long serverId);
 
     WxwSilkVoceDTO  getSilkVoice(String param, Long companyUserId);
+
+    /**
+     * 外部联系人图片视频文件下载
+     * @param param    参数
+     * @param serverId 服务器ID
+     * @return  WxWorkResponseDTO
+     */
+    WxWorkResponseDTO<String> downloadWeChatFile(WxwDownloadWeChatFileDTO param, Long serverId);
+
+    /**
+     * CDN上传网络图片
+     * @param param     参数
+     * @param serverId  服务器ID
+     * @return  WxWorkResponseDTO
+     */
+    WxWorkResponseDTO<WxCdnUploadImgLinkResp> cdnUploadImgLink(WxCdnUploadImgLinkDTO param, Long serverId);
 }

+ 25 - 0
fs-service-system/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java

@@ -278,4 +278,29 @@ public class WxWorkServiceImpl implements WxWorkService {
         String url = getUrl(serverId) + "/UserId2Vid";
         return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>>>() {});
     }
+
+    /**
+     * 外部联系人图片视频文件下载
+     * @param param    参数
+     * @param serverId 服务器ID
+     * @return  WxWorkResponseDTO
+     */
+    @Override
+    public WxWorkResponseDTO<String> downloadWeChatFile(WxwDownloadWeChatFileDTO param, Long serverId) {
+        String url = getUrl(serverId) + "/DownloadWeChatFile";
+        return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<String>>() {});
+    }
+
+    /**
+     * CDN上传网络图片
+     * @param param     参数
+     * @param serverId  服务器ID
+     * @return  WxWorkResponseDTO
+     */
+    @Override
+    public WxWorkResponseDTO<WxCdnUploadImgLinkResp> cdnUploadImgLink(WxCdnUploadImgLinkDTO param, Long serverId) {
+        String url = getUrl(serverId) + "/CdnUploadImgLink";
+        return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<WxCdnUploadImgLinkResp>>() {});
+    }
+
 }