5 Commits fd2f07da58 ... a34ca3408c

Autor SHA1 Mensagem Data
  caoliqin a34ca3408c feat:统一手动和自动的看课页面路径 1 semana atrás
  caoliqin 2c08bd5db6 Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java 1 semana atrás
  caoliqin 993200bbb6 Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java 1 semana atrás
  caoliqin 58a9830e18 Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java 1 semana atrás
  caoliqin fda24518ca feat:AI回复高频问题统计 1 semana atrás
19 arquivos alterados com 1416 adições e 16 exclusões
  1. 103 0
      fs-admin/src/main/java/com/fs/fastGpt/FastgptChatQuestionController.java
  2. 97 0
      fs-company/src/main/java/com/fs/company/controller/fastGpt/FastgptChatQuestionController.java
  3. 103 0
      fs-company/src/main/java/com/fs/company/controller/fastGpt/FastgptChatQuestionStatisticsController.java
  4. 2 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  5. 70 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastgptChatQuestion.java
  6. 50 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastgptChatQuestionStatistics.java
  7. 1 1
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptRoleMapper.java
  8. 64 0
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastgptChatQuestionMapper.java
  9. 68 0
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastgptChatQuestionStatisticsMapper.java
  10. 20 0
      fs-service/src/main/java/com/fs/fastGpt/param/FastgptKnowledgeMissCollectParam.java
  11. 61 0
      fs-service/src/main/java/com/fs/fastGpt/service/IFastgptChatQuestionService.java
  12. 61 0
      fs-service/src/main/java/com/fs/fastGpt/service/IFastgptChatQuestionStatisticsService.java
  13. 22 13
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  14. 111 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastgptChatQuestionCollectServiceImpl.java
  15. 93 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastgptChatQuestionServiceImpl.java
  16. 94 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastgptChatQuestionStatisticsServiceImpl.java
  17. 165 0
      fs-service/src/main/java/com/fs/fastGpt/util/FastgptQuestionNormalizeUtil.java
  18. 121 0
      fs-service/src/main/resources/mapper/fastGpt/FastgptChatQuestionMapper.xml
  19. 110 0
      fs-service/src/main/resources/mapper/fastGpt/FastgptChatQuestionStatisticsMapper.xml

+ 103 - 0
fs-admin/src/main/java/com/fs/fastGpt/FastgptChatQuestionController.java

