Jelajahi Sumber

1.提交腾讯在线文档存入向量知识库

jzp 3 hari lalu
induk
melakukan
6bfe82dd69
21 mengubah file dengan 754 tambahan dan 52 penghapusan
  1. 5 0
      fs-admin/src/main/java/com/fs/third/controller/TencentWordOpenApiController.java
  2. 6 0
      fs-ai-api/src/main/java/com/fs/ai/rag/controller/QdrantController.java
  3. 1 0
      fs-ai-api/src/main/java/com/fs/ai/rag/dto/QdrantPointDeleteReq.java
  4. 1 0
      fs-ai-api/src/main/java/com/fs/ai/rag/dto/QdrantPointSearchReq.java
  5. 22 4
      fs-ai-api/src/main/java/com/fs/ai/rag/service/impl/QdrantServiceImpl.java
  6. 5 0
      fs-company/src/main/java/com/fs/company/controller/third/TencentWordOpenApiController.java
  7. 15 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptChatConversation.java
  8. 2 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptRole.java
  9. 1 1
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptRoleMapper.java
  10. 452 4
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  11. 2 0
      fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java
  12. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwTagGroupService.java
  13. 14 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwTagGroupServiceImpl.java
  14. 1 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  15. 2 0
      fs-service/src/main/java/com/fs/third/service/ITencentWordService.java
  16. 199 35
      fs-service/src/main/java/com/fs/third/service/impl/TencentWordServiceImpl.java
  17. 2 2
      fs-service/src/main/java/com/fs/wxcid/dto/message/CdnUploadVideoResult.java
  18. 1 0
      fs-service/src/main/resources/db/tenant-initTable.sql
  19. 5 1
      fs-service/src/main/resources/mapper/fastGpt/FastGptRoleMapper.xml
  20. 11 0
      fs-service/src/main/resources/mapper/qw/QwTagGroupMapper.xml
  21. 5 4
      fs-service/src/main/resources/mapper/third/TencentWordDetailMapper.xml

+ 5 - 0
fs-admin/src/main/java/com/fs/third/controller/TencentWordOpenApiController.java

@@ -37,6 +37,11 @@ public class TencentWordOpenApiController extends BaseController {
         return R.ok();
         return R.ok();
     }
     }
 
 
+    @PostMapping("/syncToKnowledgeBase")
+    public R syncToKnowledgeBase(@RequestParam String fileId, @RequestParam Long knowledgeBaseId) {
+        return tencentWordService.syncToKnowledgeBase(fileId, knowledgeBaseId);
+    }
+
     @GetMapping("/authorizeCallback")
     @GetMapping("/authorizeCallback")
     public void authorizeCallback(Request request) {
     public void authorizeCallback(Request request) {
         //tencent开放平台登录回调(二维码登录等)
         //tencent开放平台登录回调(二维码登录等)

+ 6 - 0
fs-ai-api/src/main/java/com/fs/ai/rag/controller/QdrantController.java

@@ -47,6 +47,12 @@ public class QdrantController {
         return AjaxResult.success();
         return AjaxResult.success();
     }
     }
 
 
+    @PostMapping("/point/delete/filter")
+    public AjaxResult deletePointByFilter(@RequestBody QdrantPointDeleteReq req) {
+        qdrantService.deletePoints(req);
+        return AjaxResult.success();
+    }
+
     @PostMapping("/point/get")
     @PostMapping("/point/get")
     public AjaxResult getPoint(@RequestBody QdrantPointGetReq req) {
     public AjaxResult getPoint(@RequestBody QdrantPointGetReq req) {
         return AjaxResult.success(qdrantService.getPoint(req));
         return AjaxResult.success(qdrantService.getPoint(req));

+ 1 - 0
fs-ai-api/src/main/java/com/fs/ai/rag/dto/QdrantPointDeleteReq.java

@@ -8,4 +8,5 @@ import java.util.List;
 public class QdrantPointDeleteReq {
 public class QdrantPointDeleteReq {
     private String collectionName;
     private String collectionName;
     private List<Long> ids;
     private List<Long> ids;
+    private java.util.Map<String, Object> filter;
 }
 }

+ 1 - 0
fs-ai-api/src/main/java/com/fs/ai/rag/dto/QdrantPointSearchReq.java

@@ -10,5 +10,6 @@ public class QdrantPointSearchReq {
     private String collectionName;
     private String collectionName;
     private List<Float> vector;
     private List<Float> vector;
     private Integer topK;
     private Integer topK;
+    private Double scoreThreshold;
     private Map<String, Object> filter;
     private Map<String, Object> filter;
 }
 }

+ 22 - 4
fs-ai-api/src/main/java/com/fs/ai/rag/service/impl/QdrantServiceImpl.java

@@ -83,11 +83,21 @@ public class QdrantServiceImpl implements QdrantService {
 
 
     @Override
     @Override
     public void deletePoints(QdrantPointDeleteReq req) {
     public void deletePoints(QdrantPointDeleteReq req) {
-        if (req == null || StringUtils.isBlank(req.getCollectionName()) || req.getIds() == null || req.getIds().isEmpty()) {
-            throw new IllegalArgumentException("collectionName 和 ids 不能为空");
+        if (req == null || StringUtils.isBlank(req.getCollectionName())) {
+            throw new IllegalArgumentException("collectionName 不能为空");
+        }
+        boolean hasIds = req.getIds() != null && !req.getIds().isEmpty();
+        boolean hasFilter = req.getFilter() != null && !req.getFilter().isEmpty();
+        if (!hasIds && !hasFilter) {
+            throw new IllegalArgumentException("ids 和 filter 至少提供一个");
         }
         }
         Map<String, Object> body = new LinkedHashMap<>();
         Map<String, Object> body = new LinkedHashMap<>();
-        body.put("points", req.getIds());
+        if (hasIds) {
+            body.put("points", req.getIds());
+        }
+        if (hasFilter) {
+            body.put("filter", buildFilter(req.getFilter()));
+        }
         body.put("wait", true);
         body.put("wait", true);
         try {
         try {
             exchange(pointsDeleteUrl(req.getCollectionName()), Method.POST, body);
             exchange(pointsDeleteUrl(req.getCollectionName()), Method.POST, body);
@@ -128,7 +138,7 @@ public class QdrantServiceImpl implements QdrantService {
         body.put("limit", req.getTopK() == null ? 5 : req.getTopK());
         body.put("limit", req.getTopK() == null ? 5 : req.getTopK());
         body.put("with_payload", true);
         body.put("with_payload", true);
         body.put("with_vector", true);
         body.put("with_vector", true);
-        body.put("score_threshold", 0.5);
+        body.put("score_threshold", req.getScoreThreshold() != null ? req.getScoreThreshold() : 0.5);
         if (req.getFilter() != null && !req.getFilter().isEmpty()) {
         if (req.getFilter() != null && !req.getFilter().isEmpty()) {
             body.put("filter", buildFilter(req.getFilter()));
             body.put("filter", buildFilter(req.getFilter()));
         }
         }
@@ -227,6 +237,14 @@ public class QdrantServiceImpl implements QdrantService {
     }
     }
 
 
     private Map<String, Object> buildFilter(Map<String, Object> filterMap) {
     private Map<String, Object> buildFilter(Map<String, Object> filterMap) {
+        if (filterMap == null || filterMap.isEmpty()) {
+            return new LinkedHashMap<>();
+        }
+
+        if (filterMap.containsKey("must") || filterMap.containsKey("should") || filterMap.containsKey("must_not")) {
+            return filterMap;
+        }
+
         List<Map<String, Object>> must = new ArrayList<>();
         List<Map<String, Object>> must = new ArrayList<>();
         for (Map.Entry<String, Object> entry : filterMap.entrySet()) {
         for (Map.Entry<String, Object> entry : filterMap.entrySet()) {
             if (entry.getValue() == null) {
             if (entry.getValue() == null) {

+ 5 - 0
fs-company/src/main/java/com/fs/company/controller/third/TencentWordOpenApiController.java

@@ -31,6 +31,11 @@ public class TencentWordOpenApiController extends BaseController {
         return R.ok();
         return R.ok();
     }
     }
 
 
+    @PostMapping("/syncToKnowledgeBase")
+    public R syncToKnowledgeBase(@RequestParam String fileId, @RequestParam Long knowledgeBaseId) {
+        return tencentWordService.syncToKnowledgeBase(fileId, knowledgeBaseId);
+    }
+
     @GetMapping("/authorizeCallback")
     @GetMapping("/authorizeCallback")
     public void authorizeCallback(Request request) {
     public void authorizeCallback(Request request) {
         //tencent开放平台登录回调(二维码登录等)
         //tencent开放平台登录回调(二维码登录等)

+ 15 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptChatConversation.java

@@ -4,6 +4,9 @@ import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.JSONObject;
 import lombok.Data;
 import lombok.Data;
 
 
+import java.util.List;
+import java.util.Map;
+
 @Data
 @Data
 public class FastGptChatConversation {
 public class FastGptChatConversation {
     private JSONObject userInfo;
     private JSONObject userInfo;
@@ -12,4 +15,16 @@ public class FastGptChatConversation {
     private String isRepository;
     private String isRepository;
     private String userContent;
     private String userContent;
     private String aiContent;
     private String aiContent;
+    //向量知识库检索结果
+    private List<Map<String,String>> knowledgeBase;
+    //企微标签
+    /**
+     * List<Map<分组名,Map<标签名,标签id>>>
+     */
+    private List<Map<String,Map<String,String>>> tagMapList;
+
+    /**
+     *  ai实际返回的标签
+     */
+    private String tags;
 }
 }

+ 2 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptRole.java

@@ -87,4 +87,6 @@ public class FastGptRole extends BaseEntity
 
 
     //需要获取的客户信息
     //需要获取的客户信息
     private String userInfo;
     private String userInfo;
+
+    private String tagGroups;
 }
 }

+ 1 - 1
fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptRoleMapper.java

@@ -90,7 +90,7 @@ public interface FastGptRoleMapper
 
 
     @Select("select id dictValue,name dictLabel from fastgpt_role_type ")
     @Select("select id dictValue,name dictLabel from fastgpt_role_type ")
     List<OptionsVO> selectFastGptRoleType();
     List<OptionsVO> selectFastGptRoleType();
-    @Select("select r.role_id, r.role_name,t.contact_info,r.company_id, r.create_time, r.update_time, r.role_type, r.mode_config_json, r.mode, r.kf_id, r.kf_url, r.avatar, r.kf_media_id,r.reminder_words, r.bind_corp_id,r.channel_type,r.send_course_status,r.course_id,r.user_info from fastgpt_role r LEFT JOIN fastgpt_role_type t on t.id =r.role_type where role_id = #{roleId}")
+    @Select("select r.role_id, r.role_name,t.contact_info,r.company_id, r.create_time, r.update_time, r.role_type, r.mode_config_json, r.mode, r.kf_id, r.kf_url, r.avatar, r.kf_media_id,r.reminder_words, r.bind_corp_id,r.channel_type,r.send_course_status,r.course_id,r.user_info,r.tag_groups from fastgpt_role r LEFT JOIN fastgpt_role_type t on t.id =r.role_type where role_id = #{roleId}")
     FastGptRole selectFastGptRoleTypeByRoleId(Long roleId);
     FastGptRole selectFastGptRoleTypeByRoleId(Long roleId);
 
 
     List<FastGptRole> selectFastGptRoleByRoleIds(@Param("roleIds") List<Long> roleIds);
     List<FastGptRole> selectFastGptRoleByRoleIds(@Param("roleIds") List<Long> roleIds);

+ 452 - 4
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -6,13 +6,17 @@ import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONArray;
 import com.fs.common.annotation.Excel;
 import com.fs.common.annotation.Excel;
-import com.fs.common.config.FSConfig;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.PubFun;
+import com.fs.common.utils.ServletUtils;
+import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyConfig;
 import com.fs.company.domain.CompanyConfig;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyConfigMapper;
 import com.fs.company.mapper.CompanyConfigMapper;
 import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.config.ai.AiHostProper;
 import com.fs.config.ai.AiHostProper;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.domain.FsUserCourseVideo;
@@ -50,10 +54,20 @@ import com.fs.his.utils.ConfigUtil;
 import com.fs.hisStore.enums.SysConfigEnum;
 import com.fs.hisStore.enums.SysConfigEnum;
 import com.fs.im.dto.OpenImMsgDTO;
 import com.fs.im.dto.OpenImMsgDTO;
 import com.fs.im.vo.OpenImMsgCallBackVO;
 import com.fs.im.vo.OpenImMsgCallBackVO;
+import com.fs.ipad.IpadSendUtils;
+import com.fs.ipad.param.WxSendAtMsgParam;
+import com.fs.ipad.vo.WxGetSessionRoomListVo;
+import com.fs.ipad.vo.WxRoomUserListVo;
 import com.fs.qw.domain.*;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.QwAutoTagsRulesTags;
 import com.fs.qw.param.QwAutoTagsRulesTags;
+import com.fs.qw.param.QwExternalContactAddTagParam;
+import com.fs.qw.param.QwExternalContactParam;
 import com.fs.qw.service.*;
 import com.fs.qw.service.*;
+import com.fs.qw.vo.QwExternalContactVO;
+import com.fs.qw.vo.QwTagGroupListVO;
+import com.fs.qw.vo.QwTagGroupVO;
+import com.fs.qw.vo.QwTagVO;
 import com.fs.qwApi.config.OpenQwConfig;
 import com.fs.qwApi.config.OpenQwConfig;
 import com.fs.qwApi.domain.QwResult;
 import com.fs.qwApi.domain.QwResult;
 import com.fs.qwApi.param.QwEditUserTagParam;
 import com.fs.qwApi.param.QwEditUserTagParam;
@@ -76,6 +90,7 @@ import org.jetbrains.annotations.Nullable;
 import org.json.JSONObject;
 import org.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -94,6 +109,7 @@ import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
@@ -188,6 +204,22 @@ public class AiHookServiceImpl implements AiHookService {
     @Autowired
     @Autowired
     private CloudHostProper cloudHostProper;
     private CloudHostProper cloudHostProper;
 
 
+    @Autowired
+    private com.fs.company.service.AiKnowledgeBaseService aiKnowledgeBaseService;
+
+    @Value("${ai.api.base-url:http://localhost:9009}")
+    private String aiApiBaseUrl;
+
+    @Autowired
+    ICompanyService companyService;
+
+    @Autowired
+    IpadSendUtils ipadSendUtils;
+
+    @Autowired
+    ICompanyUserService companyUserService;
+
+
     private static final String AI_REPLY = "AI_REPLY:";
     private static final String AI_REPLY = "AI_REPLY:";
     private static final String AI_REPLY_TAG = "AI_REPLY_TAG:";
     private static final String AI_REPLY_TAG = "AI_REPLY_TAG:";
 
 
@@ -490,6 +522,18 @@ public class AiHookServiceImpl implements AiHookService {
         if(qwExternalContacts.getType() == 1){
         if(qwExternalContacts.getType() == 1){
             FastGptChatSession fastGptChatSession= getFastGptSession(qwExternalContacts,user,dto);
             FastGptChatSession fastGptChatSession= getFastGptSession(qwExternalContacts,user,dto);
             if (qwContent.contains("验证请求") || qwContent.contains("联系人验证请求") || qwContent.contains("我已经添加了你")){
             if (qwContent.contains("验证请求") || qwContent.contains("联系人验证请求") || qwContent.contains("我已经添加了你")){
+                Calendar calendar1 = Calendar.getInstance();
+                //定时任务会处理10分钟以内的,所以设置20分钟
+                calendar1.add(Calendar.MINUTE, 60);
+                Date expireTime = calendar1.getTime();
+
+                FastGptChatSession chatSession = new FastGptChatSession();
+                chatSession.setLastTime(expireTime);
+                chatSession.setIsArtificial(1);
+                chatSession.setUserId(String.valueOf(sender));
+                chatSession.setSessionId(fastGptChatSession.getSessionId());
+
+                fastGptChatSessionMapper.updateFastGptChatSession(chatSession);
                 return R.ok();
                 return R.ok();
             }
             }
             if(type == 104||type == 101){
             if(type == 104||type == 101){
@@ -628,6 +672,12 @@ public class AiHookServiceImpl implements AiHookService {
             }
             }
             Gson gson = new Gson();
             Gson gson = new Gson();
             contentKh = finalContentKh;*/
             contentKh = finalContentKh;*/
+
+            if(contentKh.contains("【转人工】")){
+                log.error("ai请求转人工:"+role.getRoleId()+":"+qwExternalContacts.getName());
+                notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName(),"ai请求转人工",qwExternalContacts.getId(),sender);
+                return R.ok();
+            }
             Gson gson = new Gson();
             Gson gson = new Gson();
             FastGptChatConversation fastGptChatConversation = gson.fromJson(contentKh, FastGptChatConversation.class);
             FastGptChatConversation fastGptChatConversation = gson.fromJson(contentKh, FastGptChatConversation.class);
             String content = fastGptChatConversation.getAiContent();
             String content = fastGptChatConversation.getAiContent();
@@ -638,6 +688,15 @@ public class AiHookServiceImpl implements AiHookService {
                 int tokens = responseDatum.getTokens();
                 int tokens = responseDatum.getTokens();
                 token+=tokens;
                 token+=tokens;
             }
             }
+
+
+            /**
+             * 客户打标签
+             */
+            if(fastGptChatConversation.getTags() != null && !fastGptChatConversation.getTags().isEmpty()){
+                saveQwExtTags(qwExternalContacts,fastGptChatConversation,fastGptChatSession,user,tenantId);
+            }
+
             //存聊天记录
             //存聊天记录
             addSaveAiMsg(2,2,contentKh,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),result.getUsage().prompt_tokens,result.getUsage().completion_tokens,token);
             addSaveAiMsg(2,2,contentKh,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),result.getUsage().prompt_tokens,result.getUsage().completion_tokens,token);
             if (!content.isEmpty()){
             if (!content.isEmpty()){
@@ -705,6 +764,41 @@ public class AiHookServiceImpl implements AiHookService {
         return R.ok();
         return R.ok();
     }
     }
 
 
+    /**
+     * 租户给客户打标签
+     * @param qwExternalContacts
+     * @param fastGptChatConversation
+     * @param fastGptChatSession
+     * @param user
+     * @param tenantId
+     */
+    private void saveQwExtTags(QwExternalContact qwExternalContacts,FastGptChatConversation fastGptChatConversation,FastGptChatSession fastGptChatSession,QwUser user,Long tenantId) {
+        QwExternalContactAddTagParam Param = new QwExternalContactAddTagParam();
+
+        //添加需要打标签的客户
+        QwExternalContactParam param1 = new QwExternalContactParam();
+        param1.setCompanyId(user.getCompanyId());
+        List<Long> list = new ArrayList<>();
+        list.add(qwExternalContacts.getId());
+        Param.setUserIds(list);
+
+        try {
+            String[] split = fastGptChatConversation.getTags().split(",");
+            Param.setTagIds(Arrays.asList(split));
+            Param.setCorpId(user.getCorpId());
+        } catch (Exception e) {
+            System.out.println("标签格式错误,会话id:" + fastGptChatSession.getSessionId());
+        }
+
+        String url = OpenQwConfig.baseApi + "/addTag?tenantId=" + tenantId;
+        String result = HttpUtil.createPost(url)
+                .body(JSON.toJSONString(Param))
+                .execute()
+                .body();
+
+        System.out.println(result);
+    }
+
     private void sendImgMsg(String contentKh, Long sender, String uuid, Long serverId) {
     private void sendImgMsg(String contentKh, Long sender, String uuid, Long serverId) {
         com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(contentKh);
         com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(contentKh);
         JSONArray imgUrls = jsonObject.getJSONArray("imgUrl");
         JSONArray imgUrls = jsonObject.getJSONArray("imgUrl");
@@ -1278,7 +1372,71 @@ public class AiHookServiceImpl implements AiHookService {
         crmMsg.setCompanyId(user.getCompanyId());
         crmMsg.setCompanyId(user.getCompanyId());
         crmMsg.setCompanyUserId(user.getCompanyUserId());
         crmMsg.setCompanyUserId(user.getCompanyUserId());
         crmMsgService.insertCrmMsg(crmMsg);
         crmMsgService.insertCrmMsg(crmMsg);
+        try {
+            this.asyncAtMsg(user, "你的客户:" + chatSession.getNickName() + ", 因  \"" + reason + "\"  需要转人工,请及时回复");
+        } catch (Exception e) {
+            System.out.println("转人工发送消息失败:"+e.getMessage());
+        }
+
+    }
 
 
+    /**
+     * 异步发送掉线提醒
+     */
+    @Async
+    public void asyncAtMsg(QwUser qwUser, String msg) {
+        atMsg(qwUser, msg);
+    }
+
+    public void atMsg(QwUser qwUser, String msg) {
+        try {
+            String corpId = qwUser.getCorpId();
+            log.info("掉线提醒通知:{}, {}, {}", qwUser.getId(), qwUser.getQwUserName(), corpId);
+            // 获取通知账号
+            QwUser user = qwUserMapper.selectOfflineUser();
+            if(user == null){
+                log.info("qwId:{}=====未找到通知账号", qwUser.getId());
+                return;
+            }
+            Company company = companyService.selectCompanyById(qwUser.getCompanyId());
+            log.info("查到主体:{}", qwUser);
+            List<WxGetSessionRoomListVo.RoomList> sessionRoomList = ipadSendUtils.getSessionRoomList(user.getUid(), user.getServerId());
+            Optional<WxGetSessionRoomListVo.RoomList> optional = sessionRoomList.stream().filter(e -> e.getNickname().equals(company.getGroupName()) || e.getNickname().equals(company.getCompanyName())).findFirst();
+            if(!optional.isPresent()){
+                log.warn("qwId:{}=====会话管理未找到群聊,corpId:{},群聊名称:{}, 查到群聊名称:{}", qwUser.getId(), corpId, company.getCompanyName(), PubFun.listToNewList(sessionRoomList, WxGetSessionRoomListVo.RoomList::getNickname));
+                log.info("qwId:{}=====会话管理未找到群聊,corpId:{},群聊名称:{}, 查到群聊名称:{}", qwUser.getId(), corpId, company.getCompanyName(), PubFun.listToNewList(sessionRoomList, WxGetSessionRoomListVo.RoomList::getNickname));
+                return;
+            }
+            WxGetSessionRoomListVo.RoomList room = optional.get();
+            log.info("找到会话群聊:{}", room);
+            CompanyUser companyUser = companyUserService.selectCompanyUserById(qwUser.getCompanyUserId());
+            log.info("企微账号:{}", JSON.toJSONString(companyUser));
+            List<WxRoomUserListVo.MemberList> memberLists = ipadSendUtils.getSessionRoomUserList(user.getUid(), room.getRoom_id(), user.getServerId());
+            Function<WxRoomUserListVo.MemberList, String> getName = e -> StringUtils.isEmpty(e.getRoom_nickname()) ? e.getNickname() : e.getRoom_nickname();
+            Optional<WxRoomUserListVo.MemberList> first = memberLists.stream().filter(e -> getName.apply(e).equals(companyUser.getUserName()) || getName.apply(e).equals(companyUser.getNickName())).findFirst();
+            String sendMsg = "企微账号:" + qwUser.getQwUserName() + " - " + msg;
+            if(!first.isPresent()){
+                WxWorkSendTextMsgDTO dto = new WxWorkSendTextMsgDTO();
+                dto.setUuid(user.getUid());
+                dto.setSend_userid(room.getRoom_id());
+                dto.setIsRoom(true);
+                dto.setContent(sendMsg);
+                ipadSendUtils.sendTxtNoVo(dto, user.getServerId());
+            }else{
+                WxRoomUserListVo.MemberList memberList = first.get();
+                log.info("找到掉线人:{}", memberList);
+                WxSendAtMsgParam param = new WxSendAtMsgParam();
+                param.setUuid(user.getUid());
+                param.setContent(sendMsg);
+                param.setSend_userid(room.getRoom_id());
+                param.setAtids(Collections.singletonList(memberList.getUin()));
+                param.setRoom(true);
+                log.info("发送数据组装:{}", param);
+                ipadSendUtils.sendTextAtMsg(param, user.getServerId());
+            }
+        }catch (Exception e){
+            log.warn("掉线提醒发送失败", e);
+        }
     }
     }
 
 
     void sendQwAppMsg(String corpId, Integer agentId,String qwUserId,String content){
     void sendQwAppMsg(String corpId, Integer agentId,String qwUserId,String content){
@@ -1578,7 +1736,7 @@ public class AiHookServiceImpl implements AiHookService {
             }
             }
 
 
             //添加关键词
             //添加关键词
-            addPromptWordNew(messageList,msgC,qwExternalContactsId,role,fastGptChatSession);
+            addPromptWordNew(messageList,msgC,qwExternalContactsId,role,fastGptChatSession,user);
             R r = chatService.initiatingTakeChat(param, "http://129.28.170.206:3000/api/", appKey);
             R r = chatService.initiatingTakeChat(param, "http://129.28.170.206:3000/api/", appKey);
             Object data1 = r.get("data");
             Object data1 = r.get("data");
             if(!(data1 instanceof KnowledgeBaseResult)){
             if(!(data1 instanceof KnowledgeBaseResult)){
@@ -1598,6 +1756,30 @@ public class AiHookServiceImpl implements AiHookService {
         }
         }
 
 
     }
     }
+
+    private void addQwUserTags(FastGptChatConversation conversation, QwUser user,FastGptRole role) {
+        String tagGroups = role.getTagGroups();
+        if (tagGroups==null){
+            return;
+        }
+
+        List<QwTagGroupListVO> qwTagGroupListVO = qwTagGroupService.selectQwTagGroupByIds(tagGroups,user.getCorpId());
+
+        List<Map<String, Map<String,String>>> arrayList = new ArrayList<>();
+        Map<String, Map<String,String>> hashMap = new HashMap<>();
+        for (QwTagGroupListVO groupListVO : qwTagGroupListVO) {
+            Map<String,String> map = new HashMap<>();
+            List<QwTagVO> tag = groupListVO.getTag();
+            for (QwTagVO qwTagVO : tag) {
+                map.put(qwTagVO.getName(),qwTagVO.getTagId());
+            }
+            hashMap.put(groupListVO.getName(), map);
+        }
+
+        arrayList.add(hashMap);
+        conversation.setTagMapList(arrayList);
+    }
+
     /** 增加课程信息 **/
     /** 增加课程信息 **/
     private void addCourseWatchLog(Long id) {
     private void addCourseWatchLog(Long id) {
         FsCourseWatchLogVO log = fsCourseWatchLogMapper.selectFsCourseWatchLogByExtId(id);
         FsCourseWatchLogVO log = fsCourseWatchLogMapper.selectFsCourseWatchLogByExtId(id);
@@ -1631,11 +1813,12 @@ public class AiHookServiceImpl implements AiHookService {
         }
         }
     }
     }
     /** 组装发送AI内容 **/
     /** 组装发送AI内容 **/
-    private void addPromptWordNew(List<ChatParam.Message> messageList,String count,Long extId,FastGptRole role,FastGptChatSession fastGptChatSession){
+    private void addPromptWordNew(List<ChatParam.Message> messageList,String count,Long extId,FastGptRole role,FastGptChatSession fastGptChatSession,QwUser user){
 
 
         FastGptChatConversation conversation = new FastGptChatConversation();
         FastGptChatConversation conversation = new FastGptChatConversation();
         conversation.setUserInfo(new com.alibaba.fastjson.JSONObject());
         conversation.setUserInfo(new com.alibaba.fastjson.JSONObject());
         conversation.setHistory(new com.alibaba.fastjson.JSONArray());
         conversation.setHistory(new com.alibaba.fastjson.JSONArray());
+        List<Map<String, String>> knowledgeBase = new ArrayList<>();
 
 
         if(role.getReminderWords() != null && !role.getReminderWords().isEmpty()){
         if(role.getReminderWords() != null && !role.getReminderWords().isEmpty()){
             conversation.setAiInfo(role.getReminderWords());
             conversation.setAiInfo(role.getReminderWords());
@@ -1669,10 +1852,14 @@ public class AiHookServiceImpl implements AiHookService {
 
 
 
 
         List<FastGptChatMsg> msgs=fastGptChatMsgService.selectFastGptChatMsgByMsgSessionIdAndExtId(fastGptChatSession.getSessionId(),extId);
         List<FastGptChatMsg> msgs=fastGptChatMsgService.selectFastGptChatMsgByMsgSessionIdAndExtId(fastGptChatSession.getSessionId(),extId);
+
+        String contextQuery = count;
         if (!msgs.isEmpty()){
         if (!msgs.isEmpty()){
             com.alibaba.fastjson.JSONArray historyArray = new com.alibaba.fastjson.JSONArray();
             com.alibaba.fastjson.JSONArray historyArray = new com.alibaba.fastjson.JSONArray();
             Collections.reverse(msgs);
             Collections.reverse(msgs);
             msgs.remove(msgs.size() - 1);
             msgs.remove(msgs.size() - 1);
+            StringBuilder contextBuilder = new StringBuilder();
+            int historyCount = 0;
             for (FastGptChatMsg msg : msgs) {
             for (FastGptChatMsg msg : msgs) {
                 Integer sendType = msg.getSendType();
                 Integer sendType = msg.getSendType();
                 String content = msg.getContent();
                 String content = msg.getContent();
@@ -1685,14 +1872,103 @@ public class AiHookServiceImpl implements AiHookService {
                 msgObj.put("role", sendType==1?"user":"ai");
                 msgObj.put("role", sendType==1?"user":"ai");
                 msgObj.put("content", content);
                 msgObj.put("content", content);
                 historyArray.add(msgObj);
                 historyArray.add(msgObj);
+                if (content != null && !content.trim().isEmpty() && historyCount < 6) {
+                    if (sendType == 1) {
+                        contextBuilder.insert(0, "用户:" + content + "\n");
+                    } else {
+                        contextBuilder.insert(0, "AI:" + content + "\n");
+                    }
+                    historyCount++;
+                }
+            }
+            if (contextBuilder.length() > 0 && count != null && !count.trim().isEmpty()) {
+                contextQuery = contextBuilder.toString().trim() + "\n用户:" + count;
             }
             }
             conversation.setHistory(historyArray);
             conversation.setHistory(historyArray);
         }
         }
 
 
+        //从向量知识库中检索相关内容
+        if (count != null && !count.trim().isEmpty()) {
+            String searchQuery = contextQuery != null ? contextQuery : count;
+            log.info("知识库检索查询文本 | original={} | contextQuery={}", count, searchQuery);
+            try {
+                com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.fs.company.domain.AiKnowledgeBase> lqw =
+                        new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
+                lqw.eq(com.fs.company.domain.AiKnowledgeBase::getDelFlag, 0);
+                List<com.fs.company.domain.AiKnowledgeBase> kbList = aiKnowledgeBaseService.list(lqw);
+                if (kbList == null || kbList.isEmpty()) {
+                    log.warn("向量知识库检索跳过: 当前租户下无知识库 | roleId={}", role.getRoleId());
+                } else {
+                    Set<Long> addedIds = new HashSet<>();
+                    for (com.fs.company.domain.AiKnowledgeBase kb : kbList) {
+                        String collectionName = kb.getCollectionName();
+                        if (collectionName == null || collectionName.trim().isEmpty()) {
+                            continue;
+                        }
+
+                        // 第一路:向量语义搜索(使用上下文查询)
+                        List<Float> vector = createEmbedding(searchQuery);
+                        if (vector != null && !vector.isEmpty()) {
+                            List<Map<String, Object>> searchResults = searchQdrant(collectionName, vector, 3, 0.3);
+                            if (searchResults != null) {
+                                for (Map<String, Object> item : searchResults) {
+                                    Long pointId = extractPointId(item);
+                                    if (pointId != null && addedIds.contains(pointId)) {
+                                        continue;
+                                    }
+                                    if (pointId != null) {
+                                        addedIds.add(pointId);
+                                    }
+                                    Map<String, String> kbItem = extractPayloadItem(item);
+                                    if (!kbItem.isEmpty()) {
+                                        knowledgeBase.add(kbItem);
+                                    }
+                                }
+                            }
+                        }
+
+                        // 第二路:Payload关键词过滤搜索(从上下文和当前消息中提取关键词)
+                        List<String> keywords = extractKeywords(searchQuery);
+                        List<String> currentKeywords = extractKeywords(count);
+                        Set<String> allKeywords = new LinkedHashSet<>(currentKeywords);
+                        allKeywords.addAll(keywords);
+                        if (!allKeywords.isEmpty()) {
+                            for (String keyword : allKeywords) {
+                                List<Map<String, Object>> filterResults = searchQdrantByPayload(collectionName, keyword, 10);
+                                if (filterResults != null) {
+                                    for (Map<String, Object> item : filterResults) {
+                                        Long pointId = extractPointId(item);
+                                        if (pointId != null && addedIds.contains(pointId)) {
+                                            continue;
+                                        }
+                                        if (pointId != null) {
+                                            addedIds.add(pointId);
+                                        }
+                                        Map<String, String> kbItem = extractPayloadItem(item);
+                                        if (!kbItem.isEmpty()) {
+                                            knowledgeBase.add(kbItem);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                log.error("向量知识库检索失败 | roleId={} | content={}", role.getRoleId(), count, e);
+            }
+        }
+        log.info("向量知识库检索完成 | knowledgeBaseSize={} | roleId={}", knowledgeBase.size(), role.getRoleId());
+        conversation.setKnowledgeBase(knowledgeBase);
+
         if (count!=null&& !count.isEmpty()){
         if (count!=null&& !count.isEmpty()){
             conversation.setUserContent(count);
             conversation.setUserContent(count);
         }
         }
 
 
+        /**
+         * 添加企微标签
+         */
+        addQwUserTags(conversation,user,role);
 
 
         ChatParam.Message message1=new ChatParam.Message();
         ChatParam.Message message1=new ChatParam.Message();
         message1.setRole("user");
         message1.setRole("user");
@@ -1701,6 +1977,178 @@ public class AiHookServiceImpl implements AiHookService {
         message1.setContent(jsonStr);
         message1.setContent(jsonStr);
         messageList.add(message1);
         messageList.add(message1);
     }
     }
+
+    private Long extractPointId(Map<String, Object> item) {
+        Object id = item.get("id");
+        if (id instanceof Number) {
+            return ((Number) id).longValue();
+        }
+        return null;
+    }
+
+    private Map<String, String> extractPayloadItem(Map<String, Object> item) {
+        Map<String, String> kbItem = new HashMap<>();
+        Object payloadObj = item.get("payload");
+        if (payloadObj instanceof Map) {
+            Map<?, ?> payload = (Map<?, ?>) payloadObj;
+            Object qObj = payload.get("q");
+            Object aObj = payload.get("a");
+            if (qObj != null) {
+                kbItem.put("q", qObj.toString());
+            }
+            if (aObj != null) {
+                kbItem.put("a", aObj.toString());
+            }
+        }
+        return kbItem;
+    }
+
+    private List<String> extractKeywords(String text) {
+        List<String> keywords = new ArrayList<>();
+        if (text == null || text.trim().isEmpty()) {
+            return keywords;
+        }
+        java.util.regex.Matcher durationMatcher = java.util.regex.Pattern.compile("\\d+\\s*[天日周月年]").matcher(text);
+        while (durationMatcher.find()) {
+            keywords.add(durationMatcher.group().replaceAll("\\s+", ""));
+        }
+        String[] productWords = {"套餐", "方案", "服务", "产品", "价格", "费用", "优惠", "活动", "会员", "课程", "项目"};
+        for (String word : productWords) {
+            if (text.contains(word)) {
+                keywords.add(word);
+            }
+        }
+        return keywords;
+    }
+
+    @SuppressWarnings("unchecked")
+    private List<Map<String, Object>> searchQdrantByPayload(String collectionName, String keyword, int limit) {
+        try {
+            com.alibaba.fastjson.JSONObject req = new com.alibaba.fastjson.JSONObject();
+            req.put("collectionName", collectionName);
+            req.put("vector", Collections.nCopies(1024, 0.0f));
+            req.put("topK", limit);
+            req.put("scoreThreshold", 0.0);
+
+            com.alibaba.fastjson.JSONObject filter = new com.alibaba.fastjson.JSONObject();
+            com.alibaba.fastjson.JSONArray should = new com.alibaba.fastjson.JSONArray();
+            com.alibaba.fastjson.JSONObject qMatch = new com.alibaba.fastjson.JSONObject();
+            qMatch.put("key", "q");
+            com.alibaba.fastjson.JSONObject qMatchValue = new com.alibaba.fastjson.JSONObject();
+            qMatchValue.put("value", keyword);
+            qMatch.put("match", qMatchValue);
+            should.add(qMatch);
+            com.alibaba.fastjson.JSONObject aMatch = new com.alibaba.fastjson.JSONObject();
+            aMatch.put("key", "a");
+            com.alibaba.fastjson.JSONObject aMatchValue = new com.alibaba.fastjson.JSONObject();
+            aMatchValue.put("value", keyword);
+            aMatch.put("match", aMatchValue);
+            should.add(aMatch);
+            filter.put("should", should);
+            req.put("filter", filter);
+
+            String url = aiApiBaseUrl + "/qdrant/point/search";
+            String result = HttpUtil.post(url, req.toJSONString());
+            com.alibaba.fastjson.JSONObject resp = com.alibaba.fastjson.JSONObject.parseObject(result);
+            Integer code = resp.getInteger("code");
+            if (code == null || code != 200) {
+                return null;
+            }
+            Object dataObj = resp.get("data");
+            if (dataObj instanceof List) {
+                List<Map<String, Object>> results = new ArrayList<>();
+                for (Object item : (List<?>) dataObj) {
+                    if (item instanceof Map) {
+                        results.add((Map<String, Object>) item);
+                    }
+                }
+                return results;
+            }
+            return null;
+        } catch (Exception e) {
+            log.warn("Payload关键词搜索失败 | collectionName={} | keyword={}", collectionName, keyword, e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private List<Float> createEmbedding(String text) {
+        try {
+            com.alibaba.fastjson.JSONObject req = new com.alibaba.fastjson.JSONObject();
+            req.put("text", text);
+            String url = aiApiBaseUrl + "/ai/embedding/create";
+            log.info("请求Embedding API | url={} | textLength={}", url, text.length());
+            String result = HttpUtil.post(url, req.toJSONString());
+            log.info("Embedding API响应 | respLength={}", result != null ? result.length() : 0);
+            com.alibaba.fastjson.JSONObject resp = com.alibaba.fastjson.JSONObject.parseObject(result);
+            Integer code = resp.getInteger("code");
+            if (code == null || code != 200) {
+                log.error("Embedding API返回错误 | code={} | msg={} | resp={}", code, resp.getString("msg"), result);
+                return null;
+            }
+            com.alibaba.fastjson.JSONArray embeddingArray = resp.getJSONArray("data");
+            if (embeddingArray == null || embeddingArray.isEmpty()) {
+                log.error("Embedding API返回data为空 | resp={}", result);
+                return null;
+            }
+            List<Float> vector = new ArrayList<>();
+            for (Object item : embeddingArray) {
+                if (item instanceof Number) {
+                    vector.add(((Number) item).floatValue());
+                }
+            }
+            log.info("Embedding向量解析成功 | vectorSize={}", vector.size());
+            return vector;
+        } catch (Exception e) {
+            log.error("生成Embedding向量失败 | text={}", text, e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private List<Map<String, Object>> searchQdrant(String collectionName, List<Float> vector, int topK, double scoreThreshold) {
+        try {
+            com.alibaba.fastjson.JSONObject req = new com.alibaba.fastjson.JSONObject();
+            req.put("collectionName", collectionName);
+            req.put("vector", vector);
+            req.put("topK", topK);
+            req.put("scoreThreshold", scoreThreshold);
+            String url = aiApiBaseUrl + "/qdrant/point/search";
+            log.info("请求Qdrant搜索 | url={} | collectionName={} | topK={} | scoreThreshold={}", url, collectionName, topK, scoreThreshold);
+            String result = HttpUtil.post(url, req.toJSONString());
+            log.info("Qdrant搜索响应 | collectionName={} | respLength={}", collectionName, result != null ? result.length() : 0);
+            com.alibaba.fastjson.JSONObject resp = com.alibaba.fastjson.JSONObject.parseObject(result);
+            Integer code = resp.getInteger("code");
+            if (code == null || code != 200) {
+                log.error("Qdrant搜索API返回错误 | code={} | msg={} | collectionName={} | resp={}", code, resp.getString("msg"), collectionName, result);
+                return null;
+            }
+            Object dataObj = resp.get("data");
+            if (dataObj == null) {
+                log.error("Qdrant搜索API返回data为null | collectionName={} | resp={}", collectionName, result);
+                return null;
+            }
+            if (dataObj instanceof List) {
+                List<Map<String, Object>> results = new ArrayList<>();
+                for (Object item : (List<?>) dataObj) {
+                    if (item instanceof Map) {
+                        Map<String, Object> resultMap = (Map<String, Object>) item;
+                        Object score = resultMap.get("score");
+                        log.info("Qdrant搜索结果 | collectionName={} | score={} | id={}", collectionName, score, resultMap.get("id"));
+                        results.add(resultMap);
+                    }
+                }
+                log.info("Qdrant搜索完成 | collectionName={} | resultCount={}", collectionName, results.size());
+                return results;
+            }
+            log.error("Qdrant搜索API返回data类型异常 | collectionName={} | dataType={}", collectionName, dataObj.getClass().getName());
+            return null;
+        } catch (Exception e) {
+            log.error("Qdrant搜索失败 | collectionName={}", collectionName, e);
+            return null;
+        }
+    }
+
     /** 组装发送AI内容 **/
     /** 组装发送AI内容 **/
     private void addPromptWord(List<ChatParam.Message> messageList,String count,Long extId,String words,String countInfo,Long sessionId){
     private void addPromptWord(List<ChatParam.Message> messageList,String count,Long extId,String words,String countInfo,Long sessionId){
 
 
@@ -2224,7 +2672,7 @@ public class AiHookServiceImpl implements AiHookService {
                     if (oneDayAgo.getTime().after(fastGptChatSession.getLastTime())) {
                     if (oneDayAgo.getTime().after(fastGptChatSession.getLastTime())) {
                         Calendar calendar1 = Calendar.getInstance();
                         Calendar calendar1 = Calendar.getInstance();
                         //定时任务会处理10分钟以内的,所以设置20分钟
                         //定时任务会处理10分钟以内的,所以设置20分钟
-                        calendar1.add(Calendar.MINUTE, 20);
+                        calendar1.add(Calendar.MINUTE, 30);
                         Date expireTime = calendar1.getTime();
                         Date expireTime = calendar1.getTime();
 
 
                         FastGptChatSession chatSession = new FastGptChatSession();
                         FastGptChatSession chatSession = new FastGptChatSession();

+ 2 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java

@@ -96,4 +96,6 @@ public interface QwTagGroupMapper
     List<QwTagGroupListVO> selectQwTagGroups(QwTagGroup qwTagGroup);
     List<QwTagGroupListVO> selectQwTagGroups(QwTagGroup qwTagGroup);
 
 
     QwTagGroup selectQwTagGroupByName(@Param("tagGroup") String tagGroup, @Param("corpId") String corpId);
     QwTagGroup selectQwTagGroupByName(@Param("tagGroup") String tagGroup, @Param("corpId") String corpId);
+
+    List<QwTagGroupListVO> selectQwTagGroupByIds(@Param("tagGroups") String tagGroups, @Param("corpId") String corpId);
 }
 }

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

@@ -89,4 +89,6 @@ public interface IQwTagGroupService
     void delQwTagByAi(String trimTag, Long extId);
     void delQwTagByAi(String trimTag, Long extId);
 
 
     List<QwTagGroupListVO> selectQwTagGroupListVOPage(QwTagGroup qwTagGroup);
     List<QwTagGroupListVO> selectQwTagGroupListVOPage(QwTagGroup qwTagGroup);
+
+    List<QwTagGroupListVO> selectQwTagGroupByIds(String tagGroups,String corpId);
 }
 }

+ 14 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwTagGroupServiceImpl.java

@@ -650,4 +650,18 @@ public class QwTagGroupServiceImpl implements IQwTagGroupService {
         }
         }
         return vo;
         return vo;
     }
     }
+
+    @Override
+    public List<QwTagGroupListVO> selectQwTagGroupByIds(String tagGroups,String corpId) {
+        List<QwTagGroupListVO> vo = qwTagGroupMapper.selectQwTagGroupByIds(tagGroups,corpId);
+
+        for (QwTagGroupListVO qwTagGroupListVO : vo) {
+            QwTag qwTag = new QwTag();
+            qwTag.setGroupId(qwTagGroupListVO.getGroupId());
+            qwTag.setCompanyId(qwTagGroupListVO.getCompanyId());
+            List<QwTagVO> qwTags = qwTagMapper.selectQwTagListVO(qwTag);
+            qwTagGroupListVO.setTag(qwTags);
+        }
+        return vo;
+    }
 }
 }

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

@@ -1311,7 +1311,7 @@ public class QwUserServiceImpl implements IQwUserService
 
 
         System.out.println("回调地址"+"http://saasqwapimsg.ylrzcloud.com/msg/callback/"+serverId + "/"+loginParam.getTenantId());
         System.out.println("回调地址"+"http://saasqwapimsg.ylrzcloud.com/msg/callback/"+serverId + "/"+loginParam.getTenantId());
         wxWorkSetCallbackUrlDTO.setUrl("http://saasqwapimsg.ylrzcloud.com/msg/callback/"+serverId+ "/"+loginParam.getTenantId());
         wxWorkSetCallbackUrlDTO.setUrl("http://saasqwapimsg.ylrzcloud.com/msg/callback/"+serverId+ "/"+loginParam.getTenantId());
-//        wxWorkSetCallbackUrlDTO.setUrl("http://wf89b6de.natappfree.cc/msg/callback/"+serverId+ "/"+loginParam.getTenantId());
+        //wxWorkSetCallbackUrlDTO.setUrl("http://cn-hk-bgp-4.ofalias.net:55081/msg/callback/"+serverId+ "/"+loginParam.getTenantId());
         wxWorkSetCallbackUrlDTO.setUuid(data.getUuid());
         wxWorkSetCallbackUrlDTO.setUuid(data.getUuid());
         wxWorkService.SetCallbackUrl(wxWorkSetCallbackUrlDTO,serverId);
         wxWorkService.SetCallbackUrl(wxWorkSetCallbackUrlDTO,serverId);
 
 

+ 2 - 0
fs-service/src/main/java/com/fs/third/service/ITencentWordService.java

@@ -31,4 +31,6 @@ public interface ITencentWordService {
     List<TencentWord> getFiles();
     List<TencentWord> getFiles();
 
 
     void synchronization(String fileId);
     void synchronization(String fileId);
+
+    R syncToKnowledgeBase(String fileId, Long knowledgeBaseId);
 }
 }

+ 199 - 35
fs-service/src/main/java/com/fs/third/service/impl/TencentWordServiceImpl.java

@@ -6,6 +6,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.company.domain.AiKnowledgeBase;
+import com.fs.company.mapper.AiKnowledgeBaseMapper;
 import com.fs.hisStore.enums.SysConfigEnum;
 import com.fs.hisStore.enums.SysConfigEnum;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.mapper.SysConfigMapper;
@@ -16,15 +18,14 @@ import com.fs.third.mapper.TencentWordMapper;
 import com.fs.third.mapper.TencentWordDetailMapper;
 import com.fs.third.mapper.TencentWordDetailMapper;
 import com.fs.third.mapper.TencentWordSheetMapper;
 import com.fs.third.mapper.TencentWordSheetMapper;
 import com.fs.third.service.ITencentWordService;
 import com.fs.third.service.ITencentWordService;
+import cn.hutool.http.HttpUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import okhttp3.*;
 import okhttp3.*;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
 import java.io.IOException;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 
 
@@ -43,6 +44,9 @@ public class TencentWordServiceImpl extends ServiceImpl<TencentWordMapper, Tence
     private final RedisCache redisCache;
     private final RedisCache redisCache;
     private final TencentWordSheetMapper tencentWordSheetMapper;
     private final TencentWordSheetMapper tencentWordSheetMapper;
     private final TencentWordDetailMapper tencentWordDetailMapper;
     private final TencentWordDetailMapper tencentWordDetailMapper;
+    private final AiKnowledgeBaseMapper aiKnowledgeBaseMapper;
+
+    private static final String AI_API_BASE_URL = "http://localhost:9009";
 
 
     @Override
     @Override
     public TencentWord selectTencentWordById(Long id) {
     public TencentWord selectTencentWordById(Long id) {
@@ -138,7 +142,6 @@ public class TencentWordServiceImpl extends ServiceImpl<TencentWordMapper, Tence
 
 
     @Override
     @Override
     public void synchronization(String fileId) {
     public void synchronization(String fileId) {
-        // 获取sheet
         Map<String, String> sysConfigCache = getSysConfigCache();
         Map<String, String> sysConfigCache = getSysConfigCache();
 
 
         String clientId = sysConfigCache.get("clientId");
         String clientId = sysConfigCache.get("clientId");
@@ -162,17 +165,33 @@ public class TencentWordServiceImpl extends ServiceImpl<TencentWordMapper, Tence
                 JSONObject result = JSONObject.parseObject(responseBody);
                 JSONObject result = JSONObject.parseObject(responseBody);
                 JSONArray properties = result.getJSONArray("properties");
                 JSONArray properties = result.getJSONArray("properties");
                 if (properties != null && !properties.isEmpty()) {
                 if (properties != null && !properties.isEmpty()) {
+                    List<TencentWordSheet> existingSheets = tencentWordSheetMapper.selectList(
+                            new LambdaQueryWrapper<TencentWordSheet>().eq(TencentWordSheet::getFileId, fileId));
+                    Map<String, TencentWordSheet> existingSheetMap = existingSheets.stream()
+                            .collect(Collectors.toMap(TencentWordSheet::getSheetId, s -> s, (a, b) -> a));
+
                     for (int i = 0; i < properties.size(); i++) {
                     for (int i = 0; i < properties.size(); i++) {
                         JSONObject sheetJson = properties.getJSONObject(i);
                         JSONObject sheetJson = properties.getJSONObject(i);
-                        TencentWordSheet sheet = new TencentWordSheet();
-                        sheet.setFileId(fileId);
-                        sheet.setSheetId(sheetJson.getString("sheetId"));
-                        sheet.setTitle(sheetJson.getString("title"));
-                        sheet.setRowCount(sheetJson.getInteger("rowCount"));
-                        sheet.setColumnCount(sheetJson.getInteger("columnCount"));
-                        sheet.setRowTotal(sheetJson.getInteger("rowTotal"));
-                        sheet.setColumnTotal(sheetJson.getInteger("columnTotal"));
-                        tencentWordSheetMapper.insert(sheet);
+                        String sheetId = sheetJson.getString("sheetId");
+                        TencentWordSheet sheet = existingSheetMap.get(sheetId);
+                        if (sheet != null) {
+                            sheet.setTitle(sheetJson.getString("title"));
+                            sheet.setRowCount(sheetJson.getInteger("rowCount"));
+                            sheet.setColumnCount(sheetJson.getInteger("columnCount"));
+                            sheet.setRowTotal(sheetJson.getInteger("rowTotal"));
+                            sheet.setColumnTotal(sheetJson.getInteger("columnTotal"));
+                            tencentWordSheetMapper.updateById(sheet);
+                        } else {
+                            sheet = new TencentWordSheet();
+                            sheet.setFileId(fileId);
+                            sheet.setSheetId(sheetId);
+                            sheet.setTitle(sheetJson.getString("title"));
+                            sheet.setRowCount(sheetJson.getInteger("rowCount"));
+                            sheet.setColumnCount(sheetJson.getInteger("columnCount"));
+                            sheet.setRowTotal(sheetJson.getInteger("rowTotal"));
+                            sheet.setColumnTotal(sheetJson.getInteger("columnTotal"));
+                            tencentWordSheetMapper.insert(sheet);
+                        }
                     }
                     }
                 }
                 }
             } else {
             } else {
@@ -182,11 +201,13 @@ public class TencentWordServiceImpl extends ServiceImpl<TencentWordMapper, Tence
         } catch (IOException e) {
         } catch (IOException e) {
             log.error("腾讯文档获取工作表请求异常 | fileId={}", fileId, e);
             log.error("腾讯文档获取工作表请求异常 | fileId={}", fileId, e);
         }
         }
-        List<TencentWordSheet> sheets = tencentWordSheetMapper.selectList(new LambdaQueryWrapper<TencentWordSheet>().eq(TencentWordSheet::getFileId, fileId));
+
+        List<TencentWordSheet> sheets = tencentWordSheetMapper.selectList(
+                new LambdaQueryWrapper<TencentWordSheet>().eq(TencentWordSheet::getFileId, fileId));
         ArrayList<TencentWordDetail> updates = new ArrayList<>();
         ArrayList<TencentWordDetail> updates = new ArrayList<>();
         ArrayList<TencentWordDetail> inserts = new ArrayList<>();
         ArrayList<TencentWordDetail> inserts = new ArrayList<>();
         sheets.forEach(sheet -> {
         sheets.forEach(sheet -> {
-            if (sheet.getRowCount() > 1) {
+            if (sheet.getRowCount() != null && sheet.getRowCount() > 1) {
                 getSheetInfo(fileId, sheet, updates, inserts);
                 getSheetInfo(fileId, sheet, updates, inserts);
             }
             }
         });
         });
@@ -196,6 +217,22 @@ public class TencentWordServiceImpl extends ServiceImpl<TencentWordMapper, Tence
         if (!updates.isEmpty()) {
         if (!updates.isEmpty()) {
             tencentWordDetailMapper.updateBatchById(updates);
             tencentWordDetailMapper.updateBatchById(updates);
         }
         }
+
+        //同步到向量知识库(当前租户下的所有知识库)
+        List<AiKnowledgeBase> kbList = aiKnowledgeBaseMapper.selectList(
+                new LambdaQueryWrapper<AiKnowledgeBase>().eq(AiKnowledgeBase::getDelFlag, 0));
+        if (kbList != null && !kbList.isEmpty()) {
+            for (AiKnowledgeBase kb : kbList) {
+                try {
+                    R syncResult = syncToKnowledgeBase(fileId, kb.getId());
+                    log.info("腾讯文档同步后自动写入向量知识库 | fileId={} | knowledgeBaseId={} | collectionName={} | result={}",
+                            fileId, kb.getId(), kb.getCollectionName(), syncResult.get("msg"));
+                } catch (Exception e) {
+                    log.error("腾讯文档同步后自动写入向量知识库失败 | fileId={} | knowledgeBaseId={}",
+                            fileId, kb.getId(), e);
+                }
+            }
+        }
     }
     }
 
 
     private void getSheetInfo(String fileId, TencentWordSheet sheet, ArrayList<TencentWordDetail> updates, ArrayList<TencentWordDetail> inserts) {
     private void getSheetInfo(String fileId, TencentWordSheet sheet, ArrayList<TencentWordDetail> updates, ArrayList<TencentWordDetail> inserts) {
@@ -218,11 +255,15 @@ public class TencentWordServiceImpl extends ServiceImpl<TencentWordMapper, Tence
                 .build();
                 .build();
 
 
         try (Response response = client.newCall(request).execute()) {
         try (Response response = client.newCall(request).execute()) {
-            List<TencentWordDetail> tencentWordDetails = tencentWordDetailMapper.selectList(new LambdaQueryWrapper<TencentWordDetail>().eq(TencentWordDetail::getFileId, fileId).eq(TencentWordDetail::getSheetId, sheet.getSheetId()));
-            Map<String, Long> qIdMap = tencentWordDetails.stream().collect(Collectors.toMap(TencentWordDetail::getQ, TencentWordDetail::getId));
-            if (response.isSuccessful() && response.body() != null) {
-
+            List<TencentWordDetail> tencentWordDetails = tencentWordDetailMapper.selectList(
+                    new LambdaQueryWrapper<TencentWordDetail>()
+                            .eq(TencentWordDetail::getFileId, fileId)
+                            .eq(TencentWordDetail::getSheetId, sheet.getSheetId()));
+            Map<String, TencentWordDetail> existingDetailMap = tencentWordDetails.stream()
+                    .filter(d -> d.getQ() != null)
+                    .collect(Collectors.toMap(TencentWordDetail::getQ, d -> d, (a, b) -> a));
 
 
+            if (response.isSuccessful() && response.body() != null) {
                 String responseBody = response.body().string();
                 String responseBody = response.body().string();
                 log.info("腾讯文档获取表格数据成功 | fileId={} | sheetId={} | response={}", fileId, sheet.getSheetId(), responseBody);
                 log.info("腾讯文档获取表格数据成功 | fileId={} | sheetId={} | response={}", fileId, sheet.getSheetId(), responseBody);
                 JSONObject result = JSONObject.parseObject(responseBody);
                 JSONObject result = JSONObject.parseObject(responseBody);
@@ -236,26 +277,27 @@ public class TencentWordServiceImpl extends ServiceImpl<TencentWordMapper, Tence
                             if (values != null && values.size() >= 2) {
                             if (values != null && values.size() >= 2) {
                                 String q = getCellText(values.getJSONObject(0));
                                 String q = getCellText(values.getJSONObject(0));
                                 String a = getCellText(values.getJSONObject(1));
                                 String a = getCellText(values.getJSONObject(1));
-                                TencentWordDetail detail = new TencentWordDetail();
-                                detail.setFileId(fileId);
-                                detail.setSheetId(sheet.getSheetId());
-                                Long l = qIdMap.get(q);
-                                detail.setQ(q);
-                                detail.setA(a);
-                                if (l!= null){
-                                    detail.setId(l);
-                                    updates.add(detail);
+                                if (q == null || q.trim().isEmpty()) {
                                     continue;
                                     continue;
                                 }
                                 }
-                                inserts.add(detail);
-//                                tencentWordDetailMapper.insert(detail);
+                                TencentWordDetail existing = existingDetailMap.get(q);
+                                if (existing != null) {
+                                    if (!q.equals(existing.getQ()) || (a != null && !a.equals(existing.getA())) || (a == null && existing.getA() != null)) {
+                                        existing.setA(a);
+                                        updates.add(existing);
+                                    }
+                                } else {
+                                    TencentWordDetail detail = new TencentWordDetail();
+                                    detail.setFileId(fileId);
+                                    detail.setSheetId(sheet.getSheetId());
+                                    detail.setQ(q);
+                                    detail.setA(a);
+                                    inserts.add(detail);
+                                }
                             }
                             }
                         }
                         }
                     }
                     }
                 }
                 }
-//                if (!updates.isEmpty()) {
-//                    tencentWordDetailMapper.updateBatchById(updates);
-//                }
             } else {
             } else {
                 String errorBody = response.body() != null ? response.body().string() : "";
                 String errorBody = response.body() != null ? response.body().string() : "";
                 log.error("腾讯文档获取表格数据失败 | fileId={} | sheetId={} | status={} | body={}", fileId, sheet.getSheetId(), response.code(), errorBody);
                 log.error("腾讯文档获取表格数据失败 | fileId={} | sheetId={} | status={} | body={}", fileId, sheet.getSheetId(), response.code(), errorBody);
@@ -270,8 +312,16 @@ public class TencentWordServiceImpl extends ServiceImpl<TencentWordMapper, Tence
             return null;
             return null;
         }
         }
         JSONObject cellValue = cell.getJSONObject("cellValue");
         JSONObject cellValue = cell.getJSONObject("cellValue");
-        if (cellValue != null) {
-            return cellValue.getString("text");
+        if (cellValue == null) {
+            return null;
+        }
+        String text = cellValue.getString("text");
+        if (text != null && !text.trim().isEmpty()) {
+            return text;
+        }
+        Object value = cellValue.get("value");
+        if (value != null) {
+            return String.valueOf(value);
         }
         }
         return null;
         return null;
     }
     }
@@ -289,4 +339,118 @@ public class TencentWordServiceImpl extends ServiceImpl<TencentWordMapper, Tence
         }
         }
         return sysConfigMap;
         return sysConfigMap;
     }
     }
+
+    @Override
+    public R syncToKnowledgeBase(String fileId, Long knowledgeBaseId) {
+        AiKnowledgeBase knowledgeBase = aiKnowledgeBaseMapper.selectById(knowledgeBaseId);
+        if (knowledgeBase == null) {
+            return R.error("知识库不存在");
+        }
+        String collectionName = knowledgeBase.getCollectionName();
+        if (collectionName == null || collectionName.trim().isEmpty()) {
+            return R.error("知识库向量集合未初始化");
+        }
+
+        List<TencentWordDetail> details = tencentWordDetailMapper.selectList(
+                new LambdaQueryWrapper<TencentWordDetail>().eq(TencentWordDetail::getFileId, fileId));
+        if (details.isEmpty()) {
+            log.info("该文件下没有可同步的问答数据, 跳过向量知识库同步 | fileId={}", fileId);
+            return R.ok("无同步数据, 不做处理");
+        }
+
+        // 生成向量和payload
+        List<Long> ids = new ArrayList<>();
+        List<List<Float>> vectors = new ArrayList<>();
+        List<String> documents = new ArrayList<>();
+        List<Map<String, Object>> payloads = new ArrayList<>();
+
+        for (TencentWordDetail detail : details) {
+            String q = detail.getQ();
+            String a = detail.getA();
+            if (q == null || q.trim().isEmpty()) {
+                continue;
+            }
+
+            List<Float> embedding;
+            try {
+                embedding = createEmbedding(q);
+            } catch (Exception e) {
+                log.error("生成向量失败 | detailId={} | q={}", detail.getId(), q, e);
+                continue;
+            }
+
+            ids.add(detail.getId());
+            vectors.add(embedding);
+
+            String documentText = "问题:" + q;
+            if (a != null && !a.trim().isEmpty()) {
+                documentText += "\n答案:" + a;
+            }
+            documents.add(documentText);
+
+            Map<String, Object> payload = new LinkedHashMap<>();
+            payload.put("fileId", detail.getFileId());
+            payload.put("sheetId", detail.getSheetId());
+            payload.put("q", q);
+            payload.put("a", a);
+            payloads.add(payload);
+        }
+
+        if (ids.isEmpty()) {
+            return R.error("没有成功生成向量的数据");
+        }
+
+        // 删除旧collection
+        try {
+            String deleteResult = HttpUtil.post(AI_API_BASE_URL + "/qdrant/collection/delete?collectionName=" + collectionName, "");
+            log.info("删除旧collection | collectionName={} | result={}", collectionName, deleteResult);
+        } catch (Exception e) {
+            log.warn("删除旧collection失败(可能不存在), 继续创建 | collectionName={}", collectionName, e);
+        }
+
+        // 重新创建collection
+        try {
+            JSONObject createReq = new JSONObject();
+            JSONObject vectorsConfig = new JSONObject();
+            vectorsConfig.put("size", 1024);
+            vectorsConfig.put("distance", "Cosine");
+            createReq.put("collectionName", collectionName);
+            createReq.put("vectorSize", 1024);
+            String createResult = HttpUtil.post(AI_API_BASE_URL + "/qdrant/collection/create", createReq.toJSONString());
+            log.info("重新创建collection | collectionName={} | result={}", collectionName, createResult);
+        } catch (Exception e) {
+            log.error("创建collection失败 | collectionName={}", collectionName, e);
+            return R.error("创建向量知识库集合失败: " + e.getMessage());
+        }
+
+        // 写入向量数据
+        JSONObject upsertReq = new JSONObject();
+        upsertReq.put("collectionName", collectionName);
+        upsertReq.put("ids", ids);
+        upsertReq.put("vectors", vectors);
+        upsertReq.put("documents", documents);
+        upsertReq.put("payloads", payloads);
+
+        try {
+            String result = HttpUtil.post(AI_API_BASE_URL + "/qdrant/point/upsert", upsertReq.toJSONString());
+            log.info("向量数据写入Qdrant完成 | collectionName={} | count={} | result={}", collectionName, ids.size(), result);
+        } catch (Exception e) {
+            log.error("向量数据写入Qdrant失败 | collectionName={}", collectionName, e);
+            return R.error("写入向量数据库失败: " + e.getMessage());
+        }
+
+        return R.ok("成功同步" + ids.size() + "条数据到知识库");
+    }
+
+    private List<Float> createEmbedding(String text) {
+        JSONObject req = new JSONObject();
+        req.put("text", text);
+        String result = HttpUtil.post(AI_API_BASE_URL + "/ai/embedding/create", req.toJSONString());
+        JSONObject resp = JSONObject.parseObject(result);
+        JSONArray embeddingArray = resp.getJSONArray("data");
+        if (embeddingArray == null || embeddingArray.isEmpty()) {
+            throw new RuntimeException("Embedding返回向量为空");
+        }
+        return embeddingArray.toJavaList(Float.class);
+    }
 }
 }

+ 2 - 2
fs-service/src/main/java/com/fs/wxcid/dto/message/CdnUploadVideoResult.java

@@ -57,8 +57,8 @@ public class CdnUploadVideoResult {
     @JSONField(name = "RetCode")
     @JSONField(name = "RetCode")
     private Integer retCode;
     private Integer retCode;
 
 
-    @JSONField(name = "FileID")
-    private String fileID;
+    @JSONField(name = "FileId")
+    private String fileId;
 
 
     @JSONField(name = "ThumbHeight")
     @JSONField(name = "ThumbHeight")
     private Integer thumbHeight;
     private Integer thumbHeight;

+ 1 - 0
fs-service/src/main/resources/db/tenant-initTable.sql

@@ -3818,6 +3818,7 @@ CREATE TABLE `fastgpt_role` (
   `send_course_status` tinyint(1) DEFAULT NULL COMMENT '是否发送新客先导课',
   `send_course_status` tinyint(1) DEFAULT NULL COMMENT '是否发送新客先导课',
   `course_id` bigint DEFAULT NULL COMMENT '课程id',
   `course_id` bigint DEFAULT NULL COMMENT '课程id',
   `user_info` varchar(3000) DEFAULT '昵称,性别,联系方式,预算范围,行程时长,预计出行时间,同行关系,核心需求,意向套餐' COMMENT '用户信息',
   `user_info` varchar(3000) DEFAULT '昵称,性别,联系方式,预算范围,行程时长,预计出行时间,同行关系,核心需求,意向套餐' COMMENT '用户信息',
+  `tag_groups` varchar(255) DEFAULT NULL COMMENT '需要打标签的标签组',
   PRIMARY KEY (`role_id`) USING BTREE
   PRIMARY KEY (`role_id`) USING BTREE
 ) ENGINE=InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='应用表';
 ) ENGINE=InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='应用表';
 
 

+ 5 - 1
fs-service/src/main/resources/mapper/fastGpt/FastGptRoleMapper.xml

@@ -28,12 +28,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="sendCourseStatus"    column="send_course_status"    />
         <result property="sendCourseStatus"    column="send_course_status"    />
         <result property="courseId"    column="course_id"    />
         <result property="courseId"    column="course_id"    />
         <result property="userInfo"    column="user_info"    />
         <result property="userInfo"    column="user_info"    />
+        <result property="tagGroups"    column="tag_groups"    />
     </resultMap>
     </resultMap>
 
 
     <sql id="selectFastGptRoleVo">
     <sql id="selectFastGptRoleVo">
         select role_id, role_name,contact_info,company_id, create_time, update_time, role_type, mode_config_json,
         select role_id, role_name,contact_info,company_id, create_time, update_time, role_type, mode_config_json,
                mode, kf_id, kf_url, avatar, kf_media_id,reminder_words, bind_corp_id,channel_type,logistics,forbid_send_start,
                mode, kf_id, kf_url, avatar, kf_media_id,reminder_words, bind_corp_id,channel_type,logistics,forbid_send_start,
-               forbid_send_end,forbid_status,send_course_status,course_id,user_info
+               forbid_send_end,forbid_status,send_course_status,course_id,user_info,tag_groups
         from fastgpt_role
         from fastgpt_role
     </sql>
     </sql>
 
 
@@ -113,6 +114,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="bindCorpId != null">bind_corp_id,</if>
             <if test="bindCorpId != null">bind_corp_id,</if>
             <if test="contactInfo != null">contact_info,</if>
             <if test="contactInfo != null">contact_info,</if>
             <if test="userInfo != null">user_info,</if>
             <if test="userInfo != null">user_info,</if>
+            <if test="tagGroups != null">tag_groups,</if>
         </trim>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="roleName != null">#{roleName},</if>
             <if test="roleName != null">#{roleName},</if>
@@ -130,6 +132,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="bindCorpId != null">#{bindCorpId},</if>
             <if test="bindCorpId != null">#{bindCorpId},</if>
             <if test="contactInfo != null">#{contactInfo},</if>
             <if test="contactInfo != null">#{contactInfo},</if>
             <if test="userInfo != null">#{userInfo},</if>
             <if test="userInfo != null">#{userInfo},</if>
+            <if test="tagGroups != null">#{tagGroups},</if>
         </trim>
         </trim>
     </insert>
     </insert>
 
 
@@ -156,6 +159,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="sendCourseStatus != null">send_course_status = #{sendCourseStatus},</if>
             <if test="sendCourseStatus != null">send_course_status = #{sendCourseStatus},</if>
             <if test="courseId != null">course_id = #{courseId},</if>
             <if test="courseId != null">course_id = #{courseId},</if>
             <if test="userInfo != null">user_info = #{userInfo},</if>
             <if test="userInfo != null">user_info = #{userInfo},</if>
+            <if test="tagGroups != null">tag_groups = #{tagGroups},</if>
         </trim>
         </trim>
         where role_id = #{roleId}
         where role_id = #{roleId}
     </update>
     </update>

+ 11 - 0
fs-service/src/main/resources/mapper/qw/QwTagGroupMapper.xml

@@ -114,4 +114,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectQwTagGroupByName" resultType="com.fs.qw.domain.QwTagGroup">
     <select id="selectQwTagGroupByName" resultType="com.fs.qw.domain.QwTagGroup">
         select * from qw_tag_group where name=#{tagGroup} and corp_id=#{corpId} limit 1
         select * from qw_tag_group where name=#{tagGroup} and corp_id=#{corpId} limit 1
     </select>
     </select>
+    <select id="selectQwTagGroupByIds" resultType="com.fs.qw.vo.QwTagGroupListVO">
+        select * from qw_tag_group
+        <where>
+            <if test="tagGroups != null and tagGroups != '' ">
+                and find_in_set(id,#{tagGroups})
+            </if>
+            <if test="corpId != null and corpId != '' ">
+                and corp_id= #{corpId}
+            </if>
+        </where>
+    </select>
 </mapper>
 </mapper>

+ 5 - 4
fs-service/src/main/resources/mapper/third/TencentWordDetailMapper.xml

@@ -5,7 +5,7 @@
 <mapper namespace="com.fs.third.mapper.TencentWordDetailMapper">
 <mapper namespace="com.fs.third.mapper.TencentWordDetailMapper">
     <resultMap id="TencentWordDetailResult" type="com.fs.third.domain.TencentWordDetail">
     <resultMap id="TencentWordDetailResult" type="com.fs.third.domain.TencentWordDetail">
         <id property="id" column="id"/>
         <id property="id" column="id"/>
-        <result property="fileID" column="file_id"/>
+        <result property="fileId" column="file_id"/>
         <result property="sheetId" column="sheet_id"/>
         <result property="sheetId" column="sheet_id"/>
         <result property="q" column="q"/>
         <result property="q" column="q"/>
         <result property="a" column="a"/>
         <result property="a" column="a"/>
@@ -15,9 +15,10 @@
         <result property="updateTime" column="update_time"/>
         <result property="updateTime" column="update_time"/>
     </resultMap>
     </resultMap>
     <insert id="insertBatch">
     <insert id="insertBatch">
-        <foreach collection="list" item="item" separator=";">
-            insert into tencent_word_detail (file_id, sheet_id, q, a, create_by, create_time, update_by, update_time)
-            values (#{item.fileID}, #{item.sheetId}, #{item.q}, #{item.a}, #{item.createBy}, now(), #{item.updateBy}, now())
+        insert into tencent_word_detail (file_id, sheet_id, q, a, create_by, create_time, update_by, update_time)
+        values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.fileId}, #{item.sheetId}, #{item.q}, #{item.a}, #{item.createBy}, now(), #{item.updateBy}, now())
         </foreach>
         </foreach>
     </insert>
     </insert>