@@ -0,0 +1,103 @@
+package com.fs.fastGpt;
+
+import java.util.List;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.fastGpt.domain.FastgptChatQuestion;
+import com.fs.fastGpt.service.IFastgptChatQuestionService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 聊天问题收集Controller
+ *
+ * @author fs
+ * @date 2026-04-20
+ */
+@RestController
+@RequestMapping("/fastGpt/fastgptChatQuestion")
+public class FastgptChatQuestionController extends BaseController
+{
+    @Autowired
+    private IFastgptChatQuestionService fastgptChatQuestionService;
+
+    /**
+     * 查询聊天问题收集列表
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastgptChatQuestion:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FastgptChatQuestion fastgptChatQuestion)
+    {
+        startPage();
+        List<FastgptChatQuestion> list = fastgptChatQuestionService.selectFastgptChatQuestionList(fastgptChatQuestion);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出聊天问题收集列表
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastgptChatQuestion:export')")
+    @Log(title = "聊天问题收集", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FastgptChatQuestion fastgptChatQuestion)
+    {
+        List<FastgptChatQuestion> list = fastgptChatQuestionService.selectFastgptChatQuestionList(fastgptChatQuestion);
+        ExcelUtil<FastgptChatQuestion> util = new ExcelUtil<FastgptChatQuestion>(FastgptChatQuestion.class);
+        return util.exportExcel(list, "聊天问题收集数据");
+    }
+
+    /**
+     * 获取聊天问题收集详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastgptChatQuestion:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fastgptChatQuestionService.selectFastgptChatQuestionById(id));
+    }
+
+    /**
+     * 新增聊天问题收集
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastgptChatQuestion:add')")
+    @Log(title = "聊天问题收集", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FastgptChatQuestion fastgptChatQuestion)
+    {
+        return toAjax(fastgptChatQuestionService.insertFastgptChatQuestion(fastgptChatQuestion));
+    }
+
+    /**
+     * 修改聊天问题收集
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastgptChatQuestion:edit')")
+    @Log(title = "聊天问题收集", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FastgptChatQuestion fastgptChatQuestion)
+    {
+        return toAjax(fastgptChatQuestionService.updateFastgptChatQuestion(fastgptChatQuestion));
+    }
+
+    /**
+     * 删除聊天问题收集
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastgptChatQuestion:remove')")
+    @Log(title = "聊天问题收集", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fastgptChatQuestionService.deleteFastgptChatQuestionByIds(ids));
+    }
+}

+ 97 - 0
fs-company/src/main/java/com/fs/company/controller/fastGpt/FastgptChatQuestionController.java

@@ -0,0 +1,97 @@
+package com.fs.company.controller.fastGpt;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.fastGpt.domain.FastgptChatQuestion;
+import com.fs.fastGpt.service.IFastgptChatQuestionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 聊天问题收集Controller
+ *
+ * @author fs
+ * @date 2026-04-20
+ */
+@RestController
+@RequestMapping("/fastGpt/fastgptChatQuestion")
+public class FastgptChatQuestionController extends BaseController
+{
+    @Autowired
+    private IFastgptChatQuestionService fastgptChatQuestionService;
+
+    /**
+     * 查询聊天问题收集列表
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastgptChatQuestion:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FastgptChatQuestion fastgptChatQuestion)
+    {
+        startPage();
+        List<FastgptChatQuestion> list = fastgptChatQuestionService.selectFastgptChatQuestionList(fastgptChatQuestion);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出聊天问题收集列表
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastgptChatQuestion:export')")
+    @Log(title = "聊天问题收集", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FastgptChatQuestion fastgptChatQuestion)
+    {
+        List<FastgptChatQuestion> list = fastgptChatQuestionService.selectFastgptChatQuestionList(fastgptChatQuestion);
+        ExcelUtil<FastgptChatQuestion> util = new ExcelUtil<FastgptChatQuestion>(FastgptChatQuestion.class);
+        return util.exportExcel(list, "聊天问题收集数据");
+    }
+
+    /**
+     * 获取聊天问题收集详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastgptChatQuestion:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fastgptChatQuestionService.selectFastgptChatQuestionById(id));
+    }
+
+    /**
+     * 新增聊天问题收集
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastgptChatQuestion:add')")
+    @Log(title = "聊天问题收集", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FastgptChatQuestion fastgptChatQuestion)
+    {
+        return toAjax(fastgptChatQuestionService.insertFastgptChatQuestion(fastgptChatQuestion));
+    }
+
+    /**
+     * 修改聊天问题收集
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastgptChatQuestion:edit')")
+    @Log(title = "聊天问题收集", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FastgptChatQuestion fastgptChatQuestion)
+    {
+        return toAjax(fastgptChatQuestionService.updateFastgptChatQuestion(fastgptChatQuestion));
+    }
+
+    /**
+     * 删除聊天问题收集
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastgptChatQuestion:remove')")
+    @Log(title = "聊天问题收集", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fastgptChatQuestionService.deleteFastgptChatQuestionByIds(ids));
+    }
+}

+ 103 - 0
fs-company/src/main/java/com/fs/company/controller/fastGpt/FastgptChatQuestionStatisticsController.java

@@ -0,0 +1,103 @@
+package com.fs.company.controller.fastGpt;
+
+import java.util.List;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.fastGpt.domain.FastgptChatQuestionStatistics;
+import com.fs.fastGpt.service.IFastgptChatQuestionStatisticsService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 高频聊天问题统计Controller
+ *
+ * @author fs
+ * @date 2026-04-22
+ */
+@RestController
+@RequestMapping("/fastGpt/fastGptChatQuestionStatistics")
+public class FastgptChatQuestionStatisticsController extends BaseController
+{
+    @Autowired
+    private IFastgptChatQuestionStatisticsService fastgptChatQuestionStatisticsService;
+
+    /**
+     * 查询高频聊天问题统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastGptChatQuestionStatistics:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FastgptChatQuestionStatistics fastgptChatQuestionStatistics)
+    {
+        startPage();
+        List<FastgptChatQuestionStatistics> list = fastgptChatQuestionStatisticsService.selectFastgptChatQuestionStatisticsList(fastgptChatQuestionStatistics);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出高频聊天问题统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastGptChatQuestionStatistics:export')")
+    @Log(title = "高频聊天问题统计", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FastgptChatQuestionStatistics fastgptChatQuestionStatistics)
+    {
+        List<FastgptChatQuestionStatistics> list = fastgptChatQuestionStatisticsService.selectFastgptChatQuestionStatisticsList(fastgptChatQuestionStatistics);
+        ExcelUtil<FastgptChatQuestionStatistics> util = new ExcelUtil<FastgptChatQuestionStatistics>(FastgptChatQuestionStatistics.class);
+        return util.exportExcel(list, "高频聊天问题统计数据");
+    }
+
+    /**
+     * 获取高频聊天问题统计详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastGptChatQuestionStatistics:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fastgptChatQuestionStatisticsService.selectFastgptChatQuestionStatisticsById(id));
+    }
+
+    /**
+     * 新增高频聊天问题统计
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastGptChatQuestionStatistics:add')")
+    @Log(title = "高频聊天问题统计", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FastgptChatQuestionStatistics fastgptChatQuestionStatistics)
+    {
+        return toAjax(fastgptChatQuestionStatisticsService.insertFastgptChatQuestionStatistics(fastgptChatQuestionStatistics));
+    }
+
+    /**
+     * 修改高频聊天问题统计
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastGptChatQuestionStatistics:edit')")
+    @Log(title = "高频聊天问题统计", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FastgptChatQuestionStatistics fastgptChatQuestionStatistics)
+    {
+        return toAjax(fastgptChatQuestionStatisticsService.updateFastgptChatQuestionStatistics(fastgptChatQuestionStatistics));
+    }
+
+    /**
+     * 删除高频聊天问题统计
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastGptChatQuestionStatistics:remove')")
+    @Log(title = "高频聊天问题统计", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fastgptChatQuestionStatisticsService.deleteFastgptChatQuestionStatisticsByIds(ids));
+    }
+}

+ 2 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -140,7 +140,7 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
 
     private static final String userRealLink = "/pages/user/users/becomeVIP?";
 
-    private static final String appRealLink = "/courseH5/pages_course/videovip?course=";
+    private static final String appRealLink = "/appcourse/pages_course/videovip?course=";
     public static final String appShortLink = "/courseH5/pages_course/videovip?s=";
 
     /**
@@ -801,7 +801,7 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
 //            String sortLink = domainName + link.getRealLink().replace("/#","");
 //            return R.ok().put("url", sortLink).put("link", random).put("linkId", link.getLinkId());
             //没人用我先注释了,手动发课 直接用 链接带参数
-            if (CloudHostUtils.hasCloudHostName("中康","蒙牛")){
+            if (CloudHostUtils.hasCloudHostName("中康","蒙牛", "鸿森堂")){
                 String domainName = getDomainName(param.getCompanyUserId(), config);
                 String encodedCourseJson = URLEncoder.encode(courseJson, "UTF-8");
                 String sortLink = domainName +appRealLink+ encodedCourseJson;

+ 70 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastgptChatQuestion.java

@@ -0,0 +1,70 @@
+package com.fs.fastGpt.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 聊天问题收集对象 fastgpt_chat_question
+ *
+ * @author fs
+ * @date 2026-04-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FastgptChatQuestion extends BaseEntity{
+
+    /** 主键id */
+    private Long id;
+
+    /** 会话id */
+    @Excel(name = "会话id")
+    private Long sessionId;
+
+    /** 消息id */
+    @Excel(name = "消息id")
+    private Long msgId;
+
+    /** 外部ID */
+    @Excel(name = "外部ID")
+    private String extId;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private String userId;
+
+    /** 公司ID */
+    @Excel(name = "公司ID")
+    private Long companyId;
+
+    /** 销售ID */
+    @Excel(name = "销售ID")
+    private Long companyUserId;
+
+    /** 角色ID */
+    @Excel(name = "角色ID")
+    private Long roleId;
+
+    /** 昵称 */
+    @Excel(name = "昵称")
+    private String nickName;
+
+    /** 用户类型 1微信用户 2小程序用户 3销售用户 */
+    @Excel(name = "用户类型 1微信用户 2小程序用户 3销售用户")
+    private Integer userType;
+
+    /** 客户内容 */
+    @Excel(name = "客户内容")
+    private String userContent;
+
+    /** 销售回复内容 */
+    @Excel(name = "销售回复内容")
+    private String companyUserContent;
+
+    /** 高频问题统计id,关联高频聊天问题统计表 */
+    @Excel(name = "高频问题统计id")
+    private Long questionStatisticsId;
+
+}

+ 50 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastgptChatQuestionStatistics.java

@@ -0,0 +1,50 @@
+package com.fs.fastGpt.domain;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 高频聊天问题统计对象 fastgpt_chat_question_statistics
+ *
+ * @author fs
+ * @date 2026-04-22
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FastgptChatQuestionStatistics extends BaseEntity{
+
+    /** 主键id */
+    private Long id;
+
+    /** 所属类别,用数字区分类别 */
+    @Excel(name = "所属类别,用数字区分类别")
+    private Integer questionCategory;
+
+    /** 来源 */
+    @Excel(name = "来源")
+    private Integer source;
+
+    /** 内容摘要 */
+    @Excel(name = "内容摘要")
+    private String contentSummary;
+
+    /** 归类指纹(SimHash 64bit) */
+    @Excel(name = "归类指纹")
+    private Long simhash;
+
+    /** 频次,持续增加 */
+    @Excel(name = "频次,持续增加")
+    private Integer frequency;
+
+    /** 是否解决,1-是,0-否 */
+    @Excel(name = "是否解决,1-是,0-否")
+    private Integer isResolve;
+
+    /** 聊天问题id,关联聊天问题收集表 */
+    @Excel(name = "聊天问题id,关联聊天问题收集表")
+    private Long questionId;
+
+
+}

+ 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 ")
     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 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,user_info from fastgpt_role r LEFT JOIN fastgpt_role_type t on t.id =r.role_type where role_id = #{roleId}")
     FastGptRole selectFastGptRoleTypeByRoleId(Long roleId);
 
     List<FastGptRole> selectFastGptRoleByRoleIds(@Param("roleIds") List<Long> roleIds);

+ 64 - 0
fs-service/src/main/java/com/fs/fastGpt/mapper/FastgptChatQuestionMapper.java

@@ -0,0 +1,64 @@
+package com.fs.fastGpt.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.fastGpt.domain.FastgptChatQuestion;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 聊天问题收集Mapper接口
+ * 
+ * @author fs
+ * @date 2026-04-20
+ */
+public interface FastgptChatQuestionMapper extends BaseMapper<FastgptChatQuestion>{
+    /**
+     * 查询聊天问题收集
+     * 
+     * @param id 聊天问题收集主键
+     * @return 聊天问题收集
+     */
+    FastgptChatQuestion selectFastgptChatQuestionById(Long id);
+
+    /**
+     * 查询聊天问题收集列表
+     * 
+     * @param fastgptChatQuestion 聊天问题收集
+     * @return 聊天问题收集集合
+     */
+    List<FastgptChatQuestion> selectFastgptChatQuestionList(FastgptChatQuestion fastgptChatQuestion);
+
+    /**
+     * 新增聊天问题收集
+     * 
+     * @param fastgptChatQuestion 聊天问题收集
+     * @return 结果
+     */
+    int insertFastgptChatQuestion(FastgptChatQuestion fastgptChatQuestion);
+
+    /**
+     * 修改聊天问题收集
+     * 
+     * @param fastgptChatQuestion 聊天问题收集
+     * @return 结果
+     */
+    int updateFastgptChatQuestion(FastgptChatQuestion fastgptChatQuestion);
+
+    /**
+     * 删除聊天问题收集
+     * 
+     * @param id 聊天问题收集主键
+     * @return 结果
+     */
+    int deleteFastgptChatQuestionById(Long id);
+
+    /**
+     * 批量删除聊天问题收集
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFastgptChatQuestionByIds(Long[] ids);
+
+    int updateQuestionStatisticsIdById(@Param("id") Long id, @Param("questionStatisticsId") Long questionStatisticsId);
+}

+ 68 - 0
fs-service/src/main/java/com/fs/fastGpt/mapper/FastgptChatQuestionStatisticsMapper.java

@@ -0,0 +1,68 @@
+package com.fs.fastGpt.mapper;
+
+import java.util.Date;
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.fastGpt.domain.FastgptChatQuestionStatistics;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 高频聊天问题统计Mapper接口
+ *
+ * @author fs
+ * @date 2026-04-22
+ */
+public interface FastgptChatQuestionStatisticsMapper extends BaseMapper<FastgptChatQuestionStatistics>{
+    /**
+     * 查询高频聊天问题统计
+     *
+     * @param id 高频聊天问题统计主键
+     * @return 高频聊天问题统计
+     */
+    FastgptChatQuestionStatistics selectFastgptChatQuestionStatisticsById(Long id);
+
+    /**
+     * 查询高频聊天问题统计列表
+     *
+     * @param fastgptChatQuestionStatistics 高频聊天问题统计
+     * @return 高频聊天问题统计集合
+     */
+    List<FastgptChatQuestionStatistics> selectFastgptChatQuestionStatisticsList(FastgptChatQuestionStatistics fastgptChatQuestionStatistics);
+
+    /**
+     * 新增高频聊天问题统计
+     *
+     * @param fastgptChatQuestionStatistics 高频聊天问题统计
+     * @return 结果
+     */
+    int insertFastgptChatQuestionStatistics(FastgptChatQuestionStatistics fastgptChatQuestionStatistics);
+
+    /**
+     * 修改高频聊天问题统计
+     *
+     * @param fastgptChatQuestionStatistics 高频聊天问题统计
+     * @return 结果
+     */
+    int updateFastgptChatQuestionStatistics(FastgptChatQuestionStatistics fastgptChatQuestionStatistics);
+
+    /**
+     * 删除高频聊天问题统计
+     *
+     * @param id 高频聊天问题统计主键
+     * @return 结果
+     */
+    int deleteFastgptChatQuestionStatisticsById(Long id);
+
+    /**
+     * 批量删除高频聊天问题统计
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFastgptChatQuestionStatisticsByIds(Long[] ids);
+
+    FastgptChatQuestionStatistics selectBestMatchBySimhash(@Param("simhash") Long simhash,
+                                                         @Param("threshold") Integer threshold);
+
+    int incrementFrequencyById(@Param("id") Long id, @Param("updateTime") Date updateTime);
+}

+ 20 - 0
fs-service/src/main/java/com/fs/fastGpt/param/FastgptKnowledgeMissCollectParam.java

@@ -0,0 +1,20 @@
+package com.fs.fastGpt.param;
+
+import lombok.Data;
+
+@Data
+public class FastgptKnowledgeMissCollectParam {
+
+    private String aiUserContent;
+    private String contentEmj;
+
+    private Long sessionId;
+    private Long msgId;
+    private String extId;
+    private String userId;
+    private Long companyId;
+    private Long companyUserId;
+    private Long roleId;
+    private String nickName;
+    private Integer userType;
+}

+ 61 - 0
fs-service/src/main/java/com/fs/fastGpt/service/IFastgptChatQuestionService.java

@@ -0,0 +1,61 @@
+package com.fs.fastGpt.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.fastGpt.domain.FastgptChatQuestion;
+
+/**
+ * 聊天问题收集Service接口
+ * 
+ * @author fs
+ * @date 2026-04-20
+ */
+public interface IFastgptChatQuestionService extends IService<FastgptChatQuestion>{
+    /**
+     * 查询聊天问题收集
+     * 
+     * @param id 聊天问题收集主键
+     * @return 聊天问题收集
+     */
+    FastgptChatQuestion selectFastgptChatQuestionById(Long id);
+
+    /**
+     * 查询聊天问题收集列表
+     * 
+     * @param fastgptChatQuestion 聊天问题收集
+     * @return 聊天问题收集集合
+     */
+    List<FastgptChatQuestion> selectFastgptChatQuestionList(FastgptChatQuestion fastgptChatQuestion);
+
+    /**
+     * 新增聊天问题收集
+     * 
+     * @param fastgptChatQuestion 聊天问题收集
+     * @return 结果
+     */
+    int insertFastgptChatQuestion(FastgptChatQuestion fastgptChatQuestion);
+
+    /**
+     * 修改聊天问题收集
+     * 
+     * @param fastgptChatQuestion 聊天问题收集
+     * @return 结果
+     */
+    int updateFastgptChatQuestion(FastgptChatQuestion fastgptChatQuestion);
+
+    /**
+     * 批量删除聊天问题收集
+     * 
+     * @param ids 需要删除的聊天问题收集主键集合
+     * @return 结果
+     */
+    int deleteFastgptChatQuestionByIds(Long[] ids);
+
+    /**
+     * 删除聊天问题收集信息
+     * 
+     * @param id 聊天问题收集主键
+     * @return 结果
+     */
+    int deleteFastgptChatQuestionById(Long id);
+}

+ 61 - 0
fs-service/src/main/java/com/fs/fastGpt/service/IFastgptChatQuestionStatisticsService.java

@@ -0,0 +1,61 @@
+package com.fs.fastGpt.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.fastGpt.domain.FastgptChatQuestionStatistics;
+
+/**
+ * 高频聊天问题统计Service接口
+ * 
+ * @author fs
+ * @date 2026-04-22
+ */
+public interface IFastgptChatQuestionStatisticsService extends IService<FastgptChatQuestionStatistics>{
+    /**
+     * 查询高频聊天问题统计
+     * 
+     * @param id 高频聊天问题统计主键
+     * @return 高频聊天问题统计
+     */
+    FastgptChatQuestionStatistics selectFastgptChatQuestionStatisticsById(Long id);
+
+    /**
+     * 查询高频聊天问题统计列表
+     * 
+     * @param fastgptChatQuestionStatistics 高频聊天问题统计
+     * @return 高频聊天问题统计集合
+     */
+    List<FastgptChatQuestionStatistics> selectFastgptChatQuestionStatisticsList(FastgptChatQuestionStatistics fastgptChatQuestionStatistics);
+
+    /**
+     * 新增高频聊天问题统计
+     * 
+     * @param fastgptChatQuestionStatistics 高频聊天问题统计
+     * @return 结果
+     */
+    int insertFastgptChatQuestionStatistics(FastgptChatQuestionStatistics fastgptChatQuestionStatistics);
+
+    /**
+     * 修改高频聊天问题统计
+     * 
+     * @param fastgptChatQuestionStatistics 高频聊天问题统计
+     * @return 结果
+     */
+    int updateFastgptChatQuestionStatistics(FastgptChatQuestionStatistics fastgptChatQuestionStatistics);
+
+    /**
+     * 批量删除高频聊天问题统计
+     * 
+     * @param ids 需要删除的高频聊天问题统计主键集合
+     * @return 结果
+     */
+    int deleteFastgptChatQuestionStatisticsByIds(Long[] ids);
+
+    /**
+     * 删除高频聊天问题统计信息
+     * 
+     * @param id 高频聊天问题统计主键
+     * @return 结果
+     */
+    int deleteFastgptChatQuestionStatisticsById(Long id);
+}

+ 22 - 13
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -5,7 +5,6 @@ import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.fs.common.annotation.Excel;
-import com.fs.common.config.FSConfig;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.company.domain.CompanyConfig;
@@ -27,6 +26,7 @@ import com.fs.fastGpt.domain.*;
 import com.fs.fastGpt.mapper.FastGptChatReplaceWordsMapper;
 import com.fs.fastGpt.mapper.FastGptChatSessionMapper;
 import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
+import com.fs.fastGpt.param.FastgptKnowledgeMissCollectParam;
 import com.fs.fastGpt.param.SendAIParam;
 import com.fs.fastGpt.service.*;
 import com.fs.fastgptApi.param.ChatParam;
@@ -41,13 +41,11 @@ import com.fs.his.domain.FsStoreOrder;
 import com.fs.his.dto.ExpressInfoDTO;
 import com.fs.his.dto.TracesDTO;
 import com.fs.his.enums.ShipperCodeEnum;
-import com.fs.his.mapper.FsStoreMapper;
 import com.fs.his.mapper.FsStoreOrderMapper;
 import com.fs.his.service.IFsExpressService;
 import com.fs.his.service.IFsStoreOrderService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.hisStore.enums.SysConfigEnum;
-import com.fs.im.dto.OpenImMsgDTO;
 import com.fs.im.vo.OpenImMsgCallBackVO;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
@@ -62,14 +60,12 @@ import com.fs.qwHookApi.vo.QwHookMsgVO;
 import com.fs.qwHookApi.vo.QwHookVO;
 import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.mapper.QwSopLogsMapper;
-import com.fs.utils.SensitiveDataUtils;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxwork.dto.*;
 import com.fs.wxwork.service.WxWorkService;
 import com.google.gson.Gson;
 import com.vdurmont.emoji.EmojiParser;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.Nullable;
 import org.json.JSONObject;
@@ -77,7 +73,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
-
 import java.lang.reflect.Field;
 import java.sql.Time;
 import java.time.DayOfWeek;
@@ -188,6 +183,9 @@ public class AiHookServiceImpl implements AiHookService {
     @Autowired
     private ConfigUtil configUtil;
 
+    @Autowired
+    private FastgptChatQuestionCollectServiceImpl fastgptChatQuestionCollectServiceImpl;
+
 
     /** Ai半小时未回复提醒 **/
     /**
@@ -602,6 +600,22 @@ public class AiHookServiceImpl implements AiHookService {
                 Gson gson = new Gson();
                 FastGptChatConversation fastGptChatConversation = gson.fromJson(contentKh, FastGptChatConversation.class);
                 content = fastGptChatConversation.getAiContent();
+
+                // 知识库未命中:异步写明细 + 归类统计(不阻塞主流程)
+                if ("0".equals(String.valueOf(fastGptChatConversation.getIsRepository()))) {
+                    FastgptKnowledgeMissCollectParam p = new FastgptKnowledgeMissCollectParam();
+                    p.setAiUserContent(fastGptChatConversation.getUserContent());
+                    p.setContentEmj(contentEmj);
+                    p.setSessionId(fastGptChatSession.getSessionId());
+                    p.setExtId(qwExternalContacts.getExternalUserId());
+                    p.setUserId(fastGptChatSession.getUserId());
+                    p.setCompanyId(qwExternalContacts.getCompanyId());
+                    p.setCompanyUserId(qwExternalContacts.getCompanyUserId());
+                    p.setRoleId(role.getRoleId());
+                    p.setNickName(qwExternalContacts.getName());
+                    p.setUserType(fastGptChatSession.getUserType());
+                    fastgptChatQuestionCollectServiceImpl.collectAsync(p);
+                }
             }else{
                 content = replace(result.getChoices().get(0).getMessage().getContent()).trim();
             }
@@ -688,11 +702,6 @@ public class AiHookServiceImpl implements AiHookService {
                 }
             }
 
-            //
-            // todo 把当前的内容转成jsonObject,然后取出isRepository,判断是否为知识库,如果不是知识库的,就存储到表中
-            // 什么时候销售回复,销售回复的在哪里可以看到
-//            contentKh
-
             aiEventProcess(sender, uid, role, contentKh, user, fastGptChatSession, serverId,qwExternalContacts);
 
 
@@ -1609,7 +1618,7 @@ public class AiHookServiceImpl implements AiHookService {
         Integer reply = (Integer)redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
         if (reply!=i){
             //次数变动 重新等待5秒
-            R r = sendAiMsg(reply, fastGptChatSession, role, user, qwExternalContactsId, appKey, qwExternalContacts,sender);
+            R r = sendAiMsgNew(reply, fastGptChatSession, role, user, qwExternalContactsId, appKey, qwExternalContacts,sender);
             return r;
         }else {
             System.out.println("开始ai回答");
@@ -1650,7 +1659,7 @@ public class AiHookServiceImpl implements AiHookService {
             //次数变动 重新等待5秒
             if (reply2!=i){
                 System.out.println("等待");
-                R r1 = sendAiMsg(reply, fastGptChatSession, role, user, qwExternalContactsId, appKey, qwExternalContacts,sender);
+                R r1 = sendAiMsgNew(reply, fastGptChatSession, role, user, qwExternalContactsId, appKey, qwExternalContacts,sender);
                 return r1;
             }
             addSaveAiMsg(2,1,messageList.get(0).getContent(),user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),null,null,null);

+ 111 - 0
fs-service/src/main/java/com/fs/fastGpt/service/impl/FastgptChatQuestionCollectServiceImpl.java

@@ -0,0 +1,111 @@
+package com.fs.fastGpt.service.impl;
+
+import com.fs.common.BeanCopyUtils;
+import com.fs.common.utils.DateUtils;
+import com.fs.fastGpt.domain.FastgptChatQuestion;
+import com.fs.fastGpt.domain.FastgptChatQuestionStatistics;
+import com.fs.fastGpt.mapper.FastgptChatQuestionMapper;
+import com.fs.fastGpt.mapper.FastgptChatQuestionStatisticsMapper;
+import com.fs.fastGpt.param.FastgptKnowledgeMissCollectParam;
+import com.fs.fastGpt.service.IFastgptChatQuestionService;
+import com.fs.fastGpt.util.FastgptQuestionNormalizeUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+
+/**
+ * 知识库未命中:异步写入明细,按 content_summary 归并统计并回填 question_statistics_id
+ */
+@Slf4j
+@Service
+public class FastgptChatQuestionCollectServiceImpl {
+
+    private static final int SIMHASH_THRESHOLD = 14;
+    private static final double JACCARD_THRESHOLD = 0.55d;
+
+
+    @Autowired
+    private IFastgptChatQuestionService fastgptChatQuestionService;
+
+    @Autowired
+    private FastgptChatQuestionStatisticsMapper fastgptChatQuestionStatisticsMapper;
+
+    @Autowired
+    private FastgptChatQuestionMapper fastgptChatQuestionMapper;
+
+    @Async("threadPoolTaskExecutor")
+    @Transactional(rollbackFor = Exception.class)
+    public void collectAsync(FastgptKnowledgeMissCollectParam param) {
+        if (param == null) {
+            return;
+        }
+        try {
+            String display = StringUtils.isNotBlank(param.getAiUserContent())
+                    ? param.getAiUserContent().trim()
+                    : param.getContentEmj();
+            if (StringUtils.isBlank(display)) {
+                log.warn("知识库未命中收集跳过:问题文本为空 sessionId={}", param.getSessionId());
+                return;
+            }
+
+            FastgptChatQuestion q = buildQuestion(param, display);
+            fastgptChatQuestionService.insertFastgptChatQuestion(q);
+            Long detailId = q.getId();
+            if (detailId == null) {
+                log.error("知识库未命中收集失败:未生成明细主键 sessionId={}", param.getSessionId());
+                return;
+            }
+
+            Date now = DateUtils.getNowDate();
+            long sh = FastgptQuestionNormalizeUtil.simhash64(display);
+            FastgptChatQuestionStatistics best = fastgptChatQuestionStatisticsMapper.selectBestMatchBySimhash(sh, SIMHASH_THRESHOLD);
+            Long statId;
+            if (best != null && best.getId() != null) {
+                double jac = FastgptQuestionNormalizeUtil.jaccard(
+                        FastgptQuestionNormalizeUtil.ngramTokens(display),
+                        FastgptQuestionNormalizeUtil.ngramTokens(best.getContentSummary())
+                );
+                if (jac < JACCARD_THRESHOLD) {
+                    best = null;
+                }
+            }
+            if (best != null && best.getId() != null) {
+                statId = best.getId();
+                fastgptChatQuestionStatisticsMapper.incrementFrequencyById(statId, now);
+            } else {
+                FastgptChatQuestionStatistics row = new FastgptChatQuestionStatistics();
+                row.setQuestionCategory(0);
+                row.setContentSummary(display.length() > 200 ? display.substring(0, 200) : display);
+                row.setSimhash(sh);
+                row.setIsResolve(0);
+                row.setQuestionId(detailId);
+                row.setFrequency(1);
+                row.setCreateTime(now);
+                row.setUpdateTime(now);
+                fastgptChatQuestionStatisticsMapper.insertFastgptChatQuestionStatistics(row);
+                statId = row.getId();
+                if (statId == null) {
+                    FastgptChatQuestionStatistics insertedBest = fastgptChatQuestionStatisticsMapper.selectBestMatchBySimhash(sh, SIMHASH_THRESHOLD);
+                    statId = insertedBest != null ? insertedBest.getId() : null;
+                }
+            }
+            if (statId != null) {
+                fastgptChatQuestionMapper.updateQuestionStatisticsIdById(detailId, statId);
+            }
+        } catch (Exception e) {
+            log.error("知识库未命中异步收集异常 sessionId={}", param.getSessionId(), e);
+        }
+    }
+
+    private static FastgptChatQuestion buildQuestion(FastgptKnowledgeMissCollectParam param, String userContent) {
+        FastgptChatQuestion q = new FastgptChatQuestion();
+        BeanCopyUtils.copy(param, q);
+        q.setUserContent(userContent);
+        return q;
+    }
+}

+ 93 - 0
fs-service/src/main/java/com/fs/fastGpt/service/impl/FastgptChatQuestionServiceImpl.java

@@ -0,0 +1,93 @@
+package com.fs.fastGpt.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.fastGpt.mapper.FastgptChatQuestionMapper;
+import com.fs.fastGpt.domain.FastgptChatQuestion;
+import com.fs.fastGpt.service.IFastgptChatQuestionService;
+
+/**
+ * 聊天问题收集Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-04-20
+ */
+@Service
+public class FastgptChatQuestionServiceImpl extends ServiceImpl<FastgptChatQuestionMapper, FastgptChatQuestion> implements IFastgptChatQuestionService {
+
+    /**
+     * 查询聊天问题收集
+     * 
+     * @param id 聊天问题收集主键
+     * @return 聊天问题收集
+     */
+    @Override
+    public FastgptChatQuestion selectFastgptChatQuestionById(Long id)
+    {
+        return baseMapper.selectFastgptChatQuestionById(id);
+    }
+
+    /**
+     * 查询聊天问题收集列表
+     * 
+     * @param fastgptChatQuestion 聊天问题收集
+     * @return 聊天问题收集
+     */
+    @Override
+    public List<FastgptChatQuestion> selectFastgptChatQuestionList(FastgptChatQuestion fastgptChatQuestion)
+    {
+        return baseMapper.selectFastgptChatQuestionList(fastgptChatQuestion);
+    }
+
+    /**
+     * 新增聊天问题收集
+     * 
+     * @param fastgptChatQuestion 聊天问题收集
+     * @return 结果
+     */
+    @Override
+    public int insertFastgptChatQuestion(FastgptChatQuestion fastgptChatQuestion)
+    {
+        fastgptChatQuestion.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFastgptChatQuestion(fastgptChatQuestion);
+    }
+
+    /**
+     * 修改聊天问题收集
+     * 
+     * @param fastgptChatQuestion 聊天问题收集
+     * @return 结果
+     */
+    @Override
+    public int updateFastgptChatQuestion(FastgptChatQuestion fastgptChatQuestion)
+    {
+        return baseMapper.updateFastgptChatQuestion(fastgptChatQuestion);
+    }
+
+    /**
+     * 批量删除聊天问题收集
+     * 
+     * @param ids 需要删除的聊天问题收集主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFastgptChatQuestionByIds(Long[] ids)
+    {
+        return baseMapper.deleteFastgptChatQuestionByIds(ids);
+    }
+
+    /**
+     * 删除聊天问题收集信息
+     * 
+     * @param id 聊天问题收集主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFastgptChatQuestionById(Long id)
+    {
+        return baseMapper.deleteFastgptChatQuestionById(id);
+    }
+}

+ 94 - 0
fs-service/src/main/java/com/fs/fastGpt/service/impl/FastgptChatQuestionStatisticsServiceImpl.java

@@ -0,0 +1,94 @@
+package com.fs.fastGpt.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.fastGpt.mapper.FastgptChatQuestionStatisticsMapper;
+import com.fs.fastGpt.domain.FastgptChatQuestionStatistics;
+import com.fs.fastGpt.service.IFastgptChatQuestionStatisticsService;
+
+/**
+ * 高频聊天问题统计Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-04-22
+ */
+@Service
+public class FastgptChatQuestionStatisticsServiceImpl extends ServiceImpl<FastgptChatQuestionStatisticsMapper, FastgptChatQuestionStatistics> implements IFastgptChatQuestionStatisticsService {
+
+    /**
+     * 查询高频聊天问题统计
+     * 
+     * @param id 高频聊天问题统计主键
+     * @return 高频聊天问题统计
+     */
+    @Override
+    public FastgptChatQuestionStatistics selectFastgptChatQuestionStatisticsById(Long id)
+    {
+        return baseMapper.selectFastgptChatQuestionStatisticsById(id);
+    }
+
+    /**
+     * 查询高频聊天问题统计列表
+     * 
+     * @param fastgptChatQuestionStatistics 高频聊天问题统计
+     * @return 高频聊天问题统计
+     */
+    @Override
+    public List<FastgptChatQuestionStatistics> selectFastgptChatQuestionStatisticsList(FastgptChatQuestionStatistics fastgptChatQuestionStatistics)
+    {
+        return baseMapper.selectFastgptChatQuestionStatisticsList(fastgptChatQuestionStatistics);
+    }
+
+    /**
+     * 新增高频聊天问题统计
+     * 
+     * @param fastgptChatQuestionStatistics 高频聊天问题统计
+     * @return 结果
+     */
+    @Override
+    public int insertFastgptChatQuestionStatistics(FastgptChatQuestionStatistics fastgptChatQuestionStatistics)
+    {
+        fastgptChatQuestionStatistics.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFastgptChatQuestionStatistics(fastgptChatQuestionStatistics);
+    }
+
+    /**
+     * 修改高频聊天问题统计
+     * 
+     * @param fastgptChatQuestionStatistics 高频聊天问题统计
+     * @return 结果
+     */
+    @Override
+    public int updateFastgptChatQuestionStatistics(FastgptChatQuestionStatistics fastgptChatQuestionStatistics)
+    {
+        fastgptChatQuestionStatistics.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFastgptChatQuestionStatistics(fastgptChatQuestionStatistics);
+    }
+
+    /**
+     * 批量删除高频聊天问题统计
+     * 
+     * @param ids 需要删除的高频聊天问题统计主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFastgptChatQuestionStatisticsByIds(Long[] ids)
+    {
+        return baseMapper.deleteFastgptChatQuestionStatisticsByIds(ids);
+    }
+
+    /**
+     * 删除高频聊天问题统计信息
+     * 
+     * @param id 高频聊天问题统计主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFastgptChatQuestionStatisticsById(Long id)
+    {
+        return baseMapper.deleteFastgptChatQuestionStatisticsById(id);
+    }
+}

+ 165 - 0
fs-service/src/main/java/com/fs/fastGpt/util/FastgptQuestionNormalizeUtil.java

@@ -0,0 +1,165 @@
+package com.fs.fastGpt.util;
+
+import java.text.Normalizer;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+public final class FastgptQuestionNormalizeUtil {
+
+    private FastgptQuestionNormalizeUtil() {
+    }
+
+    private static final int MAX_KEY_LEN = 500;
+
+    // NFKC + 小写 + 去标点空白,写入 statistics.content_summary 做唯一命中
+    public static String normalizeContentSummaryKey(String raw) {
+        if (raw == null) {
+            return "";
+        }
+        String s = Normalizer.normalize(raw.trim(), Normalizer.Form.NFKC).toLowerCase(Locale.ROOT);
+        s = s.replaceAll("[\\p{P}\\p{Z}\\s]+", "");
+        if (s.length() > MAX_KEY_LEN) {
+            return s.substring(0, MAX_KEY_LEN);
+        }
+        return s;
+    }
+
+    // SimHash(64bit),用于“同一意思”近似归类
+    public static long simhash64(String raw) {
+        if (raw == null) {
+            return 0L;
+        }
+        String s = Normalizer.normalize(raw.trim(), Normalizer.Form.NFKC).toLowerCase(Locale.ROOT);
+        if (s.isEmpty()) {
+            return 0L;
+        }
+
+        String[] parts = s.split("[^\\p{IsHan}\\p{Alnum}]+");
+        int[] v = new int[64];
+        for (String p : parts) {
+            if (p == null) {
+                continue;
+            }
+            p = p.trim();
+            if (p.isEmpty()) {
+                continue;
+            }
+
+            // 中文用 2~3 字 n-gram,增强“措辞不同但同义”的重叠度
+            if (p.matches(".*\\p{IsHan}.*")) {
+                String han = p.replaceAll("[^\\p{IsHan}]+", "");
+                if (han.length() == 1) {
+                    addTokenVector(han, v);
+                } else {
+                    for (int i = 0; i < han.length() - 1; i++) {
+                        addTokenVector(han.substring(i, i + 2), v);
+                    }
+                    for (int i = 0; i < han.length() - 2; i++) {
+                        addTokenVector(han.substring(i, i + 3), v);
+                    }
+                }
+                String alnum = p.replaceAll("[^\\p{Alnum}]+", "");
+                if (!alnum.isEmpty()) {
+                    addTokenVector(alnum, v);
+                }
+                continue;
+            }
+
+            addTokenVector(p, v);
+        }
+        long out = 0L;
+        for (int i = 0; i < 64; i++) {
+            if (v[i] >= 0) {
+                out |= (1L << i);
+            }
+        }
+        return out;
+    }
+
+    // 用于 Jaccard 二次确认的 token 集合(2~3 字 n-gram + 英数字串)
+    public static Set<String> ngramTokens(String raw) {
+        Set<String> out = new HashSet<>();
+        if (raw == null) {
+            return out;
+        }
+        String s = Normalizer.normalize(raw.trim(), Normalizer.Form.NFKC).toLowerCase(Locale.ROOT);
+        if (s.isEmpty()) {
+            return out;
+        }
+        String[] parts = s.split("[^\\p{IsHan}\\p{Alnum}]+");
+        for (String p : parts) {
+            if (p == null) {
+                continue;
+            }
+            p = p.trim();
+            if (p.isEmpty()) {
+                continue;
+            }
+            String han = p.replaceAll("[^\\p{IsHan}]+", "");
+            if (!han.isEmpty()) {
+                if (han.length() == 1) {
+                    out.add(han);
+                } else {
+                    for (int i = 0; i < han.length() - 1; i++) {
+                        out.add(han.substring(i, i + 2));
+                    }
+                    for (int i = 0; i < han.length() - 2; i++) {
+                        out.add(han.substring(i, i + 3));
+                    }
+                }
+            }
+            String alnum = p.replaceAll("[^\\p{Alnum}]+", "");
+            if (!alnum.isEmpty()) {
+                out.add(alnum);
+            }
+        }
+        return out;
+    }
+
+    public static double jaccard(Set<String> a, Set<String> b) {
+        if (a == null || b == null || a.isEmpty() || b.isEmpty()) {
+            return 0.0d;
+        }
+        int inter = 0;
+        if (a.size() <= b.size()) {
+            for (String x : a) {
+                if (b.contains(x)) {
+                    inter++;
+                }
+            }
+        } else {
+            for (String x : b) {
+                if (a.contains(x)) {
+                    inter++;
+                }
+            }
+        }
+        int union = a.size() + b.size() - inter;
+        return union <= 0 ? 0.0d : (inter * 1.0d / union);
+    }
+
+    private static void addTokenVector(String t, int[] v) {
+        if (t == null) {
+            return;
+        }
+        t = t.trim();
+        if (t.isEmpty()) {
+            return;
+        }
+        long h = fnv1a64(t);
+        for (int i = 0; i < 64; i++) {
+            long bit = (h >>> i) & 1L;
+            v[i] += bit == 1L ? 1 : -1;
+        }
+    }
+
+    private static long fnv1a64(String s) {
+        long h = 0xcbf29ce484222325L;
+        for (int i = 0; i < s.length(); i++) {
+            h ^= s.charAt(i);
+            h *= 0x100000001b3L;
+        }
+        return h;
+    }
+}

+ 121 - 0
fs-service/src/main/resources/mapper/fastGpt/FastgptChatQuestionMapper.xml

@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.fastGpt.mapper.FastgptChatQuestionMapper">
+
+    <resultMap type="FastgptChatQuestion" id="FastgptChatQuestionResult">
+        <result property="id"    column="id"    />
+        <result property="sessionId"    column="session_id"    />
+        <result property="msgId"    column="msg_id"    />
+        <result property="extId"    column="ext_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="roleId"    column="role_id"    />
+        <result property="nickName"    column="nick_name"    />
+        <result property="userType"    column="user_type"    />
+        <result property="userContent"    column="user_content"    />
+        <result property="companyUserContent"    column="company_user_content"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="questionStatisticsId"    column="question_statistics_id"    />
+    </resultMap>
+
+    <sql id="selectFastgptChatQuestionVo">
+        select id, session_id, msg_id, ext_id, user_id, company_id, company_user_id, role_id, nick_name, user_type, user_content, company_user_content, create_time, question_statistics_id from fastgpt_chat_question
+    </sql>
+
+    <select id="selectFastgptChatQuestionList" parameterType="FastgptChatQuestion" resultMap="FastgptChatQuestionResult">
+        <include refid="selectFastgptChatQuestionVo"/>
+        <where>
+            <if test="sessionId != null "> and session_id = #{sessionId}</if>
+            <if test="msgId != null "> and msg_id = #{msgId}</if>
+            <if test="extId != null  and extId != ''"> and ext_id = #{extId}</if>
+            <if test="userId != null  and userId != ''"> and user_id = #{userId}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="roleId != null "> and role_id = #{roleId}</if>
+            <if test="nickName != null  and nickName != ''"> and nick_name like concat('%', #{nickName}, '%')</if>
+            <if test="userType != null "> and user_type = #{userType}</if>
+            <if test="userContent != null  and userContent != ''"> and user_content = #{userContent}</if>
+            <if test="companyUserContent != null  and companyUserContent != ''"> and company_user_content = #{companyUserContent}</if>
+            <if test="questionStatisticsId != null  and questionStatisticsId != ''"> and question_statistics_id = #{questionStatisticsId}</if>
+        </where>
+    </select>
+
+    <select id="selectFastgptChatQuestionById" parameterType="Long" resultMap="FastgptChatQuestionResult">
+        <include refid="selectFastgptChatQuestionVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertFastgptChatQuestion" parameterType="FastgptChatQuestion" useGeneratedKeys="true" keyProperty="id">
+        insert into fastgpt_chat_question
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="sessionId != null">session_id,</if>
+            <if test="msgId != null">msg_id,</if>
+            <if test="extId != null">ext_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="roleId != null">role_id,</if>
+            <if test="nickName != null">nick_name,</if>
+            <if test="userType != null">user_type,</if>
+            <if test="userContent != null">user_content,</if>
+            <if test="companyUserContent != null">company_user_content,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="questionStatisticsId != null">question_statistics_id,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="sessionId != null">#{sessionId},</if>
+            <if test="msgId != null">#{msgId},</if>
+            <if test="extId != null">#{extId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="roleId != null">#{roleId},</if>
+            <if test="nickName != null">#{nickName},</if>
+            <if test="userType != null">#{userType},</if>
+            <if test="userContent != null">#{userContent},</if>
+            <if test="companyUserContent != null">#{companyUserContent},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="questionStatisticsId != null">#{questionStatisticsId},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFastgptChatQuestion" parameterType="FastgptChatQuestion">
+        update fastgpt_chat_question
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="sessionId != null">session_id = #{sessionId},</if>
+            <if test="msgId != null">msg_id = #{msgId},</if>
+            <if test="extId != null">ext_id = #{extId},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="roleId != null">role_id = #{roleId},</if>
+            <if test="nickName != null">nick_name = #{nickName},</if>
+            <if test="userType != null">user_type = #{userType},</if>
+            <if test="userContent != null">user_content = #{userContent},</if>
+            <if test="companyUserContent != null">company_user_content = #{companyUserContent},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="questionStatisticsId != null">question_statistics_id = #{questionStatisticsId},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFastgptChatQuestionById" parameterType="Long">
+        delete from fastgpt_chat_question where id = #{id}
+    </delete>
+
+    <delete id="deleteFastgptChatQuestionByIds" parameterType="String">
+        delete from fastgpt_chat_question where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <update id="updateQuestionStatisticsIdById">
+        update fastgpt_chat_question
+        set question_statistics_id = #{questionStatisticsId}
+        where id = #{id}
+    </update>
+</mapper>

+ 110 - 0
fs-service/src/main/resources/mapper/fastGpt/FastgptChatQuestionStatisticsMapper.xml

@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.fastGpt.mapper.FastgptChatQuestionStatisticsMapper">
+
+    <resultMap type="FastgptChatQuestionStatistics" id="FastgptChatQuestionStatisticsResult">
+        <result property="id"    column="id"    />
+        <result property="questionCategory"    column="question_category"    />
+        <result property="source"    column="source"    />
+        <result property="contentSummary"    column="content_summary"    />
+        <result property="simhash"    column="simhash"    />
+        <result property="frequency"    column="frequency"    />
+        <result property="isResolve"    column="is_resolve"    />
+        <result property="questionId"    column="question_id"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectFastgptChatQuestionStatisticsVo">
+        select id, question_category, source, content_summary, simhash, frequency, is_resolve, question_id, create_time, update_time from fastgpt_chat_question_statistics
+    </sql>
+
+    <select id="selectFastgptChatQuestionStatisticsList" parameterType="FastgptChatQuestionStatistics" resultMap="FastgptChatQuestionStatisticsResult">
+        <include refid="selectFastgptChatQuestionStatisticsVo"/>
+        <where>
+            <if test="questionCategory != null "> and question_category = #{questionCategory}</if>
+            <if test="source != null "> and source = #{source}</if>
+            <if test="contentSummary != null  and contentSummary != ''"> and content_summary = #{contentSummary}</if>
+            <if test="frequency != null "> and frequency = #{frequency}</if>
+            <if test="isResolve != null "> and is_resolve = #{isResolve}</if>
+            <if test="questionId != null "> and question_id = #{questionId}</if>
+        </where>
+    </select>
+
+    <select id="selectFastgptChatQuestionStatisticsById" parameterType="Long" resultMap="FastgptChatQuestionStatisticsResult">
+        <include refid="selectFastgptChatQuestionStatisticsVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertFastgptChatQuestionStatistics" parameterType="FastgptChatQuestionStatistics" useGeneratedKeys="true" keyProperty="id">
+        insert into fastgpt_chat_question_statistics
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="questionCategory != null">question_category,</if>
+            <if test="source != null">source,</if>
+            <if test="contentSummary != null">content_summary,</if>
+            <if test="simhash != null">simhash,</if>
+            <if test="frequency != null">frequency,</if>
+            <if test="isResolve != null">is_resolve,</if>
+            <if test="questionId != null">question_id,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="questionCategory != null">#{questionCategory},</if>
+            <if test="source != null">#{source},</if>
+            <if test="contentSummary != null">#{contentSummary},</if>
+            <if test="simhash != null">#{simhash},</if>
+            <if test="frequency != null">#{frequency},</if>
+            <if test="isResolve != null">#{isResolve},</if>
+            <if test="questionId != null">#{questionId},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFastgptChatQuestionStatistics" parameterType="FastgptChatQuestionStatistics">
+        update fastgpt_chat_question_statistics
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="questionCategory != null">question_category = #{questionCategory},</if>
+            <if test="source != null">source = #{source},</if>
+            <if test="contentSummary != null">content_summary = #{contentSummary},</if>
+            <if test="simhash != null">simhash = #{simhash},</if>
+            <if test="frequency != null">frequency = #{frequency},</if>
+            <if test="isResolve != null">is_resolve = #{isResolve},</if>
+            <if test="questionId != null">question_id = #{questionId},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFastgptChatQuestionStatisticsById" parameterType="Long">
+        delete from fastgpt_chat_question_statistics where id = #{id}
+    </delete>
+
+    <delete id="deleteFastgptChatQuestionStatisticsByIds" parameterType="String">
+        delete from fastgpt_chat_question_statistics where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <select id="selectBestMatchBySimhash" resultMap="FastgptChatQuestionStatisticsResult">
+        SELECT s.*,
+               BIT_COUNT(s.simhash ^ #{simhash}) AS dist
+        FROM fastgpt_chat_question_statistics s
+        WHERE s.simhash IS NOT NULL
+        HAVING dist &lt;= #{threshold}
+        ORDER BY dist ASC, s.frequency DESC, s.id ASC
+        LIMIT 1
+    </select>
+
+    <update id="incrementFrequencyById">
+        update fastgpt_chat_question_statistics
+        set frequency = frequency + 1,
+            update_time = #{updateTime}
+        where id = #{id}
+    </update>
+</mapper>