zx 1 week ago
parent
commit
3715e3a133
28 changed files with 3744 additions and 689 deletions
  1. 0 19
      fs-qw-api-msg/src/main/java/com/fs/app/config/ConfigProperties.java
  2. 0 49
      fs-qw-api-msg/src/main/java/com/fs/app/config/QWConfigProperties.java
  3. 3 20
      fs-qw-api-msg/src/main/java/com/fs/framework/config/DataSourceConfig.java
  4. 1 3
      fs-qw-api-msg/src/main/java/com/fs/framework/config/MyBatisConfig.java
  5. 0 1
      fs-qw-api-msg/src/main/java/com/fs/framework/config/SecurityConfig.java
  6. 1 2
      fs-qw-api-msg/src/main/java/com/fs/framework/config/SwaggerConfig.java
  7. 0 149
      fs-qw-api-msg/src/main/resources/application-dev.yml
  8. 0 151
      fs-qw-api-msg/src/main/resources/application-druid-ylrz.yml
  9. 0 150
      fs-qw-api-msg/src/main/resources/application-druid.yml
  10. 8 144
      fs-qw-api-msg/src/main/resources/application.yml
  11. 22 0
      fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java
  12. 21 0
      fs-service/src/main/java/com/fs/fastGpt/service/AiNewService.java
  13. 1455 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  14. 1205 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiNewServiceImpl.java
  15. 48 0
      fs-service/src/main/java/com/fs/fastgptApi/result/AiImgResult.java
  16. 102 0
      fs-service/src/main/java/com/fs/fastgptApi/util/AiImgUtil.java
  17. 54 0
      fs-service/src/main/java/com/fs/qw/domain/QwUserVoiceLog.java
  18. 6 0
      fs-service/src/main/java/com/fs/qw/domain/QwWorkTask.java
  19. 73 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserVoiceLogMapper.java
  20. 3 0
      fs-service/src/main/java/com/fs/qw/mapper/QwWorkTaskMapper.java
  21. 68 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserVoiceLogService.java
  22. 4 0
      fs-service/src/main/java/com/fs/qw/service/IQwWorkTaskService.java
  23. 190 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserVoiceLogServiceImpl.java
  24. 15 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwWorkTaskServiceImpl.java
  25. 228 0
      fs-service/src/main/java/com/fs/qw/vo/QwUserVoiceLogTotalVo.java
  26. 71 0
      fs-service/src/main/java/com/fs/qw/vo/QwUserVoiceLogVo.java
  27. 158 0
      fs-service/src/main/resources/mapper/qw/QwUserVoiceLogMapper.xml
  28. 8 0
      fs-service/src/main/resources/mapper/qw/QwWorkTaskMapper.xml

+ 0 - 19
fs-qw-api-msg/src/main/java/com/fs/app/config/ConfigProperties.java

@@ -1,19 +0,0 @@
-package com.fs.app.config;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Component;
-
-@Component
-public class ConfigProperties {
-
-    @Value("${Ai.apiKey}")
-    private String apiKey;
-
-    public String getApiKey() {
-        return apiKey;
-    }
-
-    public void setApiKey(String apiKey) {
-        this.apiKey = apiKey;
-    }
-}

+ 0 - 49
fs-qw-api-msg/src/main/java/com/fs/app/config/QWConfigProperties.java

@@ -1,49 +0,0 @@
-package com.fs.app.config;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Component;
-
-@Component
-public class QWConfigProperties {
-
-    @Value("${custom.token}")
-    private String token;
-
-    @Value("${custom.encoding-aes-key}")
-    private String encodingAesKey;
-
-    @Value("${custom.corp-id}")
-    private String corpId;
-    @Value("${custom.secret}")
-    private String secret;
-
-    @Value("${custom.private-key-path}")
-    private String privateKeyPath;
-
-    @Value("${custom.webhook-url}")
-    private String webhookUrl;
-
-    public String getToken() {
-        return token;
-    }
-
-    public String getEncodingAesKey() {
-        return encodingAesKey;
-    }
-
-    public String getCorpId() {
-        return corpId;
-    }
-
-    public String getSecret() {
-        return secret;
-    }
-
-    public String getPrivateKeyPath() {
-        return privateKeyPath;
-    }
-
-    public String getWebhookUrl() {
-        return webhookUrl;
-    }
-}

+ 3 - 20
fs-qw-api-msg/src/main/java/com/fs/framework/config/DataSourceConfig.java

@@ -21,16 +21,12 @@ import java.util.Map;
 
 @Configuration
 public class DataSourceConfig {
+
     @Bean
     @ConfigurationProperties(prefix = "spring.datasource.sop.druid.master")
     public DataSource sopDataSource() {
         return new DruidDataSource();
     }
-    @Bean
-    @ConfigurationProperties(prefix = "spring.datasource.clickhouse")
-    public DataSource clickhouseDataSource() {
-        return new DruidDataSource();
-    }
 
     @Bean
     @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.master")
@@ -38,26 +34,13 @@ public class DataSourceConfig {
         return new DruidDataSource();
     }
 
-    @Bean
-    @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.slave")
-    public DataSource slaveDataSource() {
-        return new DruidDataSource();
-    }
-
 
 
     @Bean
     @Primary
-    public DynamicDataSource dataSource(@Qualifier("clickhouseDataSource") DataSource clickhouseDataSource,
-                                        @Qualifier("masterDataSource") DataSource masterDataSource,
-                                        @Qualifier("sopDataSource") DataSource sopDataSource,
-                                        @Qualifier("slaveDataSource") DataSource slaveDataSource) {
+    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
         Map<Object, Object> targetDataSources = new HashMap<>();
-        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
-
-        targetDataSources.put(DataSourceType.SLAVE, slaveDataSource);
         targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
-        targetDataSources.put(DataSourceType.CLICKHOUSE.name(), clickhouseDataSource); // Ensure matching key
         return new DynamicDataSource(masterDataSource, targetDataSources);
     }
 
@@ -66,7 +49,7 @@ public class DataSourceConfig {
      */
     @SuppressWarnings({ "rawtypes", "unchecked" })
     @Bean
-    @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
+    @ConditionalOnProperty(name = "spring.datasource.mysql.druid.statViewServlet.enabled", havingValue = "true")
     public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
     {
         // 获取web监控页面的参数

+ 1 - 3
fs-qw-api-msg/src/main/java/com/fs/framework/config/MyBatisConfig.java

@@ -1,10 +1,8 @@
 package com.fs.framework.config;
 
 import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
-import com.fs.common.utils.StringUtils;
 import org.apache.ibatis.io.VFS;
 import org.apache.ibatis.session.SqlSessionFactory;
-import org.mybatis.spring.SqlSessionFactoryBean;
 import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
@@ -28,7 +26,7 @@ import java.util.List;
 
 /**
  * Mybatis支持*匹配扫描包
- * 
+ *
 
  */
 @Configuration

+ 0 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -125,7 +125,6 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 .antMatchers("/*/api-docs").anonymous()
                 .antMatchers("/druid/**").anonymous()
                 .antMatchers("/qw/data/**").anonymous()
-                .antMatchers("/qw/test/**").anonymous()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated()
                 .and()

+ 1 - 2
fs-qw-api-msg/src/main/java/com/fs/framework/config/SwaggerConfig.java

@@ -2,7 +2,6 @@ package com.fs.framework.config;
 
 import com.fs.common.config.FSConfig;
 import io.swagger.annotations.ApiOperation;
-import io.swagger.models.auth.In;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
@@ -20,7 +19,7 @@ import java.util.List;
 
 /**
  * Swagger2的接口配置
- * 
+ *
 
  */
 @Configuration

+ 0 - 149
fs-qw-api-msg/src/main/resources/application-dev.yml

@@ -1,149 +0,0 @@
-# 数据源配置
-spring:
-    # redis 配置
-    redis:
-        # 地址
-        host: localhost
-        # 端口,默认为6379
-        port: 6379
-        # 数据库索引
-        database: 0
-        # 密码
-        password:
-        # 连接超时时间
-        timeout: 20s
-        lettuce:
-            pool:
-                # 连接池中的最小空闲连接
-                min-idle: 0
-                # 连接池中的最大空闲连接
-                max-idle: 8
-                # 连接池的最大数据库连接数
-                max-active: 8
-                # #连接池最大阻塞等待时间(使用负值表示没有限制)
-                max-wait: -1ms
-    datasource:
-        clickhouse:
-            type: com.alibaba.druid.pool.DruidDataSource
-            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
-            url: jdbc:clickhouse://1.14.104.71:8123/sop_test?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
-            username: default
-            password: rt2024
-            initialSize: 10
-            maxActive: 100
-            minIdle: 10
-            maxWait: 6000
-        mysql:
-            type: com.alibaba.druid.pool.DruidDataSource
-            driverClassName: com.mysql.cj.jdbc.Driver
-            druid:
-                # 主库数据源
-                master:
-                    url: jdbc:mysql://42.194.245.189:3306/rt_fs_his_test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: root
-                    password: YJF_2024
-                # 从库数据源
-                slave:
-                    # 从数据源开关/默认关闭
-                    enabled: false
-                    url:
-                    username:
-                    password:
-                # 初始连接数
-                initialSize: 5
-                # 最小连接池数量
-                minIdle: 10
-                # 最大连接池数量
-                maxActive: 20
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: 123456
-                filter:
-                    stat:
-                        enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
-        sop:
-            type: com.alibaba.druid.pool.DruidDataSource
-            driverClassName: com.mysql.cj.jdbc.Driver
-            druid:
-                # 主库数据源
-                master:
-                    url: jdbc:mysql://42.194.245.189:3306/test_his_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: root
-                    password: YJF_2024
-                # 初始连接数
-                initialSize: 5
-                # 最小连接池数量
-                minIdle: 10
-                # 最大连接池数量
-                maxActive: 20
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: 123456
-                filter:
-                    stat:
-                        enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
-
-rocketmq:
-    name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
-    producer:
-        group: my-producer-group
-        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
-    consumer:
-        group: test-group
-        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey

+ 0 - 151
fs-qw-api-msg/src/main/resources/application-druid-ylrz.yml

@@ -1,151 +0,0 @@
-# 数据源配置
-spring:
-    # redis 配置
-    redis:
-        # 地址
-        host: localhost
-        # 端口,默认为6379
-        port: 6379
-        # 数据库索引
-        database: 0
-        # 密码
-        password:
-        # 连接超时时间
-        timeout: 20s
-        lettuce:
-            pool:
-                # 连接池中的最小空闲连接
-                min-idle: 0
-                # 连接池中的最大空闲连接
-                max-idle: 8
-                # 连接池的最大数据库连接数
-                max-active: 8
-                # #连接池最大阻塞等待时间(使用负值表示没有限制)
-                max-wait: -1ms
-    datasource:
-        clickhouse:
-            type: com.alibaba.druid.pool.DruidDataSource
-#            driverClassName: ru.yandex.clickhouse.ClickHouseDriver
-            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
-            url: jdbc:clickhouse://1.14.104.71:8123/sop_test?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
-            username: default
-            password: rt2024
-            initialSize: 10
-            maxActive: 100
-            minIdle: 10
-            maxWait: 6000
-        mysql:
-            type: com.alibaba.druid.pool.DruidDataSource
-            driverClassName: com.mysql.cj.jdbc.Driver
-            druid:
-                # 主库数据源
-                master:
-                    url: jdbc:mysql://42.194.245.189:3306/rt_fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: root
-                    password: YJF_2024
-                # 从库数据源
-                slave:
-                    # 从数据源开关/默认关闭
-                    enabled: false
-                    url:
-                    username:
-                    password:
-                # 初始连接数
-                initialSize: 5
-                # 最小连接池数量
-                minIdle: 10
-                # 最大连接池数量
-                maxActive: 20
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: 123456
-                filter:
-                    stat:
-                        enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
-        sop:
-            type: com.alibaba.druid.pool.DruidDataSource
-            driverClassName: com.mysql.cj.jdbc.Driver
-            druid:
-                # 主库数据源
-                master:
-                    url: jdbc:mysql://42.194.245.189:3306/test_his_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: root
-                    password: YJF_2024
-                # 初始连接数
-                initialSize: 5
-                # 最小连接池数量
-                minIdle: 10
-                # 最大连接池数量
-                maxActive: 20
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: 123456
-                filter:
-                    stat:
-                        enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
-
-rocketmq:
-    name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
-    producer:
-        group: my-producer-group
-        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
-    consumer:
-        group: test-group
-        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
-

+ 0 - 150
fs-qw-api-msg/src/main/resources/application-druid.yml

@@ -1,150 +0,0 @@
-# 数据源配置
-spring:
-    # redis 配置
-    redis:
-        # 地址  localhost
-        host: 127.0.0.1
-        # 端口,默认为6379
-        port: 6379
-        # 数据库索引
-        database: 0
-        # 密码
-        password:
-        #        password:
-        # 连接超时时间
-        timeout: 10s
-        lettuce:
-            pool:
-                # 连接池中的最小空闲连接
-                min-idle: 0
-                # 连接池中的最大空闲连接
-                max-idle: 8
-                # 连接池的最大数据库连接数
-                max-active: 8
-                # #连接池最大阻塞等待时间(使用负值表示没有限制)
-                max-wait: -1ms
-    datasource:
-#        clickhouse:
-#            type: com.alibaba.druid.pool.DruidDataSource
-#            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
-#            url: jdbc:clickhouse://cc-2vc8zzo26w0l7m2l6.public.clickhouse.ads.aliyuncs.com/sop?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
-#            username: rt_2024
-#            password: Yzx_19860213
-#            initialSize: 10
-#            maxActive: 100
-#            minIdle: 10
-#            maxWait: 6000
-        mysql:
-            type: com.alibaba.druid.pool.DruidDataSource
-            driverClassName: com.mysql.cj.jdbc.Driver
-            druid:
-                # 主库数据源
-                master:
-                    url: jdbc:mysql://1.14.207.209:8008/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: root
-                    password: Rtyy_2023
-                # 从库数据源
-                slave:
-                    # 从数据源开关/默认关闭
-                    enabled: false
-                    url:
-                    username:
-                    password:
-                # 初始连接数
-                initialSize: 5
-                # 最小连接池数量
-                minIdle: 10
-                # 最大连接池数量
-                maxActive: 20
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: 123456
-                filter:
-                    stat:
-                        enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
-        sop:
-            type: com.alibaba.druid.pool.DruidDataSource
-            driverClassName: com.mysql.cj.jdbc.Driver
-            druid:
-                # 主库数据源
-                master:
-                    url: jdbc:mysql://1.14.207.209:8006/fs_his_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: root
-                    password: Rtyy_2023
-                # 初始连接数
-                initialSize: 5
-                # 最小连接池数量
-                minIdle: 10
-                # 最大连接池数量
-                maxActive: 20
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: 123456
-                filter:
-                    stat:
-                        enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
-rocketmq:
-    name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
-    producer:
-        group: my-producer-group
-        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
-    consumer:
-        group: voice-group
-        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
-

+ 8 - 144
fs-qw-api-msg/src/main/resources/application.yml

@@ -1,149 +1,13 @@
-# 项目相关配置
-fs:
-  # 名称
-  name: fs
-  # 版本
-  version: 1.1.0
-  # 版权年份
-  copyrightYear: 2021
-  # 实例演示开关
-  demoEnabled: true
-  # 文件路径 示例( Windows配置D:/fs/uploadPath,Linux配置 /home/fs/uploadPath)
-  profile: c:/fs/uploadPath
-  # 获取ip地址开关
-  addressEnabled: false
-  # 验证码类型 math 数组计算 char 字符验证
-  captchaType: math
-
-# 开发环境配置
 server:
-  # 服务器的HTTP端口,默认为8080
-  port: 8667
-  servlet:
-    # 应用的访问路径
-    context-path: /
-  tomcat:
-    # tomcat的URI编码
-    uri-encoding: UTF-8
-    # tomcat最大线程数,默认为200
-    max-threads: 1000
-    # Tomcat启动初始化的线程数,默认值25
-    min-spare-threads: 50
-
-# 日志配置
-logging:
-  level:
-    com.fs: info
-    org.springframework: warn
-
+  port: 8006
 # Spring配置
 spring:
-  # 资源信息
-  messages:
-    # 国际化资源文件路径
-    basename: i18n/messages
   profiles:
-#    active: dev
     active: dev
-    include: config
-  # 文件上传
-  servlet:
-     multipart:
-       # 单个文件大小
-       max-file-size:  10MB
-       # 设置总上传的文件大小
-       max-request-size:  20MB
-  # 服务模块
-  devtools:
-    restart:
-      # 热部署开关
-      enabled: true
-
-# token配置
-token:
-    # 令牌自定义标识
-    header: Authorization
-    # 令牌密钥
-    secret: abcdefghijklmnopqrstuvwxyz
-    # 令牌有效期(默认30分钟)
-    expireTime: 180
-mybatis-plus:
-  # 搜索指定包别名
-  typeAliasesPackage: com.fs.**.domain,com.fs.**.bo
-  # 配置mapper的扫描,找到所有的mapper.xml映射文件
-  mapperLocations: classpath*:/mapper/**/*.xml
-  configLocation: classpath:mybatis/mybatis-config.xml
-  # 全局配置
-  global-config:
-    db-config:
-      # 主键类型  0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
-      idType: AUTO
-      # 字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
-      fieldStrategy: NOT_EMPTY
-    banner: false
-    # 配置
-  configuration:
-    # 驼峰式命名
-    mapUnderscoreToCamelCase: true
-    # 全局映射器启用缓存
-    cacheEnabled: true
-    # 配置默认的执行器
-    defaultExecutorType: REUSE
-    # 允许 JDBC 支持自动生成主键
-    useGeneratedKeys: true
-
-# MyBatis配置
-mybatis:
-    # 搜索指定包别名
-    typeAliasesPackage: com.fs.**.domain
-    # 配置mapper的扫描,找到所有的mapper.xml映射文件
-    mapperLocations: classpath*:mapper/**/*Mapper.xml
-    # 加载全局的配置文件
-    configLocation: classpath:mybatis/mybatis-config.xml
-
-# PageHelper分页插件
-pagehelper:
-  helperDialect: mysql
-  supportMethodsArguments: true
-  params: count=countSql
-
-# Swagger配置
-swagger:
-  # 是否开启swagger
-  enabled: false
-  # 请求前缀
-  pathMapping: /dev-api
-# 防止XSS攻击
-xss:
-  # 过滤开关
-  enabled: true
-  # 排除链接(多个用逗号分隔)
-  excludes: /system/notice,/system/config/*
-  # 匹配链接
-  urlPatterns: /system/*,/monitor/*,/tool/*
-wx:
-  miniapp:
-    configs:
-      - appid: wxbe53e91d9ad11ca6
-        secret: 447135e6ca602fa4745b81216f600615
-        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
-        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
-        msgDataFormat: JSON
-  pay:
-    appId: wx782bacb12a6b5d4f #微信公众号或者小程序等的appid
-    mchId: 1518509741 #微信支付商户号
-    mchKey: Jinrichengzhang88888888888888888 #微信支付商户密钥
-    subAppId:  #服务商模式下的子商户公众账号ID
-    subMchId:  #服务商模式下的子商户号
-    keyPath: c:\tools\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
-    notifyUrl: https://api.haitujia.com/app/wxpay/wxPayNotify
-custom:
-  token: "1o62d3YxvdHd4LEUiltnu7sK"
-  encoding-aes-key: "UJfTQ5qKTKlegjkXtp1YuzJzxeHlUKvq5GyFbERN1iU"
-  corp-id: "ww51717e2b71d5e2d3"
-  secret: "6ODAmw-8W4t6h9mdzHh2Z4Apwj8mnsyRnjEDZOHdA7k"
-  private-key-path: "privatekey.pem"
-  webhook-url: "https://your-server.com/wecom/archive"
-Ai:
-  apiKey: "208d3549-8dc9-4ef6-b3fa-5aa358f1ab20"
-
+    include: common,config-dev
+#    active: druid-jzzx
+#    include: common,config-druid-jzzx
+#    active: druid-hdt
+#    include: common,config-druid-
+#    active: druid-sxjz
+#    include: common,config-druid-sxjz

+ 22 - 0
fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java

@@ -0,0 +1,22 @@
+package com.fs.fastGpt.service;
+
+import com.fs.common.core.domain.R;
+import com.fs.qwHookApi.vo.QwHookVO;
+import com.fs.wxwork.dto.WxWorkResponseDTO;
+
+public interface AiHookService {
+    /** 发送ai提醒 **/
+    R AiRemind();
+
+    /** ai回复**/
+    R qwHookNotifyAiReply(Long qwUserID, Long sender,String count,String uid,Integer type);
+
+    /** 转人工 **/
+    void artificial(QwHookVO vo);
+
+    R qwHookNotifyAddMsg(Long qwUserID, Long sender,String count,String uid);
+
+    void expireAiMsg();
+
+    WxWorkResponseDTO<String> getFileUrl(String uuid, String fileId, String aesKey, String authKey, String fileName, Integer fileSize, Long serverId);
+}

+ 21 - 0
fs-service/src/main/java/com/fs/fastGpt/service/AiNewService.java

@@ -0,0 +1,21 @@
+package com.fs.fastGpt.service;
+
+import com.fs.common.core.domain.R;
+import com.fs.qw.domain.QwMessageGather;
+import com.fs.qwHookApi.vo.QwHookVO;
+
+public interface AiNewService {
+    /** 发送ai提醒 **/
+    void AiRemind();
+
+    /** ai回复**/
+    R qwHookNotifyAiReply(QwMessageGather qwMessageGather,String corpId);
+
+    /** 转人工 **/
+    void artificial(QwHookVO vo);
+
+    R qwHookNotifyAddMsg(QwMessageGather qwMessageGather,String corpId);
+
+    void expireAiMsg();
+
+}

+ 1455 - 0
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -0,0 +1,1455 @@
+package com.fs.fastGpt.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.course.mapper.FsUserCourseVideoMapper;
+import com.fs.course.param.FsCourseLinkCreateParam;
+import com.fs.course.service.IFsCourseLinkService;
+import com.fs.course.vo.FsCourseWatchLogVO;
+import com.fs.fastGpt.config.ModeConfig;
+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.SendAIParam;
+import com.fs.fastGpt.service.AiHookService;
+import com.fs.fastGpt.service.IFastGptChatMsgService;
+import com.fs.fastGpt.service.IFastGptRoleService;
+import com.fs.fastgptApi.param.ChatParam;
+import com.fs.fastgptApi.result.ChatDetailTStreamFResult;
+import com.fs.fastgptApi.service.ChatService;
+import com.fs.fastgptApi.service.Impl.AudioServiceImpl;
+import com.fs.fastgptApi.util.AiImgUtil;
+import com.fs.fastgptApi.vo.AudioVO;
+import com.fs.qw.domain.*;
+import com.fs.qw.mapper.QwCompanyMapper;
+import com.fs.qw.mapper.QwExternalContactInfoMapper;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.service.*;
+import com.fs.qwApi.param.QwSendMsgParam;
+import com.fs.qwApi.service.QwApiService;
+import com.fs.qwHookApi.param.QwHookSendMsgParam;
+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.wxwork.dto.*;
+import com.fs.wxwork.service.WxWorkService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+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.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class AiHookServiceImpl implements AiHookService {
+    @Autowired
+    private FastGptChatSessionMapper fastGptChatSessionMapper;
+    @Autowired
+    private ChatService chatService;
+    @Autowired
+    private QwUserMapper qwUserMapper;
+    @Autowired
+    private IFastGptRoleService roleService;
+    @Autowired
+    private QwExternalContactInfoMapper qwExternalContactInfoMapper;
+    @Autowired
+    private IFastGptChatMsgService fastGptChatMsgService;
+    @Autowired
+    private IQwContactWayService qwContactWayService;
+    @Autowired
+    QwApiService qwApiService;
+    @Autowired
+    QwCompanyMapper qwCompanyMapper;
+    @Autowired
+    FsCourseWatchLogMapper fsCourseWatchLogMapper;
+    @Autowired
+    FsUserCourseVideoMapper fsUserCourseVideoMapper;
+    @Autowired
+    FsCourseWatchLogMapper   watchLogMapper;
+    @Autowired
+    IQwJsApiService qwGetJsapiTicketService;
+    @Autowired
+    private IFsCourseLinkService iFsCourseLinkService;
+    @Autowired
+    FastgptChatVoiceHomoMapper fastgptChatVoiceHomoMapper;
+    @Autowired
+    RedisCache redisCache;
+    @Autowired
+    private IQwUserService qwUserService;
+    @Autowired
+    IQwWorkTaskService qwWorkTaskService;
+    @Autowired
+    AiImgUtil aiImgUtil;
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+    @Autowired
+    private IQwMsgService qwMsgService;
+    @Autowired
+    private IQwTagGroupService qwTagGroupService;
+    @Autowired
+    private FastGptChatReplaceWordsMapper fastGptChatReplaceWordsMapper;
+    @Autowired
+    RedisTemplate<String, String> redisTemplate;
+    @Autowired
+    WxWorkService wxWorkService;
+
+//    @Autowired
+//    private IFastGptKeywordSendService fastGptKeywordSendService;
+
+    /** Ai半小时未回复提醒 **/
+    /**
+     *
+     * sender    像是fastgpt_chat_msg中的user_id字段
+     * receiver     qw_user表中的vid字段
+     * uuid         qw_user表中的uid字段
+     * type  fasgpt_chat_msg中的msg_type字段
+     * @return
+     */
+    @Override
+    public R AiRemind() {
+        List<FastGptChatSession> fastGptChatSessions = fastGptChatSessionMapper.selectFastGptChatSessionByRemind();
+        if(fastGptChatSessions != null && !fastGptChatSessions.isEmpty()){
+            for (FastGptChatSession fastGptChatSession : fastGptChatSessions) {
+                String qwContent = "用户未回复";
+                Long qwUserId = fastGptChatSession.getQwUserId();
+
+                if(fastGptChatSession.getUserId() != null){
+                    Long sender = Long.valueOf(fastGptChatSession.getUserId());
+                    QwUser user = qwUserMapper.selectQwUserById(qwUserId);
+                    String uid = user.getUid();
+                    //查询接收人
+                    if(user==null){
+                        System.out.println("查询接收人为空");
+                        return R.ok();
+                    }
+                    if(user.getFastGptRoleId()==null){
+                        System.out.println("未绑定角色");
+                        return R.ok();
+                    }
+                    Long serverId = user.getServerId();
+                    System.out.println("服务器id"+serverId);
+                    if (serverId == null) {
+                        System.out.println("服务id为空");
+                        return R.ok();
+                    }
+                    FastGptRole role=roleService.selectFastGptRoleTypeByRoleId(user.getFastGptRoleId());
+                    //没用ai角色跳过
+                    if(role==null){
+                        System.out.println("没用ai角色跳过");
+                        return R.ok();
+                    }
+                    String modeConfig=role.getModeConfigJson();
+                    //key不为空
+                    if(StringUtils.isEmpty(modeConfig)){
+                        System.out.println("没有aiKey");
+                        return R.ok();
+                    }
+                    ModeConfig config=JSONUtil.toBean(modeConfig,ModeConfig.class);
+                    if(StringUtils.isEmpty(config.getAPPKey())){
+                        System.out.println("没有aiKey");
+                        return R.ok();
+                    }
+
+                    QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactById(fastGptChatSession.getQwExtId());
+                    if (qwExternalContacts==null){
+                        System.out.println("没有外部联系人");
+                        return R.ok();
+                    }
+                    if(qwExternalContacts.getType()!=null&&qwExternalContacts.getType()==1){
+
+                        //判断是否转人工
+                        if (fastGptChatSession.getIsArtificial()==1){
+                            System.out.println("转人工了");
+                            return R.ok();
+                        }
+
+                        //用户首次发送消息
+                        redisCache.setCacheObject("reply:" + fastGptChatSession.getSessionId(),1,5,TimeUnit.MINUTES);
+                        redisCache.setCacheObject("msg:" + fastGptChatSession.getSessionId(),qwContent,5,TimeUnit.MINUTES);
+                        System.out.println("等待");
+                        R r= sendAiMsg(1,fastGptChatSession,role,user,qwExternalContacts.getId(),config.getAPPKey(),qwExternalContacts);
+                        System.out.println(r);
+                        //完成对话 删除消息记录
+                        redisCache.deleteObject("reply:" + fastGptChatSession.getSessionId());
+                        redisCache.deleteObject("msg:" + fastGptChatSession.getSessionId());
+                        if(!r.get("code").equals(200)){
+                            log.info("ai报错转人工:"+role.getRoleId()+":"+qwExternalContacts.getName());
+                            notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," ai报错转人工",qwExternalContacts.getId());
+                            return R.ok();
+                        }
+
+                        ChatDetailTStreamFResult result=(ChatDetailTStreamFResult)r.get("data");
+                        //是否转人工
+                        if(result.getChoices().isEmpty()){
+                            return R.ok();
+                        }
+                        String contentKh = result.getChoices().get(0).getMessage().getContent();
+                        String content = replace(result.getChoices().get(0).getMessage().getContent()).trim();
+                        //计算token
+                        List<ChatDetailTStreamFResult.ResponseNode> responseData = result.getResponseData();
+                        int token=0;
+                        for (ChatDetailTStreamFResult.ResponseNode responseDatum : responseData) {
+                            int tokens = responseDatum.getTokens();
+                            token+=tokens;
+                        }
+                        //存聊天记录
+                        addSaveAiMsg(2,2,contentKh,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),result.getUsage().prompt_tokens,result.getUsage().completion_tokens,token);
+                        if (!content.isEmpty()){
+                            getFastGptSession(qwExternalContacts,user);
+                            //从fastgpt_chat_artificial_words表中查询所有转人工文本
+//                            List<FastgptChatArtificialWords> chatArtificialWords = qwExternalContactMapper.selectChatGptChatArtificialWords();
+//                            List<String> collect = chatArtificialWords.stream().map(m -> m.getContent()).collect(Collectors.toList());
+//                            if (collect.stream().anyMatch(contentKh::contains)){
+//                                notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," 触发关键词",qwExternalContacts.getId());
+//                                return R.ok();
+//                            }
+                            if (result.isLongText()){
+                                //新增用户信息
+                                addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
+                                sendAiMsg(content,sender,uid,serverId);
+
+                            }else {
+                                String sa = contentKh.replaceAll("】\n", "】").replaceAll("\n【", "【");
+                                String nr = replace(sa);
+                                String[] split = nr.split("\n");
+                                if (split.length>6){
+                                    notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName(),"回复长度异常",qwExternalContacts.getId());
+                                    return R.ok();
+                                }
+                                List<String> countList = countString(content);
+                                //新增用户信息
+                                addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
+                                for (String msg : countList) {
+                                    sendAiMsg(msg,sender,uid,serverId);
+                                    try {
+                                        Thread.sleep(10000);
+                                    } catch (InterruptedException e) {
+
+                                    }
+                                    Integer replyH = redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
+                                    //用户正在发送消息 后面的消息不发了
+                                    if (replyH!=null&&replyH!=0){
+                                        return R.ok();
+                                    }
+                                }
+                            }
+                        }
+                        if (result.isArtificial()){
+                            notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," ai请求人工协助",qwExternalContacts.getId());
+                        }
+                    }
+                }
+            }
+        }
+        return R.ok();
+    }
+    /** Ai回复 **/
+    @Async
+    @Override
+    public R qwHookNotifyAiReply(Long qwUserId, Long sender,String qwContent,String uid,Integer type) {
+        if (qwContent==null||qwContent.isEmpty()){
+            return R.ok();
+        }
+        if (qwContent.trim().isEmpty()){
+            return R.ok();
+        }
+        if (qwContent.contains("我已经添加了你")){
+                return R.ok();
+        }
+        System.out.println(qwUserId);
+        QwUser user = qwUserMapper.selectQwUserById(qwUserId);
+        //查询接收人
+        if(user==null){
+            System.out.println("查询接收人为空");
+            return R.ok();
+        }
+        if(user.getFastGptRoleId()==null){
+            System.out.println("未绑定角色");
+            return R.ok();
+        }
+        Long serverId = user.getServerId();
+        System.out.println("服务器id"+serverId);
+        if (serverId == null) {
+            System.out.println("服务id为空");
+            return R.ok();
+        }
+        FastGptRole role=roleService.selectFastGptRoleTypeByRoleId(user.getFastGptRoleId());
+        //没用ai角色跳过
+        if(role==null){
+            System.out.println("没用ai角色跳过");
+            return R.ok();
+        }
+        String modeConfig=role.getModeConfigJson();
+        //key不为空
+        if(StringUtils.isEmpty(modeConfig)){
+            System.out.println("没有aiKey");
+            return R.ok();
+        }
+        ModeConfig config=JSONUtil.toBean(modeConfig,ModeConfig.class);
+        if(StringUtils.isEmpty(config.getAPPKey())){
+            System.out.println("没有aiKey");
+            return R.ok();
+        }
+        WxWorkVid2UserIdDTO wxWorkVid2UserIdDTO = new WxWorkVid2UserIdDTO();
+        wxWorkVid2UserIdDTO.setUser_id(Arrays.asList(sender));
+        wxWorkVid2UserIdDTO.setUuid(uid);
+        WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> WxWorkVid2UserIdRespDTO = wxWorkService.Vid2UserId(wxWorkVid2UserIdDTO,serverId);
+        List<WxWorkVid2UserIdRespDTO> data = WxWorkVid2UserIdRespDTO.getData();
+        if (data==null|| data.isEmpty()){
+
+            System.out.println("未获取到extId"+wxWorkVid2UserIdDTO);
+            return R.ok();
+        }
+        com.fs.wxwork.dto.WxWorkVid2UserIdRespDTO dto = data.get(0);
+
+        QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(dto.getOpenid(), user.getCorpId(),user.getQwUserId());
+        if (qwExternalContacts==null){
+            System.out.println("没有外部联系人");
+            return R.ok();
+        }
+        if(qwExternalContacts.getType()!=null&&qwExternalContacts.getType()==1){
+            FastGptChatSession fastGptChatSession= getFastGptSession(qwExternalContacts,user,dto);
+            if(type == 104||type == 101){
+                String imageParse = aiImgUtil.getImageParse(qwContent);
+                qwContent="用户发送图片内容:"+"\""+imageParse+"\"";
+            }
+            String contentEmj = replaceWxEmo(qwContent);
+            if(!contentEmj.isEmpty()){
+                addSaveAiMsg(1,1,contentEmj,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),null,null,null);
+            }else {
+                contentEmj ="用户发送表情:"+qwContent;
+                if (type==16){
+                    return R.ok();
+                }
+            }
+
+            //判断是否转人工
+            if (fastGptChatSession.getIsArtificial()==1){
+                System.out.println("转人工了");
+                return R.ok();
+            }
+            //获取是用户是否发送消息
+            Integer reply = redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
+            Integer replyI=1;
+            //用户正在发送消息 不发
+            if (reply!=null&&reply!=0){
+                //更新用户发送消息次数
+                redisCache.setCacheObject("reply:" + fastGptChatSession.getSessionId(),reply+1,5, TimeUnit.MINUTES);
+                //获取用户之前发送的消息
+                String msg = redisCache.getCacheObject("msg:" + fastGptChatSession.getSessionId());
+                if (!msg.isEmpty()){
+                    //更新用户发送消息内容
+                    redisCache.setCacheObject("msg:" + fastGptChatSession.getSessionId(),msg+","+contentEmj,5,TimeUnit.MINUTES);
+                }
+                //本次跳过
+                System.out.println("正在对话");
+                return R.ok();
+            }
+            //用户首次发送消息
+            redisCache.setCacheObject("reply:" + fastGptChatSession.getSessionId(),1,5,TimeUnit.MINUTES);
+            redisCache.setCacheObject("msg:" + fastGptChatSession.getSessionId(),contentEmj,5,TimeUnit.MINUTES);
+            System.out.println("等待");
+            R r= sendAiMsg(replyI,fastGptChatSession,role,user,qwExternalContacts.getId(),config.getAPPKey(),qwExternalContacts);
+            System.out.println(r);
+            //完成对话 删除消息记录
+            redisCache.deleteObject("reply:" + fastGptChatSession.getSessionId());
+            redisCache.deleteObject("msg:" + fastGptChatSession.getSessionId());
+            if(!r.get("code").equals(200)){
+                log.info("ai报错转人工:"+role.getRoleId()+":"+qwExternalContacts.getName());
+                notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," ai报错转人工",qwExternalContacts.getId());
+                return R.ok();
+            }
+            ChatDetailTStreamFResult result=(ChatDetailTStreamFResult)r.get("data");
+            //是否转人工
+            if(result.getChoices().isEmpty()){
+                return R.ok();
+            }
+            String contentKh = result.getChoices().get(0).getMessage().getContent();
+            String content = replace(result.getChoices().get(0).getMessage().getContent()).trim();
+            //计算token
+            List<ChatDetailTStreamFResult.ResponseNode> responseData = result.getResponseData();
+            int token=0;
+            for (ChatDetailTStreamFResult.ResponseNode responseDatum : responseData) {
+                int tokens = responseDatum.getTokens();
+                token+=tokens;
+            }
+            //存聊天记录
+            addSaveAiMsg(2,2,contentKh,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),result.getUsage().prompt_tokens,result.getUsage().completion_tokens,token);
+            if (!content.isEmpty()){
+                addSaveAiMsg(1,2,content,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),null,null,null);
+                //从fastgpt_chat_artificial_words表中查询所有转人工文本
+//                List<FastgptChatArtificialWords> chatArtificialWords = qwExternalContactMapper.selectChatGptChatArtificialWords();
+//                List<String> collect = chatArtificialWords.stream().map(m -> m.getContent()).collect(Collectors.toList());
+//                if (collect.stream().anyMatch(contentKh::contains)){
+//                    notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," 触发关键词",qwExternalContacts.getId());
+//                    return R.ok();
+//                }
+                if (result.isLongText()){
+                    //新增用户信息
+                    addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
+                    if (type==16){
+                        sendAiVoiceMsg(content,sender,uid,serverId,user);
+                    }else {
+                        sendAiMsg(content,sender,uid,serverId);
+                    }
+
+                }else {
+                    String sa = contentKh.replaceAll("】\n", "】").replaceAll("\n【", "【");
+                    String nr = replace(sa);
+                    String[] split = nr.split("\n");
+                    if (split.length>6){
+                        notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName(),"回复长度异常",qwExternalContacts.getId());
+                        return R.ok();
+                    }
+                    List<String> countList = countString(content);
+                    //新增用户信息
+                    addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
+                    for (String msg : countList) {
+                        if (type==16){
+                            sendAiVoiceMsg(msg,sender,uid,serverId,user);
+                        }else {
+                            sendAiMsg(msg,sender,uid,serverId);
+                        }
+                        try {
+                            Thread.sleep(10000);
+                        } catch (InterruptedException e) {
+
+                        }
+                        Integer replyH = redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
+                        //用户正在发送消息 后面的消息不发了
+                        if (replyH!=null&&replyH!=0){
+                            return R.ok();
+                        }
+                    }
+                }
+            }
+
+            //发送短链
+            //根据模板发送图片
+//            Long roleId = role.getRoleId();
+//            List<FastGptKeywordSend> fastGptKeywordSends = fastGptKeywordSendService.selectFastGptKeywordSendByRoleId(roleId);
+//            if(fastGptKeywordSends != null && !fastGptKeywordSends.isEmpty()){
+//                for (FastGptKeywordSend fastGptKeywordSend : fastGptKeywordSends) {
+//                    if (qwContent.contains(fastGptKeywordSend.getKeyword())){
+//                        sendKeyWordMsg(fastGptKeywordSend,sender,uid,serverId);
+//                    }
+//                }
+//            }
+
+
+            if (result.isArtificial()){
+                notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," ai请求人工协助",qwExternalContacts.getId());
+            }
+        }
+
+        return R.ok();
+    }
+
+    private void sendAiVoiceMsg(String content, Long sendId , String uuid,Long serverId,QwUser user) {
+        if (content == null || content.trim().isEmpty()){
+            System.out.println("输出为空格");
+            return;
+        }
+        content = voiceHomo(content);
+        System.out.println("过滤后的文字:"+content);
+        if (content.isEmpty()){
+            return;
+        }
+
+        WxwSilkVoceDTO silkVoice = wxWorkService.getSilkVoice(content, user.getCompanyUserId());
+        if (silkVoice == null){
+            return;
+        }
+        if (silkVoice.getCode()!=200){
+            return;
+        }
+        WxwSilkVoceDTO.Data data = silkVoice.getData();
+        WxwUploadCdnLinkFileDTO wxwUploadCdnLinkFileDTO = new WxwUploadCdnLinkFileDTO();
+        wxwUploadCdnLinkFileDTO.setUrl(data.getUrl());
+        wxwUploadCdnLinkFileDTO.setFilename(data.getUrl());
+        wxwUploadCdnLinkFileDTO.setUuid(uuid);
+        WxWorkResponseDTO<WxwUploadCdnLinkFileRespDTO> dto = wxWorkService.uploadCdnLinkFile(wxwUploadCdnLinkFileDTO, serverId);
+        WxwUploadCdnLinkFileRespDTO voice = dto.getData();
+        WxwSendCDNVoiceMsgDTO wxwSendCDNVoiceMsgDTO = new WxwSendCDNVoiceMsgDTO();
+        wxwSendCDNVoiceMsgDTO.setSend_userid(sendId);
+        System.out.println("语音size"+voice.getSize());
+        wxwSendCDNVoiceMsgDTO.setVoice_time(data.getDuration());
+        wxwSendCDNVoiceMsgDTO.setIsRoom(false);
+        wxwSendCDNVoiceMsgDTO.setCdnkey(voice.getCdn_key());
+        wxwSendCDNVoiceMsgDTO.setAeskey(voice.getAes_key());
+        wxwSendCDNVoiceMsgDTO.setMd5(voice.getMd5());
+        wxwSendCDNVoiceMsgDTO.setFileSize(voice.getSize());
+        wxwSendCDNVoiceMsgDTO.setUuid(uuid);
+        WxWorkResponseDTO<WxwSendCDNVoiceMsgRespDTO> wxwSendCDNVoiceMsgRespDTOWxWorkResponseDTO = wxWorkService.SendCDNVoiceMsg(wxwSendCDNVoiceMsgDTO, serverId);
+        System.out.println(wxwSendCDNVoiceMsgRespDTOWxWorkResponseDTO);
+
+    }
+
+    @Autowired
+    QwSopLogsMapper qwSopLogsMapper;
+    private void sendAiMsg(String content, Long sendId , String uuid,Long serverId) {
+        if (content == null || content.trim().isEmpty()){
+            System.out.println("输出为空格");
+            return;
+        }
+        WxWorkSendTextMsgDTO wxWorkSendTextMsgDTO = new WxWorkSendTextMsgDTO();
+        wxWorkSendTextMsgDTO.setSend_userid(sendId);
+        wxWorkSendTextMsgDTO.setUuid(uuid);
+        wxWorkSendTextMsgDTO.setContent(content);
+        wxWorkSendTextMsgDTO.setIsRoom(false);
+        WxWorkResponseDTO<WxWorkSendTextMsgRespDTO> wxWorkSendTextMsgRespDTOWxWorkResponseDTO = wxWorkService.SendTextMsg(wxWorkSendTextMsgDTO,serverId);
+        WxWorkSendTextMsgRespDTO data = wxWorkSendTextMsgRespDTOWxWorkResponseDTO.getData();
+
+
+    }
+
+//    private void sendKeyWordMsg(FastGptKeywordSend fastGptKeywordSend, Long sendId , String uuid,Long serverId) {
+//        if (fastGptKeywordSend == null || fastGptKeywordSend.getContent().isEmpty()){
+//            System.out.println("输出为空格");
+//            return;
+//        }
+//
+//        WxWorkResponseDTO<WxwUploadCdnLinkImgRespDTO> dto = new WxWorkResponseDTO<>();
+//
+//        //1.上传cdn网络图片
+//        if(fastGptKeywordSend.getImgUrl() != null){
+//            WxwUploadCdnLinkImgDTO wxwUploadCdnLinkImgDTO = new WxwUploadCdnLinkImgDTO();
+//            wxwUploadCdnLinkImgDTO.setUuid(uuid);
+//            wxwUploadCdnLinkImgDTO.setUrl(fastGptKeywordSend.getImgUrl());
+//            dto  = wxWorkService.uploadCdnLinkImg(wxwUploadCdnLinkImgDTO,serverId);
+//        }
+//
+//        //2.发送模板中的文字内容
+//        String content = fastGptKeywordSend.getContent();
+//        WxWorkSendTextMsgDTO wxWorkSendTextMsgDTO = new WxWorkSendTextMsgDTO();
+//        wxWorkSendTextMsgDTO.setSend_userid(sendId);
+//        wxWorkSendTextMsgDTO.setUuid(uuid);
+//        wxWorkSendTextMsgDTO.setContent(content);
+//        wxWorkSendTextMsgDTO.setIsRoom(false);
+//        WxWorkResponseDTO<WxWorkSendTextMsgRespDTO> wxWorkSendTextMsgRespDTOWxWorkResponseDTO = wxWorkService.SendTextMsg(wxWorkSendTextMsgDTO,serverId);
+//        WxWorkSendTextMsgRespDTO data = wxWorkSendTextMsgRespDTOWxWorkResponseDTO.getData();
+//
+//        //图片上传成功后再发送图片
+//        if("成功".equals(dto.getErrmsg())){
+//            WxwUploadCdnLinkImgRespDTO imgRespDTO = dto.getData();
+//            WxwSendCDNImgMsgDTO wxwSendCDNImgMsgDTO = new WxwSendCDNImgMsgDTO();
+//            wxwSendCDNImgMsgDTO.setSend_userid(sendId);
+//            wxwSendCDNImgMsgDTO.setUuid(uuid);
+//            wxwSendCDNImgMsgDTO.setIsRoom(false);
+//            wxwSendCDNImgMsgDTO.setCdnkey(imgRespDTO.getCdn_key());
+//            wxwSendCDNImgMsgDTO.setAeskey(imgRespDTO.getAes_key());
+//            wxwSendCDNImgMsgDTO.setMd5(imgRespDTO.getMd5());
+//            wxwSendCDNImgMsgDTO.setFileSize(imgRespDTO.getSize());
+//            wxWorkService.SendCDNImgMsg(wxwSendCDNImgMsgDTO, serverId);
+//        }
+//
+//    }
+
+    /** 回调转人工  **/
+    private void notifyArtificial(Long sessionId, QwUser user, String name,String reason,Long extId) {
+        FastGptChatSession s = new FastGptChatSession();
+        s.setIsArtificial(1);
+        s.setRemindStatus(0);
+        s.setIsReply(0);
+        s.setSessionId(sessionId);
+        s.setLastTime(new Date());
+        fastGptChatSessionMapper.updateFastGptChatSession(s);
+        log.info("转人工:"+name);
+        QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(user.getCorpId());
+        sendQwAppMsg(user.getCorpId(),Integer.parseInt(qwCompany.getServerAgentId().trim()),user.getQwUserId(),"您的客户:"+name+"因"+reason+"  需要转人工,请及时回复");
+        sendQwAppMsg(user.getCorpId(),Integer.parseInt(qwCompany.getServerAgentId().trim()),user.getQwUserId(),name);
+        qwWorkTaskService.addQwWorkByAiNotifyArtificial(user,extId,"Ai转人工");
+    }
+
+    void sendQwAppMsg(String corpId, Integer agentId,String qwUserId,String content){
+        QwSendMsgParam qwSendMsgParam = new QwSendMsgParam();
+        qwSendMsgParam.setAgentid(agentId);
+        qwSendMsgParam.setTouser(qwUserId);
+        QwSendMsgParam.Text text = new QwSendMsgParam.Text();
+        text.setContent(content);
+        qwSendMsgParam.setText(text);
+        qwSendMsgParam.setMsgtype("text");
+        qwApiService.sendMsg(qwSendMsgParam, corpId);
+    }
+
+
+    private void saveQwUserMsg(FastGptChatSession fastGptChatSession,Integer sendType,String content) {
+        content = replaceWxEmo(content);
+        if(content.isEmpty()){
+            return;
+        }
+        FastGptChatMsg fastGptChatMsgAi = new FastGptChatMsg();
+        fastGptChatMsgAi.setContent(content);
+        fastGptChatMsgAi.setSessionId(fastGptChatSession.getSessionId());
+        fastGptChatMsgAi.setRoleId(Long.parseLong(fastGptChatSession.getKfId()));
+        fastGptChatMsgAi.setSendType(sendType);
+        fastGptChatMsgAi.setCompanyId(fastGptChatSession.getCompanyId());
+        fastGptChatMsgAi.setUserId(fastGptChatSession.getUserId());
+        fastGptChatMsgAi.setUserType(1);
+        fastGptChatMsgAi.setMsgType(1);
+        fastGptChatMsgAi.setStatus(0);
+        fastGptChatMsgAi.setAvatar(fastGptChatSession.getAvatar());
+        fastGptChatMsgAi.setNickName(fastGptChatSession.getNickName());
+        fastGptChatMsgAi.setCreateTime(new Date());
+        fastGptChatMsgAi.setExtId(fastGptChatSession.getQwExtId()+"");
+        fastGptChatMsgService.insertFastGptChatMsg(fastGptChatMsgAi);
+        log.info("新增消息:"+fastGptChatMsgAi);
+    }
+    /** 存聊天记录  **/
+    private void addSaveAiMsg(Integer msgType,Integer sendType,String content, QwUser user, Long sessionId, Long roleId, QwExternalContact qwExternalContacts, String userId, Integer promptTokens, Integer completionTokens, Integer token) {
+
+        FastGptChatMsg fastGptChatMsgAi = new FastGptChatMsg();
+        fastGptChatMsgAi.setContent(content);
+        fastGptChatMsgAi.setSessionId(sessionId);
+        fastGptChatMsgAi.setRoleId(roleId);
+        fastGptChatMsgAi.setSendType(sendType);
+        fastGptChatMsgAi.setCompanyId(user.getCompanyId());
+        fastGptChatMsgAi.setUserId(userId);
+        fastGptChatMsgAi.setCompanyUserId(user.getCompanyUserId());
+        fastGptChatMsgAi.setUserType(1);
+        fastGptChatMsgAi.setMsgType(msgType);
+        fastGptChatMsgAi.setStatus(0);
+        fastGptChatMsgAi.setAvatar(qwExternalContacts.getAvatar());
+        fastGptChatMsgAi.setNickName(qwExternalContacts.getName());
+        fastGptChatMsgAi.setCreateTime(new Date());
+        fastGptChatMsgAi.setExtId(qwExternalContacts.getId()+"");
+        fastGptChatMsgAi.setPromptTokens(promptTokens);
+        fastGptChatMsgAi.setCompletionTokens(completionTokens);
+        fastGptChatMsgAi.setTotalTokens(token);
+        fastGptChatMsgService.insertFastGptChatMsg(fastGptChatMsgAi);
+    }
+    /** 获取会话  **/
+    private FastGptChatSession getFastGptSession(QwExternalContact qwExternalContacts, QwUser user) {
+        FastGptChatSession fastGptChatSession = fastGptChatSessionMapper.selectFastGptChatSessionByQwExternalContactsAndUserId(qwExternalContacts.getId(), user.getId());
+        if (fastGptChatSession==null){
+            fastGptChatSession = new FastGptChatSession();
+            String chatId = UUID.randomUUID().toString();
+            fastGptChatSession.setChatId(chatId);
+            fastGptChatSession.setKfId(user.getFastGptRoleId().toString());
+            fastGptChatSession.setStatus(1);
+            fastGptChatSession.setRemindCount(0);
+            fastGptChatSession.setRemindStatus(0);
+            fastGptChatSession.setCreateTime(new Date());
+            fastGptChatSession.setQwExtId(qwExternalContacts.getId());
+            fastGptChatSession.setQwUserId(user.getId());
+            fastGptChatSession.setIsArtificial(0);
+            fastGptChatSession.setAvatar(qwExternalContacts.getAvatar());
+            fastGptChatSession.setNickName(qwExternalContacts.getName());
+            fastGptChatSession.setCompanyId(user.getCompanyId());
+            fastGptChatSession.setLastTime(new Date());
+            fastGptChatSession.setIsReply(0);
+            fastGptChatSessionMapper.insertFastGptChatSession(fastGptChatSession);
+            addUserSex(qwExternalContacts);
+        }else {
+            if (fastGptChatSession.getRemindStatus()==1){
+                FastGptChatSession ss = new FastGptChatSession();
+                ss.setSessionId(fastGptChatSession.getSessionId());
+                ss.setRemindStatus(0);
+                ss.setIsReply(0);
+                fastGptChatSessionMapper.updateFastGptChatSession(ss);
+            }
+        }
+        return fastGptChatSession;
+    }
+    private FastGptChatSession getFastGptSession(QwExternalContact qwExternalContacts, QwUser user,WxWorkVid2UserIdRespDTO dto) {
+        FastGptChatSession fastGptChatSession = fastGptChatSessionMapper.selectFastGptChatSessionByQwExternalContactsAndUserId(qwExternalContacts.getId(), user.getId());
+        if (fastGptChatSession==null){
+            fastGptChatSession = new FastGptChatSession();
+            String chatId = UUID.randomUUID().toString();
+            fastGptChatSession.setChatId(chatId);
+            fastGptChatSession.setKfId(user.getFastGptRoleId().toString());
+            fastGptChatSession.setStatus(1);
+            fastGptChatSession.setRemindCount(0);
+            fastGptChatSession.setRemindStatus(0);
+            fastGptChatSession.setCreateTime(new Date());
+            fastGptChatSession.setQwExtId(qwExternalContacts.getId());
+            fastGptChatSession.setQwUserId(user.getId());
+            fastGptChatSession.setIsArtificial(0);
+            fastGptChatSession.setAvatar(qwExternalContacts.getAvatar());
+            fastGptChatSession.setNickName(qwExternalContacts.getName());
+            fastGptChatSession.setCompanyId(user.getCompanyId());
+            fastGptChatSession.setLastTime(new Date());
+            fastGptChatSession.setIsReply(0);
+            fastGptChatSession.setUserId(String.valueOf(dto.getUser_id()));
+            fastGptChatSessionMapper.insertFastGptChatSession(fastGptChatSession);
+            addUserSex(qwExternalContacts);
+        }else {
+            if (fastGptChatSession.getRemindStatus()==1){
+                FastGptChatSession ss = new FastGptChatSession();
+                ss.setSessionId(fastGptChatSession.getSessionId());
+                ss.setRemindStatus(0);
+                ss.setIsReply(0);
+                ss.setUserId(String.valueOf(dto.getUser_id()));
+                fastGptChatSessionMapper.updateFastGptChatSession(ss);
+            }
+        }
+        return fastGptChatSession;
+    }
+    /** 特殊转人工 **/
+    @Override
+    public void artificial(QwHookVO vo) {
+        QwHookMsgVO msgVo= JSONUtil.toBean(vo.getData(),QwHookMsgVO.class);
+        if (msgVo.getIs_room()!=null&&msgVo.getIs_room()==0){
+            QwUser user=qwUserService.selectQwUserByAppKey(msgVo.getKey());
+            if(user!=null&&user.getFastGptRoleId()!=null) {
+                FastGptChatSession fastGptChatSession = fastGptChatSessionMapper.selectFastGptChatSessionByUserIdAndKfId(msgVo.getSender(), user.getId());
+                if (fastGptChatSession!=null&&fastGptChatSession.getIsArtificial()!=1){
+                    QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(msgVo.getSender_openid(), user.getCorpId(),user.getQwUserId());
+                    if (qwExternalContacts!=null){
+                        FastGptChatSession s = new FastGptChatSession();
+                        s.setIsArtificial(1);
+                        s.setSessionId(fastGptChatSession.getSessionId());
+                        fastGptChatSessionMapper.updateFastGptChatSession(s);
+                        QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(user.getCorpId());
+                        qwSendAppMsg(qwCompany.getCorpId(),Integer.parseInt(qwCompany.getServerAgentId().trim()),user.getQwUserId(),"您的客户:"+qwExternalContacts.getName()+" 因发送图片或视频,需要转人工,请及时回复");
+                        qwSendAppMsg(qwCompany.getCorpId(),Integer.parseInt(qwCompany.getServerAgentId().trim()),user.getQwUserId(),qwExternalContacts.getName());
+                        if (fastGptChatSession.getRemindStatus()==1){
+                            FastGptChatSession ss = new FastGptChatSession();
+                            ss.setSessionId(fastGptChatSession.getSessionId());
+                            ss.setRemindStatus(0);
+                            ss.setIsReply(0);
+                            fastGptChatSessionMapper.updateFastGptChatSession(ss);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    void qwSendAppMsg(String corpId,Integer agentId, String qwUserId,String content){
+        QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(corpId);
+        QwSendMsgParam qwSendMsgParam2 = new QwSendMsgParam();
+        qwSendMsgParam2.setAgentid(agentId);
+        qwSendMsgParam2.setTouser(qwUserId);
+        QwSendMsgParam.Text text2 = new QwSendMsgParam.Text();
+        text2.setContent(content);
+        qwSendMsgParam2.setText(text2);
+        qwSendMsgParam2.setMsgtype("text");
+        qwApiService.sendMsg(qwSendMsgParam2, qwCompany.getCorpId());
+    }
+    /** 添加企微性别 **/
+    private void addUserSex(QwExternalContact qwExternalContacts) {
+
+        QwExternalContactInfo info = qwExternalContactInfoMapper.selectQwExternalContactInfoByExternalContactId(qwExternalContacts.getId());
+        if (qwExternalContacts.getGender()!=0){
+            if (info!=null){
+                info.setSex(qwExternalContacts.getGender()==1?"男":"女");
+                qwExternalContactInfoMapper.updateQwExternalContactInfo(info);
+
+            }else {
+                QwExternalContactInfo qwExternalContactInfo = new QwExternalContactInfo();
+                qwExternalContactInfo.setExternalContactId(qwExternalContacts.getId());
+                qwExternalContactInfo.setSex(qwExternalContacts.getGender()==1?"男":"女");
+                qwExternalContactInfo.setCreateTime(new Date());
+                qwExternalContactInfoMapper.insertQwExternalContactInfo(qwExternalContactInfo);
+
+            }
+        }
+
+
+
+    }
+    /** Ai发送课程链接 **/
+    private void sendUrlLink(String content, QwHookMsgVO msgVo, QwUser user, QwSession session) {
+        if (content.contains("【发送课程:当天课程】")){
+            FsCourseWatchLogVO fsCourseWatchLogVO = watchLogMapper.selectFsCourseWatchLogByExtIdAndQwUserId(session.getQwExtId(), user.getId());
+            if (fsCourseWatchLogVO!=null){
+                FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
+                param.setVideoId(fsCourseWatchLogVO.getVideoId());
+                param.setQwUserId(String.valueOf(user.getId()));
+                param.setDays(1);
+                param.setCorpId(user.getCorpId());
+                param.setCourseId(fsCourseWatchLogVO.getCourseId());
+                param.setCompanyUserId(user.getCompanyUserId());
+                param.setCompanyId(user.getCompanyId());
+                param.setQwExternalId(Long.parseLong(session.getQwExtId()));
+                param.setSendTime(new Date());
+                R linkUrl = iFsCourseLinkService.createLinkUrlWc(param);
+                qwContactWayService.addWatchLogIfNeeded(Integer.valueOf(fsCourseWatchLogVO.getVideoId()+""),
+                        Integer.valueOf(fsCourseWatchLogVO.getCourseId()+""),
+                        String.valueOf(user.getId()),
+                        String.valueOf(user.getCompanyUserId()),
+                        String.valueOf(user.getCompanyId()),
+                        String.valueOf(session.getQwExtId()));
+                if (linkUrl != null && linkUrl.get("url") != null) {
+                    String s = (String)linkUrl.get("url");
+                    sendWebSocketMsg(s,msgVo,user,session);
+                }
+            }
+        }
+        else if (content.contains("【发送课程")){
+            Pattern c = Pattern.compile("【发送课程:(.*?)】", Pattern.DOTALL);
+            Matcher cMatcher = c.matcher(content);
+            while  (cMatcher.find()) {
+                String trim = cMatcher.group(1).trim();
+                if(trim!=null&&!trim.equals("")){
+                    FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoStringId(trim);
+                    if (fsUserCourseVideo==null){
+                        FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
+                        param.setVideoId(fsUserCourseVideo.getVideoId());
+                        param.setQwUserId(String.valueOf(user.getId()));
+                        param.setDays(1);
+                        param.setCorpId(user.getCorpId());
+                        param.setCourseId(fsUserCourseVideo.getCourseId());
+                        param.setCompanyUserId(user.getCompanyUserId());
+                        param.setCompanyId(user.getCompanyId());
+                        param.setQwExternalId(Long.parseLong(session.getQwExtId()));
+                        param.setSendTime(new Date());
+                        R linkUrl = iFsCourseLinkService.createLinkUrlWc(param);
+                        qwContactWayService.addWatchLogIfNeeded(Integer.valueOf(fsUserCourseVideo.getVideoId()+""),
+                                Integer.valueOf(fsUserCourseVideo.getCourseId()+""),
+                                String.valueOf(user.getId()),
+                                String.valueOf(user.getCompanyUserId()),
+                                String.valueOf(user.getCompanyId()),
+                                String.valueOf(session.getQwExtId()));
+                        if (linkUrl != null && linkUrl.get("url") != null) {
+                            String s = (String)linkUrl.get("url");
+                            sendWebSocketMsg(s,msgVo,user,session);
+                        }
+                    }
+
+                }else {
+                    log.error("发送课程不存在:"+trim);
+                }
+
+            }
+        }
+    }
+    /** 发送Ai消息 **/
+    private R  sendAiMsg(Integer i,FastGptChatSession fastGptChatSession, FastGptRole role,QwUser user,Long qwExternalContactsId,String appKey,QwExternalContact qwExternalContacts){
+        //等待5秒
+        try {
+            Thread.sleep(10000); // 5000 毫秒 = 5 秒
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        //获取现在的次数
+        Integer reply = (Integer)redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
+        if (reply!=i){
+            //次数变动 重新等待5秒
+            return   sendAiMsg(reply,fastGptChatSession,role,user,qwExternalContactsId,appKey,qwExternalContacts);
+        }else {
+            System.out.println("开始ai回答");
+            ChatParam param=new ChatParam();
+            param.setChatId(fastGptChatSession.getChatId());
+            param.setStream(false);
+            param.setDetail(true);
+            ChatParam.Variables variables=new ChatParam.Variables();
+            variables.setUid(user.getFastGptRoleId().toString());
+            variables.setName("test");
+            param.setVariables(variables);
+            List<ChatParam.Message> messageList=new ArrayList<ChatParam.Message>();
+            param.setMessages(messageList);
+            //添加看客记录
+            addCourseWatchLog(qwExternalContactsId);
+            String msgC = (String)redisCache.getCacheObject("msg:" + fastGptChatSession.getSessionId());
+            //添加关键词
+            addPromptWord(messageList,msgC,qwExternalContactsId,role.getReminderWords(), role.getContactInfo(),fastGptChatSession.getSessionId());
+            R r = chatService.initiatingTakeChat(param,"http://154.8.194.176:3000/api",appKey);
+            Integer reply2 = (Integer)redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
+            //次数变动 重新等待5秒
+            if (reply2!=i){
+                System.out.println("等待");
+                return   sendAiMsg(reply,fastGptChatSession,role,user,qwExternalContactsId,appKey,qwExternalContacts);
+            }
+            addSaveAiMsg(2,1,messageList.get(0).getContent(),user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),null,null,null);
+            return r;
+        }
+
+    }
+    /** 增加课程信息 **/
+    private void addCourseWatchLog(Long id) {
+        FsCourseWatchLogVO log = fsCourseWatchLogMapper.selectFsCourseWatchLogByExtId(id);
+        if (log!=null){
+            QwExternalContactInfo qwExternalContactInfo = qwExternalContactInfoMapper.selectQwExternalContactInfoByExternalContactId(id);
+            QwExternalContactInfo info = new QwExternalContactInfo();
+//            Date dateToCheck = log.getCreateTime(); // 假设这是你要检查的日期
+//            Date today = new Date(); // 获取当前日期
+           // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 格式化日期
+            String name = log.getCourseName() + log.getTitle();
+            name = name.replaceAll("[【】]", "");
+//            if (sdf.format(dateToCheck).equals(sdf.format(today))) {
+                info.setStudy(name);
+                info.setCourseStatus(log.getLogType()==3?"待看课":log.getLogType()==1?"已完课":log.getLogType()==2?"已完课":"看课中断");
+
+//            }else {
+//                info.setStudy(name);
+//                info.setCourseStatus("待看课");
+//
+//
+//            }
+            if(qwExternalContactInfo!=null){
+                info.setId(qwExternalContactInfo.getId());
+                qwExternalContactInfoMapper.updateQwExternalContactInfo(info);
+            }else {
+                info.setExternalContactId(id);
+                info.setCreateTime(new Date());
+                qwExternalContactInfoMapper.insertQwExternalContactInfo(info);
+
+            }
+        }
+    }
+    /** 组装发送AI内容 **/
+    private void addPromptWord(List<ChatParam.Message> messageList,String count,Long extId,String words,String countInfo,Long sessionId){
+
+        String  str="";
+        List<FastGptChatMsg> msgs=fastGptChatMsgService.selectFastGptChatMsgByMsgSessionId(sessionId);
+        if (!msgs.isEmpty()){
+            Collections.reverse(msgs);
+            msgs.remove(msgs.size() - 1);
+            str="【历史聊天内容:\n";
+            for (FastGptChatMsg msg : msgs) {
+                Integer sendType = msg.getSendType();
+                String content = msg.getContent();
+                if(sendType!=1){
+                    if (content!=null&&content.length()>150){
+                        continue;
+                    }
+                }
+
+                str +=(sendType==1?"用户:":"AI:")+content+"\n";
+            }
+            str+="】\n";
+        }
+
+        // 这里获取后台的提示词进行匹配
+        QwExternalContactInfo info = qwExternalContactInfoMapper.selectQwExternalContactInfoByExternalContactId(extId);
+        if(info==null){
+            info=new QwExternalContactInfo();
+        }
+        if (info!=null){
+            str+="【用户状态信息\n";
+            Field[] fields = info.getClass().getDeclaredFields();
+            for (Field field : fields) {
+                field.setAccessible(true);
+                Excel annotation = field.getAnnotation(Excel.class);
+                if (annotation != null) {
+                    String name = field.getName();
+                    String fieldName = annotation.name();
+                    String[] split = countInfo.split(",");
+                    for (String zName : split) {
+                        if (zName.equals(name)) {
+                            Object value = null;
+                            try {
+                                value = field.get(info);
+                            } catch (IllegalAccessException e) {
+                            }
+                            if (value != null) {
+                                str += fieldName + ": " + value.toString() + "\n";
+                            }else {
+                                str += fieldName + ":  \n";
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        str+="】\n";
+        if (words!=null&&!"".equals(words)){
+            str+="【你的角色信息:以下内容为你的信息状态的补充而非用户信息,相当于放在角色任务里面,问到了需要知晓,但是如果无关的时候请无视此段内容 "+"\""+words+"\""+"】\n";
+        }
+        if (count!=null&&!"".equals(count)){
+            str+="【用户说的话内容(之前的内容仅仅为背景,你知道即可,以下才是用户真实说的话的内容)\n" +
+                    "\""+count+"\""+"\n" +
+                    "】";
+        }
+
+
+        ChatParam.Message message1=new ChatParam.Message();
+        message1.setRole("user");
+        message1.setContent(str);
+        messageList.add(message1);
+    }
+    /** 组装表情 **/
+    public static List<String> countString(String input) {
+
+        List<String> stringList = new ArrayList<>();
+        // 所有的回车都分段发送
+        String[] split = input.split("\n");
+        for (String s : split) {
+            List<String> sList = subCount(s);
+            stringList.addAll(sList);
+        }
+//        if (isEmoji){
+//            Random random = new Random();
+//            String[] emojiMorning = new String[] {
+//                    "😊",  // 微笑
+//                    "☀️",  // 太阳
+//                    "🌹",  // 玫瑰
+//                    "☕️",  // 茶杯
+//                    "💪",  // 强壮
+//                    "❤️"   // 爱心
+//            };
+//            String[] emojiEvening = new String[] {
+//                    "😊",  // 微笑
+//                    "🌟",  // 星星
+//                    "🌹",  // 玫瑰
+//                    "☕️",  // 茶杯
+//                    "💪",  // 强壮
+//                    "❤️"   // 爱心
+//            };
+//            List<String> sebdList = new ArrayList<>();
+//            for (String segment : stringList) {
+//                if(!segment.trim().equals("")){
+//                    LocalTime currentTime = LocalTime.now();
+//                    LocalTime startTime = LocalTime.of(5, 0);
+//                    LocalTime endTime   = LocalTime.of(18, 0);
+//                    int sj = random.nextInt(2);
+//                    // 判断当前时间是否在5点到18点之间
+//                    String emj="";
+//                    if (sj==0){
+//                        if (currentTime.isAfter(startTime) && currentTime.isBefore(endTime)) {
+//                            int i = random.nextInt(1);
+//                            emj=emojiMorning[random.nextInt(emojiMorning.length-1)];
+//                        } else {
+//                            emj=emojiEvening[random.nextInt(emojiEvening.length-1)];
+//                        }
+//                    }
+//                    sebdList.add(segment+emj) ;
+//                }
+//
+//            }
+//            return sebdList;
+//        }
+
+
+        return stringList;
+    }
+    /** 内容分段 **/
+    static List<String> subCount(String s){
+        ArrayList<String> a = new ArrayList<>();
+        if (s.length()>30){
+            String substring = s.substring(30);
+            Pattern pattern = Pattern.compile("([~。!?]+)");
+            String[] segments = pattern.split(substring);
+            Matcher matcher = pattern.matcher(substring);
+            if (matcher.find()&&segments.length>1) {
+                int dh=0;
+                String group = matcher.group();
+                if (group.equals("。")){
+                    group="";
+                    dh=1;
+                }
+                String s1 = s.substring(0, 30) + segments[0] + group;
+                a.addAll(Arrays.asList(s1));
+                if (s.substring(s1.length())!=null&&!s.substring(s1.length()+dh).equals("")){
+                    List<String> add = subCount(s.substring(s1.length()+dh));
+                    a.addAll(add);
+                }
+            }else {
+                a.add(s);
+                return  a;
+            }
+        }else {
+            a.add(s);
+        }
+        return  a;
+    }
+    /** 增加用户信息以及打标签 **/
+    private void addUserInfo(String word,Long extId,FastGptChatSession fastGptChatSession)  {
+        Pattern pattern = Pattern.compile("【用户状态信息(.*?)】", Pattern.DOTALL);
+        Matcher matcher = pattern.matcher(word);
+
+        while  (matcher.find()) {
+            QwExternalContactInfo info = qwExternalContactInfoMapper.selectQwExternalContactInfoByExternalContactId(extId);
+            String trim = matcher.group(1).trim();
+            String[] zd = trim.split("\n");
+            boolean b=false;
+
+            if (info==null){
+                info=new QwExternalContactInfo();
+                b=true;
+            }
+            Field[] fields = info.getClass().getDeclaredFields();
+            for (Field field : fields) {
+                field.setAccessible(true);
+                Excel annotation = field.getAnnotation(Excel.class);
+                if (annotation != null) {
+                    //中文名称
+                    String fieldName = annotation.name();
+                    String valueName ="";
+                    Object value = null;
+                    try {
+                        value = field.get(info);
+                    } catch (IllegalAccessException e) {
+                    }
+                    if (value != null) {
+                        valueName= value.toString();
+                    }
+                    for (String s : zd) {
+                        String[] zdName=null;
+                        if (s.contains(":")){
+                            zdName = s.split(":");
+                        }
+                        if (s.contains(":")){
+                            zdName = s.split(":");
+                        }
+                        if (zdName!=null&&zdName.length==2){
+                            String name1 = zdName[0];
+                            String name2 = zdName[1];
+                            if (name1.trim().equals(fieldName)){
+                                String name2Trim = name2.trim();
+                                if (!name2Trim.isEmpty()){
+                                    if(name1.equals("交流状态")){
+                                        if (name2Trim.equals("非首次交流")){
+                                            if (fastGptChatSession.getRemindStatus()!=null&&fastGptChatSession.getRemindStatus()==1){
+                                                FastGptChatSession s1 = new FastGptChatSession();
+                                                s1.setSessionId(fastGptChatSession.getSessionId());
+                                                s1.setRemindStatus(0);
+                                                s1.setRemindCount(0);
+                                                fastGptChatSessionMapper.updateFastGptChatSession(s1);
+                                            }
+                                        }
+                                    }
+                                    if (!valueName.trim().equals(name2Trim)){
+                                        if(name1.equals("学习到的章节")||name1.equals("今日课程完成情况")){
+                                            continue;
+                                        }
+
+                                        if (name2Trim.contains("delete")&&name2Trim.contains(";")){
+                                            name2Trim = name2Trim.replaceAll("delete.*?;", "");
+                                        }else if (name2Trim.contains("delete")){
+                                            name2Trim=" ";
+                                        }
+                                        try {
+                                            // 允许修改私有属性
+                                            field.setAccessible(true);
+                                            // 修改 name 属性的值
+                                            field.set(info, name2Trim);
+                                            b=true;
+                                        } catch (Exception e) {
+                                            System.out.println("修改错误");
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if (b){
+                if (info.getId()!=null){
+                    qwExternalContactInfoMapper.updateQwExternalContactInfo(info);
+                }else {
+                    info.setExternalContactId(extId);
+                    info.setCreateTime(new Date());
+                    qwExternalContactInfoMapper.insertQwExternalContactInfo(info);
+                }
+            }
+        }
+        Pattern tag = Pattern.compile("【标签:(.*?)】", Pattern.DOTALL);
+        Matcher TagMatcher = tag.matcher(word);
+        while  (TagMatcher.find()) {
+            String trimTag = TagMatcher.group(1).trim();
+            if(trimTag!=null&&!trimTag.equals("")){
+                qwTagGroupService.addQwTagByAi(trimTag,extId);
+            }
+
+        }
+
+        Pattern delTag = Pattern.compile("【移除标签:(.*?)】", Pattern.DOTALL);
+        Matcher delTagMatcher = delTag.matcher(word);
+        while  (delTagMatcher.find()) {
+            String deTag = delTagMatcher.group(1).trim();
+            if(deTag!=null&&!deTag.equals("")){
+                qwTagGroupService.delQwTagByAi(deTag,extId);
+            }
+
+        }
+
+        Pattern addTime = Pattern.compile("【开始计时:(.*?)】", Pattern.DOTALL);
+        Matcher addTimeMather = addTime.matcher(word);
+        while  (addTimeMather.find()) {
+            try {
+                String time = addTimeMather.group(1).trim();
+                int t = Integer.parseInt(time);
+                    FastGptChatSession s1 = new FastGptChatSession();
+                    s1.setSessionId(fastGptChatSession.getSessionId());
+                    Calendar calendar = Calendar.getInstance(); // 获取当前时间
+                    calendar.add(Calendar.MINUTE, t); // 增加t分钟
+                    s1.setRemindTime(calendar.getTime());
+                    s1.setRemindStatus(1);
+                    s1.setRemindCount(0);
+                    fastGptChatSessionMapper.updateFastGptChatSession(s1);
+
+            }catch (Exception e){
+                log.info("ai计时错误"+word);
+            }
+
+        }
+    }
+    /** 过滤[] **/
+    private static String replace(String s){
+
+        if(org.springframework.util.StringUtils.isEmpty(s)) return "";
+
+        String regex1 = "<llnnerThoughtBeginl>[\\s\\S]*?<lnnerThoughtEnd|>";
+        // 创建 Pattern 对象
+        Pattern pattern1 = Pattern.compile(regex1);
+        // 创建 Matcher 对象
+        Matcher matcher1 = pattern1.matcher(s);
+        s = matcher1.replaceAll("");
+
+        String regex = "【[\\s\\S]*?】";
+        // 创建 Pattern 对象
+        Pattern pattern = Pattern.compile(regex);
+        // 创建 Matcher 对象
+        Matcher matcher = pattern.matcher(s);
+        // 替换匹配到的内容
+        return matcher.replaceAll("");
+    }
+    /** 过滤[] **/
+    private static String replaceWxEmo(String s){
+
+        if(org.springframework.util.StringUtils.isEmpty(s)) return "";
+        // 替换匹配到的内容
+        return s.replaceAll("\\[.*?]", "").trim();
+    }
+    /** 发送语音过滤 */
+    private  String voiceHomo(String content){
+        List<FastgptChatVoiceHomo> homos = fastgptChatVoiceHomoMapper.selectFastgptChatVoiceHomoList(new FastgptChatVoiceHomo());
+        for (FastgptChatVoiceHomo homo : homos) {
+            if (content.contains(homo.getContent())) {
+                // 如果包含目标字段,则替换
+                content= content.replace(homo.getContent(), homo.getChangeCount());
+            } else {
+            }
+        }
+        content = content.replaceAll("[a-zA-Z]", "")
+                .replaceAll("\\s", "");
+        return content;
+    }
+    /** 替换违禁词 **/
+    private  String replaceWords(String content){
+        List<FastGptChatReplaceWords> words = fastGptChatReplaceWordsMapper.selectAllFastGptChatReplaceWords();
+        for (FastGptChatReplaceWords word : words) {
+            if (content.contains(word.getContent())) {
+                // 如果包含目标字段,则替换
+                content= content.replace(word.getContent(), word.getChangeCount());
+            } else {
+            }
+        }
+        return content;
+    }
+    /** 发送语音回复 */
+    private static AudioVO getSilk(String count,Long userId) {
+        AudioServiceImpl audioService = new AudioServiceImpl();
+        AudioVO Silk = audioService.TextToVoice(count,userId);
+        return Silk;
+    }
+    /** 发送普通消息 */
+    private void sendWebSocketMsg(String count, QwHookMsgVO msgVo, QwUser user,QwSession session) {
+        //发送socket
+        if (count!=null&& !count.trim().isEmpty()){
+            qwMsgService.addAiMsg(session,count,1,user);
+            QwHookSendMsgParam sendMsgParam=new QwHookSendMsgParam();
+            QwHookSendMsgParam.QwHookSendMsgData sendMsgData=new QwHookSendMsgParam.QwHookSendMsgData();
+            sendMsgParam.setType(101003);
+            sendMsgData.setMsg(replaceWords(count));
+            sendMsgData.setSendId(msgVo.getSender());
+            sendMsgData.setSyncKey("1");
+            sendMsgParam.setData(sendMsgData);
+            SendAIParam sendAIParam = new SendAIParam();
+            sendAIParam.setCmd("aiReplyMsg");
+            sendAIParam.setData(JSONUtil.toJsonStr(sendMsgParam));
+            sendAIParam.setKey(user.getAppKey());
+            redisTemplate.opsForList().leftPush("AiMsg:"+user.getAppKey(), JSON.toJSONString(sendAIParam));
+        }
+
+
+    }
+
+    /** 发送定时任务课程链接 */
+    private void sendTaskUrlLink(String content, String sendId, QwUser user, FastGptChatSession session) {
+        if (content.contains("【发送课程:当天课程】")){
+            FsCourseWatchLogVO fsCourseWatchLogVO = watchLogMapper.selectFsCourseWatchLogByExtIdAndQwUserId(session.getQwExtId().toString(), user.getId());
+            if (fsCourseWatchLogVO!=null){
+                FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
+                param.setVideoId(fsCourseWatchLogVO.getVideoId());
+                param.setQwUserId(String.valueOf(user.getId()));
+                param.setDays(1);
+                param.setCorpId(user.getCorpId());
+                param.setCourseId(fsCourseWatchLogVO.getCourseId());
+                param.setCompanyUserId(user.getCompanyUserId());
+                param.setCompanyId(user.getCompanyId());
+                param.setQwExternalId(session.getQwExtId());
+                param.setSendTime(new Date());
+                R linkUrl = iFsCourseLinkService.createLinkUrlWc(param);
+                qwContactWayService.addWatchLogIfNeeded(Integer.valueOf(fsCourseWatchLogVO.getVideoId()+""),
+                        Integer.valueOf(fsCourseWatchLogVO.getCourseId()+""),
+                        String.valueOf(user.getId()),
+                        String.valueOf(user.getCompanyUserId()),
+                        String.valueOf(user.getCompanyId()),
+                        String.valueOf(session.getQwExtId()));
+                if (linkUrl != null && linkUrl.get("url") != null) {
+                    String s = (String)linkUrl.get("url");
+                    sendWebTaskSocketMsg(s,sendId,user);
+                }
+            }
+        }
+        else if (content.contains("【发送课程")){
+            Pattern c = Pattern.compile("【发送课程:(.*?)】", Pattern.DOTALL);
+            Matcher cMatcher = c.matcher(content);
+            while  (cMatcher.find()) {
+                String trim = cMatcher.group(1).trim();
+                if(trim!=null&&!trim.equals("")){
+                    FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoStringId(trim);
+                    System.out.println("课程:"+fsUserCourseVideo);
+                    FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
+                    param.setVideoId(fsUserCourseVideo.getVideoId());
+                    param.setQwUserId(String.valueOf(user.getId()));
+                    param.setDays(1);
+                    param.setCorpId(user.getCorpId());
+                    param.setCourseId(fsUserCourseVideo.getCourseId());
+                    param.setCompanyUserId(user.getCompanyUserId());
+                    param.setCompanyId(user.getCompanyId());
+                    param.setQwExternalId(session.getQwExtId());
+                    param.setSendTime(new Date());
+                    R linkUrl = iFsCourseLinkService.createLinkUrlWc(param);
+                    qwContactWayService.addWatchLogIfNeeded(Integer.valueOf(fsUserCourseVideo.getVideoId()+""),
+                            Integer.valueOf(fsUserCourseVideo.getCourseId()+""),
+                            String.valueOf(user.getId()),
+                            String.valueOf(user.getCompanyUserId()),
+                            String.valueOf(user.getCompanyId()),
+                            String.valueOf(session.getQwExtId()));
+                    if (linkUrl != null && linkUrl.get("url") != null) {
+                        String s = (String)linkUrl.get("url");
+                        sendWebTaskSocketMsg(s,sendId,user);
+                    }
+                }
+
+            }
+        }
+
+
+
+    }
+    /** 发送定时任务消息 */
+    private void sendWebTaskSocketMsg(String count, String sendId, QwUser user) {
+        //发送socket
+        //文本消息
+        QwHookSendMsgParam sendMsgParam=new QwHookSendMsgParam();
+        QwHookSendMsgParam.QwHookSendMsgData sendMsgData=new QwHookSendMsgParam.QwHookSendMsgData();
+        sendMsgParam.setType(101003);
+        sendMsgData.setMsg(count);
+        sendMsgData.setSendId(sendId);
+        sendMsgData.setSyncKey("1");
+        sendMsgParam.setData(sendMsgData);
+        SendAIParam sendAIParam = new SendAIParam();
+        sendAIParam.setCmd("aiReplyMsg");
+        sendAIParam.setData(JSONUtil.toJsonStr(sendMsgParam));
+        sendAIParam.setKey(user.getAppKey());
+        redisTemplate.opsForList().leftPush("AiMsg:"+user.getAppKey(), JSON.toJSONString(sendAIParam));
+    }
+    @Override
+    public R qwHookNotifyAddMsg(Long qwUserID, Long sender,String count,String uid) {
+        QwUser sendUser = qwUserMapper.selectQwUserById(qwUserID);
+
+
+        if (sendUser!=null){
+
+            String extId = getExtId(sender, uid, sendUser.getServerId());
+            QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(extId, sendUser.getCorpId(),sendUser.getQwUserId());
+            if (qwExternalContacts==null){
+                return R.ok();
+            }
+             FastGptChatSession fastGptChatSession = fastGptChatSessionMapper.selectFastGptChatSessionByQwExternalContactsAndUserId(qwExternalContacts.getId(), sendUser.getId());
+            if (fastGptChatSession!=null){
+                saveQwUserMsg(fastGptChatSession,2,count);
+            }else {
+
+                if(qwExternalContacts.getType()!=null&&qwExternalContacts.getType()==1){
+                    if(sendUser.getFastGptRoleId()!=null){
+                        fastGptChatSession = new FastGptChatSession();
+                        String chatId = UUID.randomUUID().toString();
+                        fastGptChatSession.setChatId(chatId);
+                        fastGptChatSession.setKfId(sendUser.getFastGptRoleId().toString());
+                        fastGptChatSession.setStatus(1);
+                        fastGptChatSession.setRemindCount(0);
+                        fastGptChatSession.setRemindStatus(0);
+                        fastGptChatSession.setCreateTime(new Date());
+                        fastGptChatSession.setQwExtId(qwExternalContacts.getId());
+                        fastGptChatSession.setQwUserId(sendUser.getId());
+                        fastGptChatSession.setIsArtificial(0);
+                        fastGptChatSession.setAvatar(qwExternalContacts.getAvatar());
+                        fastGptChatSession.setNickName(qwExternalContacts.getName());
+                        fastGptChatSession.setCompanyId(sendUser.getCompanyId());
+                        fastGptChatSession.setLastTime(new Date());
+                        fastGptChatSession.setIsReply(0);
+                        fastGptChatSessionMapper.insertFastGptChatSession(fastGptChatSession);
+                        addUserSex(qwExternalContacts);
+                        saveQwUserMsg(fastGptChatSession,2,count);
+                    }
+                }
+            }
+        }
+        return R.ok();
+    }
+
+    @Override
+    public void expireAiMsg() {
+//        List<QwSopLogs> qwSopLogs = qwSopLogsMapper.selectExpireAiMsg();
+//        if (qwSopLogs==null|| qwSopLogs.isEmpty()){
+//            System.out.println("无");
+//            return;
+//        }
+//        qwSopLogsMapper.batchUpdateQwSopLogsById(qwSopLogs);
+//        List<QwSopLogs> distinctList = new ArrayList<>(qwSopLogs.stream()
+//                .collect(Collectors.toMap(
+//                        QwSopLogs::getExternalId,  // 以 extId 作为 Key
+//                        log -> log,            // Value 是对象本身
+//                        (existing, replacement) -> existing  // 遇到重复时保留已有值(第一个)
+//                ))
+//                .values());
+//        for (QwSopLogs logs : distinctList) {
+//            log.info("转人工:"+logs.getCorpId());
+//            QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(logs.getCorpId());
+//            sendQwAppMsg(logs.getCorpId(),Integer.parseInt(qwCompany.getServerAgentId().trim()),logs.getQwUserid(),"您的客户:"+logs.getExternalUserName()+"因  AI回复内容过期转人工,请及时登录插件确定定位配置准确");
+//        }
+
+    }
+
+
+    String getExtId(Long id,String uid,Long serverId){
+        WxWorkVid2UserIdDTO wxWorkVid2UserIdDTO = new WxWorkVid2UserIdDTO();
+        wxWorkVid2UserIdDTO.setUser_id(Arrays.asList(id));
+        wxWorkVid2UserIdDTO.setUuid(uid);
+        WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> WxWorkVid2UserIdRespDTO = wxWorkService.Vid2UserId(wxWorkVid2UserIdDTO,serverId);
+        List<WxWorkVid2UserIdRespDTO> data = WxWorkVid2UserIdRespDTO.getData();
+        if (data==null&& data.isEmpty()){
+            System.out.println("未获取到extId");
+            return "";
+        }
+        com.fs.wxwork.dto.WxWorkVid2UserIdRespDTO dto = data.get(0);
+        return dto.getOpenid();
+    }
+
+    @Override
+    public WxWorkResponseDTO<String> getFileUrl(String uuid, String fileId, String aesKey, String authKey, String fileName, Integer fileSize, Long serverId) {
+        WxwDownloadWeChatFileDTO weChatFileDTO = new WxwDownloadWeChatFileDTO();
+        weChatFileDTO.setUuid(uuid);
+        weChatFileDTO.setUrl(fileId);
+        weChatFileDTO.setAes_key(aesKey);
+        weChatFileDTO.setAuth_key(authKey);
+        weChatFileDTO.setFile_name(fileName);
+        weChatFileDTO.setSize(fileSize);
+        return wxWorkService.downloadWeChatFile(weChatFileDTO, serverId);
+    }
+
+}

+ 1205 - 0
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiNewServiceImpl.java

@@ -0,0 +1,1205 @@
+package com.fs.fastGpt.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.course.mapper.FsUserCourseVideoMapper;
+import com.fs.course.param.FsCourseLinkCreateParam;
+import com.fs.course.service.IFsCourseLinkService;
+import com.fs.course.vo.FsCourseWatchLogVO;
+import com.fs.fastGpt.config.ModeConfig;
+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.SendAIParam;
+import com.fs.fastGpt.service.AiNewService;
+import com.fs.fastGpt.service.IFastGptChatMsgService;
+import com.fs.fastGpt.service.IFastGptRoleService;
+import com.fs.fastgptApi.param.ChatParam;
+import com.fs.fastgptApi.result.ChatDetailTStreamFResult;
+import com.fs.fastgptApi.service.ChatService;
+import com.fs.fastgptApi.service.Impl.AudioServiceImpl;
+import com.fs.fastgptApi.vo.AudioVO;
+import com.fs.qw.domain.*;
+import com.fs.qw.mapper.QwCompanyMapper;
+import com.fs.qw.mapper.QwExternalContactInfoMapper;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.service.*;
+import com.fs.qwApi.param.QwSendMsgParam;
+import com.fs.qwApi.service.QwApiService;
+import com.fs.qwHookApi.param.QwHookSendMsgParam;
+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 lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+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.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class AiNewServiceImpl implements AiNewService {
+    @Autowired
+    private FastGptChatSessionMapper fastGptChatSessionMapper;
+    @Autowired
+    private ChatService chatService;
+    @Autowired
+    private QwUserMapper qwUserMapper;
+    @Autowired
+    private IFastGptRoleService roleService;
+    @Autowired
+    private QwExternalContactInfoMapper qwExternalContactInfoMapper;
+    @Autowired
+    private IFastGptChatMsgService fastGptChatMsgService;
+    @Autowired
+    private IQwContactWayService qwContactWayService;
+    @Autowired
+    QwApiService qwApiService;
+    @Autowired
+    QwCompanyMapper qwCompanyMapper;
+    @Autowired
+    FsCourseWatchLogMapper fsCourseWatchLogMapper;
+    @Autowired
+    FsUserCourseVideoMapper fsUserCourseVideoMapper;
+    @Autowired
+    FsCourseWatchLogMapper   watchLogMapper;
+    @Autowired
+    IQwJsApiService qwGetJsapiTicketService;
+    @Autowired
+    private IFsCourseLinkService iFsCourseLinkService;
+    @Autowired
+    FastgptChatVoiceHomoMapper fastgptChatVoiceHomoMapper;
+    @Autowired
+    RedisCache redisCache;
+    @Autowired
+    private IQwUserService qwUserService;
+
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+    @Autowired
+    private IQwMsgService qwMsgService;
+    @Autowired
+    private IQwTagGroupService qwTagGroupService;
+    @Autowired
+    private FastGptChatReplaceWordsMapper fastGptChatReplaceWordsMapper;
+    @Autowired
+    RedisTemplate<String, String> redisTemplate;
+
+
+    /** Ai半小时未回复提醒 **/
+    @Override
+    public void AiRemind() {
+        List<FastGptChatSession> fastGptChatSessions = fastGptChatSessionMapper.selectFastGptChatSessionByRemind();
+        for (FastGptChatSession fastGptChatSession : fastGptChatSessions) {
+            FastGptRole role=roleService.selectFastGptRoleTypeByRoleId(Long.parseLong(fastGptChatSession.getKfId()) );
+            if (role!=null){
+                String modeConfig=role.getModeConfigJson();
+                if (modeConfig==null|| modeConfig.isEmpty()){
+                    return;
+                }
+                ModeConfig config= JSONUtil.toBean(modeConfig,ModeConfig.class);
+                String timeCount="用户未回复";
+                ChatParam param=new ChatParam();
+                param.setChatId(fastGptChatSession.getChatId());
+                param.setStream(false);
+                param.setDetail(true);
+                ChatParam.Variables variables=new ChatParam.Variables();
+                variables.setUid(fastGptChatSession.getKfId());
+                variables.setName("test");
+                param.setVariables(variables);
+                List<ChatParam.Message> messageList=new ArrayList<ChatParam.Message>();
+                param.setMessages(messageList);
+                addPromptWord(messageList,timeCount,fastGptChatSession.getQwExtId(),role.getReminderWords(), role.getContactInfo(),fastGptChatSession.getSessionId());
+                R r = chatService.initiatingTakeChat(param,"http://154.8.194.176:3000/api",config.getAPPKey());
+                if(r.get("code").equals(200)){
+                    ChatDetailTStreamFResult result=(ChatDetailTStreamFResult)r.get("data");
+                    String content = result.getChoices().get(0).getMessage().getContent();
+                    String count = replace(result.getChoices().get(0).getMessage().getContent()).trim();
+                    QwUser qwUser = qwUserMapper.selectQwUserById(fastGptChatSession.getQwUserId());
+                    if(count!=null&& !count.isEmpty()) {
+                        QwHookSendMsgParam sendMsgParam = new QwHookSendMsgParam();
+                        QwHookSendMsgParam.QwHookSendMsgData sendMsgData = new QwHookSendMsgParam.QwHookSendMsgData();
+                        sendMsgParam.setType(101003);
+                        sendMsgData.setMsg(count);
+                        sendMsgData.setSendId(fastGptChatSession.getUserId());
+                        sendMsgData.setSyncKey("1");
+                        sendMsgParam.setData(sendMsgData);
+                        SendAIParam sendAIParam = new SendAIParam();
+                        sendAIParam.setCmd("aiReplyMsg");
+                        sendAIParam.setData(JSONUtil.toJsonStr(sendMsgParam));
+                        sendAIParam.setKey(qwUser.getAppKey());
+                        redisTemplate.opsForList().leftPush("AiMsg:" + qwUser.getAppKey(), JSON.toJSONString(sendAIParam));
+                    }
+                    FastGptChatMsg fastGptChatMsgAi = new FastGptChatMsg();
+                    fastGptChatMsgAi.setContent(result.getChoices().get(0).getMessage().getContent());
+                    fastGptChatMsgAi.setSessionId(fastGptChatSession.getSessionId());
+                    fastGptChatMsgAi.setRoleId(role.getRoleId());
+                    fastGptChatMsgAi.setSendType(2);
+                    fastGptChatMsgAi.setCompanyId(qwUser.getCompanyId());
+                    fastGptChatMsgAi.setUserId(fastGptChatSession.getQwExtId().toString());
+                    fastGptChatMsgAi.setCompanyUserId(qwUser.getCompanyUserId());
+                    fastGptChatMsgAi.setUserType(1);
+                    fastGptChatMsgAi.setMsgType(1);
+                    fastGptChatMsgAi.setStatus(0);
+                    fastGptChatMsgAi.setAvatar(fastGptChatSession.getAvatar());
+                    fastGptChatMsgAi.setNickName(fastGptChatSession.getNickName());
+                    fastGptChatMsgAi.setCreateTime(new Date());
+                    fastGptChatMsgAi.setPromptTokens(result.getUsage().prompt_tokens);
+                    fastGptChatMsgAi.setCompletionTokens(result.getUsage().completion_tokens);
+                    List<ChatDetailTStreamFResult.ResponseNode> responseData = result.getResponseData();
+                    int token=0;
+                    for (ChatDetailTStreamFResult.ResponseNode responseDatum : responseData) {
+                        int tokens = responseDatum.getTokens();
+                        token+=tokens;
+                    }
+                    fastGptChatMsgAi.setTotalTokens(token);
+                    fastGptChatMsgService.insertFastGptChatMsg(fastGptChatMsgAi);
+                    addUserInfo(result.getChoices().get(0).getMessage().getContent(), fastGptChatSession.getQwExtId(),fastGptChatSession);
+                    addUserInfo(content, fastGptChatSession.getQwExtId(),fastGptChatSession);
+                    sendTaskUrlLink(content,fastGptChatSession.getUserId(),qwUser,fastGptChatSession);
+                }
+            }
+            FastGptChatSession ss = new FastGptChatSession();
+            ss.setSessionId(fastGptChatSession.getSessionId());
+            ss.setRemindStatus(0);
+            ss.setRemindCount(0);
+            fastGptChatSessionMapper.updateFastGptChatSession(ss);
+        }
+
+    }
+    /** Ai回复 **/
+    @Async
+    @Override
+    public R qwHookNotifyAiReply(QwMessageGather qwMessageGather,String corpId) {
+        //是否是群聊
+        String qwContent = qwMessageGather.getText().getContent();
+        if (qwContent.contains("我已经添加了你")){
+                return R.ok();
+        }
+        List<String> tolist = qwMessageGather.getTolist();
+        if (qwMessageGather.getTolist().size() == 0){
+            return R.ok();
+        }
+        String userId = tolist.get(0);
+        System.out.println(userId);
+        System.out.println(corpId);
+        QwUser user = qwUserMapper.selectQwUserByQwUseridAndCorpId(userId,corpId);
+        //查询接收人
+        if(user==null||user.getFastGptRoleId()==null){
+            System.out.println("查询接收人为空");
+            return R.ok();
+        }
+        FastGptRole role=roleService.selectFastGptRoleTypeByRoleId(user.getFastGptRoleId());
+        //没用ai角色跳过
+        if(role==null){
+            System.out.println("没用ai角色跳过");
+            return R.ok();
+        }
+        String modeConfig=role.getModeConfigJson();
+        //key不为空
+        if(StringUtils.isEmpty(modeConfig)){
+            System.out.println("没有aiKey");
+            return R.ok();
+        }
+        ModeConfig config=JSONUtil.toBean(modeConfig,ModeConfig.class);
+        if(StringUtils.isEmpty(config.getAPPKey())){
+            System.out.println("没有aiKey");
+            return R.ok();
+        }
+
+        QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(qwMessageGather.getFrom(), user.getCorpId(),user.getQwUserId());
+        if (qwExternalContacts==null){
+            System.out.println("没有外部联系人");
+            return R.ok();
+        }
+        if(qwExternalContacts.getType()!=null&&qwExternalContacts.getType()==1){
+            FastGptChatSession fastGptChatSession= getFastGptSession(qwExternalContacts,user);
+            String contentEmj = replaceWxEmo(qwContent);
+            if(!contentEmj.isEmpty()){
+                addSaveAiMsg(1,1,contentEmj,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),null,null,null);
+            }
+            //判断是否转人工
+            if (fastGptChatSession.getIsArtificial()==1){
+                System.out.println("转人工了");
+                return R.ok();
+            }
+            if (contentEmj.isEmpty()){
+                contentEmj ="用户发送表情:"+qwContent;
+            }
+            //获取是用户是否发送消息
+            Integer reply = redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
+            Integer replyI=1;
+            //用户正在发送消息 不发
+            if (reply!=null&&reply!=0){
+                //更新用户发送消息次数
+                redisCache.setCacheObject("reply:" + fastGptChatSession.getSessionId(),reply+1,5, TimeUnit.MINUTES);
+                //获取用户之前发送的消息
+                String msg = redisCache.getCacheObject("msg:" + fastGptChatSession.getSessionId());
+                if (!msg.isEmpty()){
+                    //更新用户发送消息内容
+                    redisCache.setCacheObject("msg:" + fastGptChatSession.getSessionId(),msg+","+contentEmj,5,TimeUnit.MINUTES);
+                }
+                //本次跳过
+                System.out.println("正在对话");
+                return R.ok();
+            }
+            //用户首次发送消息
+            redisCache.setCacheObject("reply:" + fastGptChatSession.getSessionId(),1,5,TimeUnit.MINUTES);
+            redisCache.setCacheObject("msg:" + fastGptChatSession.getSessionId(),contentEmj,5,TimeUnit.MINUTES);
+            System.out.println("等待");
+            R r= sendAiMsg(replyI,fastGptChatSession,role,user,qwExternalContacts.getId(),config.getAPPKey(),qwExternalContacts);
+            System.out.println(r);
+            //完成对话 删除消息记录
+            redisCache.deleteObject("reply:" + fastGptChatSession.getSessionId());
+            redisCache.deleteObject("msg:" + fastGptChatSession.getSessionId());
+            if(!r.get("code").equals(200)){
+                log.info("ai报错转人工:"+role.getRoleId()+":"+qwExternalContacts.getName());
+                notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," ai报错转人工");
+                return R.ok();
+            }
+            ChatDetailTStreamFResult result=(ChatDetailTStreamFResult)r.get("data");
+            //是否转人工
+            if(result.getChoices().isEmpty()){
+                return R.ok();
+            }
+            String contentKh = result.getChoices().get(0).getMessage().getContent();
+            String content = replace(result.getChoices().get(0).getMessage().getContent()).trim();
+            //计算token
+            List<ChatDetailTStreamFResult.ResponseNode> responseData = result.getResponseData();
+            int token=0;
+            for (ChatDetailTStreamFResult.ResponseNode responseDatum : responseData) {
+                int tokens = responseDatum.getTokens();
+                token+=tokens;
+            }
+            //存聊天记录
+            addSaveAiMsg(2,2,contentKh,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),result.getUsage().prompt_tokens,result.getUsage().completion_tokens,token);
+            if (!content.isEmpty()){
+                //addSaveAiMsg(1,2,content,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),null,null,null);
+                if (contentKh.contains("FunctionCallBegin")||contentKh.contains("FunctionCallEnd")||contentKh.contains("不清楚")||contentKh.contains("对不起")||contentKh.contains("客服")||contentKh.contains("不太清楚")||contentKh.contains("不明白")||contentKh.contains("不太明白")||contentKh.contains("http")){
+                    notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," 触发关键词");
+                    return R.ok();
+                }
+                if (result.isLongText()){
+                    sendAiMsgSop(content,qwExternalContacts,user);
+                }else {
+                    String sa = contentKh.replaceAll("】\n", "】").replaceAll("\n【", "【");
+                    String nr = replace(sa);
+                    String[] split = nr.split("\n");
+                    if (split.length>6){
+                        notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName(),"回复长度异常");
+                        return R.ok();
+                    }
+                    List<String> countList = countString(content);
+                    for (String msg : countList) {
+                        sendAiMsgSop(msg,qwExternalContacts,user);
+
+                    }
+                }
+
+            }
+            //新增用户信息
+            addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
+            //发送短链
+
+            if (result.isArtificial()){
+                notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," ai请求人工协助");
+            }
+        }
+
+        return R.ok();
+    }
+    @Autowired
+    QwSopLogsMapper qwSopLogsMapper;
+    private void sendAiMsgSop(String content, QwExternalContact qwExternalContacts, QwUser user) {
+        QwSopLogs qwSopLogs = new QwSopLogs();
+        qwSopLogs.setQwUserid(user.getQwUserId());
+        qwSopLogs.setExternalUserId(qwExternalContacts.getExternalUserId());
+        qwSopLogs.setLogType(2);
+        qwSopLogs.setSendType(8);
+        qwSopLogs.setExternalUserName(qwExternalContacts.getName());
+        qwSopLogs.setSendStatus(3L);
+        qwSopLogs.setExternalId(qwExternalContacts.getId());
+        qwSopLogs.setCompanyId(user.getCompanyId());
+        qwSopLogs.setReceivingStatus(0L);
+        qwSopLogs.setSort(88888888);
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        qwSopLogs.setSendTime(sdf.format(new Date()));
+        qwSopLogs.setCorpId(user.getCorpId());
+        qwSopLogs.setContentJson("{\n" +
+                "\"contentType\": \"1\",\n" +
+                "\"setting\": [\n" +
+                "{\n" +
+                "\"contentType\": \"1\",\n" +
+                "\"isBindUrl\": \"2\",\n" +
+                "\"value\": \""+content+"\"\n" +
+                "}\n" +
+                "],\n" +
+                "\"type\": 1\n" +
+                "}");
+        qwSopLogsMapper.insertQwSopLogs(qwSopLogs);
+        System.out.println(content);
+    }
+
+    /** 回调转人工  **/
+    private void notifyArtificial(Long sessionId, QwUser user, String name,String reason) {
+        FastGptChatSession s = new FastGptChatSession();
+        s.setIsArtificial(1);
+        s.setRemindStatus(0);
+        s.setIsReply(0);
+        s.setSessionId(sessionId);
+        s.setLastTime(new Date());
+        fastGptChatSessionMapper.updateFastGptChatSession(s);
+        log.info("转人工:"+name);
+        QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(user.getCorpId());
+        sendQwAppMsg(user.getCorpId(),Integer.parseInt(qwCompany.getServerAgentId().trim()),user.getQwUserId(),"您的客户:"+name+"因"+reason+"  需要转人工,请及时回复");
+        sendQwAppMsg(user.getCorpId(),Integer.parseInt(qwCompany.getServerAgentId().trim()),user.getQwUserId(),name);
+    }
+
+    void sendQwAppMsg(String corpId, Integer agentId,String qwUserId,String content){
+        QwSendMsgParam qwSendMsgParam = new QwSendMsgParam();
+        qwSendMsgParam.setAgentid(agentId);
+        qwSendMsgParam.setTouser(qwUserId);
+        QwSendMsgParam.Text text = new QwSendMsgParam.Text();
+        text.setContent(content);
+        qwSendMsgParam.setText(text);
+        qwSendMsgParam.setMsgtype("text");
+        qwApiService.sendMsg(qwSendMsgParam, corpId);
+    }
+
+
+    private void saveQwUserMsg(FastGptChatSession fastGptChatSession,Integer sendType,String content) {
+        content = replaceWxEmo(content);
+        if(content.isEmpty()){
+            return;
+        }
+        FastGptChatMsg fastGptChatMsgAi = new FastGptChatMsg();
+        fastGptChatMsgAi.setContent(content);
+        fastGptChatMsgAi.setSessionId(fastGptChatSession.getSessionId());
+        fastGptChatMsgAi.setRoleId(Long.parseLong(fastGptChatSession.getKfId()));
+        fastGptChatMsgAi.setSendType(sendType);
+        fastGptChatMsgAi.setCompanyId(fastGptChatSession.getCompanyId());
+        fastGptChatMsgAi.setUserId(fastGptChatSession.getUserId());
+        fastGptChatMsgAi.setUserType(1);
+        fastGptChatMsgAi.setMsgType(1);
+        fastGptChatMsgAi.setStatus(0);
+        fastGptChatMsgAi.setAvatar(fastGptChatSession.getAvatar());
+        fastGptChatMsgAi.setNickName(fastGptChatSession.getNickName());
+        fastGptChatMsgAi.setCreateTime(new Date());
+        fastGptChatMsgAi.setExtId(fastGptChatSession.getQwExtId()+"");
+        fastGptChatMsgService.insertFastGptChatMsg(fastGptChatMsgAi);
+        log.info("新增消息:"+fastGptChatMsgAi);
+    }
+    /** 存聊天记录  **/
+    private void addSaveAiMsg(Integer msgType,Integer sendType,String content, QwUser user, Long sessionId, Long roleId, QwExternalContact qwExternalContacts, String userId, Integer promptTokens, Integer completionTokens, Integer token) {
+
+        FastGptChatMsg fastGptChatMsgAi = new FastGptChatMsg();
+        fastGptChatMsgAi.setContent(content);
+        fastGptChatMsgAi.setSessionId(sessionId);
+        fastGptChatMsgAi.setRoleId(roleId);
+        fastGptChatMsgAi.setSendType(sendType);
+        fastGptChatMsgAi.setCompanyId(user.getCompanyId());
+        fastGptChatMsgAi.setUserId(userId);
+        fastGptChatMsgAi.setCompanyUserId(user.getCompanyUserId());
+        fastGptChatMsgAi.setUserType(1);
+        fastGptChatMsgAi.setMsgType(msgType);
+        fastGptChatMsgAi.setStatus(0);
+        fastGptChatMsgAi.setAvatar(qwExternalContacts.getAvatar());
+        fastGptChatMsgAi.setNickName(qwExternalContacts.getName());
+        fastGptChatMsgAi.setCreateTime(new Date());
+        fastGptChatMsgAi.setExtId(qwExternalContacts.getId()+"");
+        fastGptChatMsgAi.setPromptTokens(promptTokens);
+        fastGptChatMsgAi.setCompletionTokens(completionTokens);
+        fastGptChatMsgAi.setTotalTokens(token);
+        fastGptChatMsgService.insertFastGptChatMsg(fastGptChatMsgAi);
+    }
+    /** 获取会话  **/
+    private FastGptChatSession getFastGptSession(QwExternalContact qwExternalContacts, QwUser user) {
+        FastGptChatSession fastGptChatSession = fastGptChatSessionMapper.selectFastGptChatSessionByQwExternalContactsAndUserId(qwExternalContacts.getId(), user.getId());
+        if (fastGptChatSession==null){
+            fastGptChatSession = new FastGptChatSession();
+            String chatId = UUID.randomUUID().toString();
+            fastGptChatSession.setChatId(chatId);
+            fastGptChatSession.setKfId(user.getFastGptRoleId().toString());
+            fastGptChatSession.setStatus(1);
+            fastGptChatSession.setRemindCount(0);
+            fastGptChatSession.setRemindStatus(0);
+            fastGptChatSession.setCreateTime(new Date());
+            fastGptChatSession.setQwExtId(qwExternalContacts.getId());
+            fastGptChatSession.setQwUserId(user.getId());
+            fastGptChatSession.setIsArtificial(0);
+            fastGptChatSession.setAvatar(qwExternalContacts.getAvatar());
+            fastGptChatSession.setNickName(qwExternalContacts.getName());
+            fastGptChatSession.setCompanyId(user.getCompanyId());
+            fastGptChatSession.setLastTime(new Date());
+            fastGptChatSession.setIsReply(0);
+            fastGptChatSessionMapper.insertFastGptChatSession(fastGptChatSession);
+            addUserSex(qwExternalContacts);
+        }else {
+            if (fastGptChatSession.getRemindStatus()==1){
+                FastGptChatSession ss = new FastGptChatSession();
+                ss.setSessionId(fastGptChatSession.getSessionId());
+                ss.setRemindStatus(0);
+                ss.setIsReply(0);
+                fastGptChatSessionMapper.updateFastGptChatSession(ss);
+            }
+        }
+        return fastGptChatSession;
+    }
+    /** 特殊转人工 **/
+    @Override
+    public void artificial(QwHookVO vo) {
+        QwHookMsgVO msgVo= JSONUtil.toBean(vo.getData(),QwHookMsgVO.class);
+        if (msgVo.getIs_room()!=null&&msgVo.getIs_room()==0){
+            QwUser user=qwUserService.selectQwUserByAppKey(msgVo.getKey());
+            if(user!=null&&user.getFastGptRoleId()!=null) {
+                FastGptChatSession fastGptChatSession = fastGptChatSessionMapper.selectFastGptChatSessionByUserIdAndKfId(msgVo.getSender(), user.getId());
+                if (fastGptChatSession!=null&&fastGptChatSession.getIsArtificial()!=1){
+                    QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(msgVo.getSender_openid(), user.getCorpId(),user.getQwUserId());
+                    if (qwExternalContacts!=null){
+                        FastGptChatSession s = new FastGptChatSession();
+                        s.setIsArtificial(1);
+                        s.setSessionId(fastGptChatSession.getSessionId());
+                        fastGptChatSessionMapper.updateFastGptChatSession(s);
+                        QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(user.getCorpId());
+                        qwSendAppMsg(qwCompany.getCorpId(),Integer.parseInt(qwCompany.getServerAgentId().trim()),user.getQwUserId(),"您的客户:"+qwExternalContacts.getName()+" 因发送图片或视频,需要转人工,请及时回复");
+                        qwSendAppMsg(qwCompany.getCorpId(),Integer.parseInt(qwCompany.getServerAgentId().trim()),user.getQwUserId(),qwExternalContacts.getName());
+                        if (fastGptChatSession.getRemindStatus()==1){
+                            FastGptChatSession ss = new FastGptChatSession();
+                            ss.setSessionId(fastGptChatSession.getSessionId());
+                            ss.setRemindStatus(0);
+                            ss.setIsReply(0);
+                            fastGptChatSessionMapper.updateFastGptChatSession(ss);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    void qwSendAppMsg(String corpId,Integer agentId, String qwUserId,String content){
+        QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(corpId);
+        QwSendMsgParam qwSendMsgParam2 = new QwSendMsgParam();
+        qwSendMsgParam2.setAgentid(agentId);
+        qwSendMsgParam2.setTouser(qwUserId);
+        QwSendMsgParam.Text text2 = new QwSendMsgParam.Text();
+        text2.setContent(content);
+        qwSendMsgParam2.setText(text2);
+        qwSendMsgParam2.setMsgtype("text");
+        qwApiService.sendMsg(qwSendMsgParam2, qwCompany.getCorpId());
+    }
+    /** 添加企微性别 **/
+    private void addUserSex(QwExternalContact qwExternalContacts) {
+
+        QwExternalContactInfo info = qwExternalContactInfoMapper.selectQwExternalContactInfoByExternalContactId(qwExternalContacts.getId());
+        if (qwExternalContacts.getGender()!=0){
+            if (info!=null){
+                info.setSex(qwExternalContacts.getGender()==1?"男":"女");
+                qwExternalContactInfoMapper.updateQwExternalContactInfo(info);
+
+            }else {
+                QwExternalContactInfo qwExternalContactInfo = new QwExternalContactInfo();
+                qwExternalContactInfo.setExternalContactId(qwExternalContacts.getId());
+                qwExternalContactInfo.setSex(qwExternalContacts.getGender()==1?"男":"女");
+                qwExternalContactInfo.setCreateTime(new Date());
+                qwExternalContactInfoMapper.insertQwExternalContactInfo(qwExternalContactInfo);
+
+            }
+        }
+
+
+
+    }
+    /** Ai发送课程链接 **/
+    private void sendUrlLink(String content, QwHookMsgVO msgVo, QwUser user, QwSession session) {
+        if (content.contains("【发送课程:当天课程】")){
+            FsCourseWatchLogVO fsCourseWatchLogVO = watchLogMapper.selectFsCourseWatchLogByExtIdAndQwUserId(session.getQwExtId(), user.getId());
+            if (fsCourseWatchLogVO!=null){
+                FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
+                param.setVideoId(fsCourseWatchLogVO.getVideoId());
+                param.setQwUserId(String.valueOf(user.getId()));
+                param.setDays(1);
+                param.setCorpId(user.getCorpId());
+                param.setCourseId(fsCourseWatchLogVO.getCourseId());
+                param.setCompanyUserId(user.getCompanyUserId());
+                param.setCompanyId(user.getCompanyId());
+                param.setQwExternalId(Long.parseLong(session.getQwExtId()));
+                param.setSendTime(new Date());
+                R linkUrl = iFsCourseLinkService.createLinkUrlWc(param);
+                qwContactWayService.addWatchLogIfNeeded(Integer.valueOf(fsCourseWatchLogVO.getVideoId()+""),
+                        Integer.valueOf(fsCourseWatchLogVO.getCourseId()+""),
+                        String.valueOf(user.getId()),
+                        String.valueOf(user.getCompanyUserId()),
+                        String.valueOf(user.getCompanyId()),
+                        String.valueOf(session.getQwExtId()));
+                if (linkUrl != null && linkUrl.get("url") != null) {
+                    String s = (String)linkUrl.get("url");
+                    sendWebSocketMsg(s,msgVo,user,session);
+                }
+            }
+        }
+        else if (content.contains("【发送课程")){
+            Pattern c = Pattern.compile("【发送课程:(.*?)】", Pattern.DOTALL);
+            Matcher cMatcher = c.matcher(content);
+            while  (cMatcher.find()) {
+                String trim = cMatcher.group(1).trim();
+                if(trim!=null&&!trim.equals("")){
+                    FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoStringId(trim);
+                    if (fsUserCourseVideo==null){
+                        FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
+                        param.setVideoId(fsUserCourseVideo.getVideoId());
+                        param.setQwUserId(String.valueOf(user.getId()));
+                        param.setDays(1);
+                        param.setCorpId(user.getCorpId());
+                        param.setCourseId(fsUserCourseVideo.getCourseId());
+                        param.setCompanyUserId(user.getCompanyUserId());
+                        param.setCompanyId(user.getCompanyId());
+                        param.setQwExternalId(Long.parseLong(session.getQwExtId()));
+                        param.setSendTime(new Date());
+                        R linkUrl = iFsCourseLinkService.createLinkUrlWc(param);
+                        qwContactWayService.addWatchLogIfNeeded(Integer.valueOf(fsUserCourseVideo.getVideoId()+""),
+                                Integer.valueOf(fsUserCourseVideo.getCourseId()+""),
+                                String.valueOf(user.getId()),
+                                String.valueOf(user.getCompanyUserId()),
+                                String.valueOf(user.getCompanyId()),
+                                String.valueOf(session.getQwExtId()));
+                        if (linkUrl != null && linkUrl.get("url") != null) {
+                            String s = (String)linkUrl.get("url");
+                            sendWebSocketMsg(s,msgVo,user,session);
+                        }
+                    }
+
+                }else {
+                    log.error("发送课程不存在:"+trim);
+                }
+
+            }
+        }
+    }
+    /** 发送Ai消息 **/
+    private R  sendAiMsg(Integer i,FastGptChatSession fastGptChatSession, FastGptRole role,QwUser user,Long qwExternalContactsId,String appKey,QwExternalContact qwExternalContacts){
+        //等待5秒
+        try {
+            Thread.sleep(15000); // 5000 毫秒 = 5 秒
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        //获取现在的次数
+        Integer reply = (Integer)redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
+        if (reply!=i){
+            //次数变动 重新等待5秒
+            return   sendAiMsg(reply,fastGptChatSession,role,user,qwExternalContactsId,appKey,qwExternalContacts);
+        }else {
+            System.out.println("开始ai回答");
+            ChatParam param=new ChatParam();
+            param.setChatId(fastGptChatSession.getChatId());
+            param.setStream(false);
+            param.setDetail(true);
+            ChatParam.Variables variables=new ChatParam.Variables();
+            variables.setUid(user.getFastGptRoleId().toString());
+            variables.setName("test");
+            param.setVariables(variables);
+            List<ChatParam.Message> messageList=new ArrayList<ChatParam.Message>();
+            param.setMessages(messageList);
+            //添加看客记录
+            addCourseWatchLog(qwExternalContactsId);
+            String msgC = (String)redisCache.getCacheObject("msg:" + fastGptChatSession.getSessionId());
+            //添加关键词
+            addPromptWord(messageList,msgC,qwExternalContactsId,role.getReminderWords(), role.getContactInfo(),fastGptChatSession.getSessionId());
+            R r = chatService.initiatingTakeChat(param,"http://154.8.194.176:3000/api",appKey);
+            Integer reply2 = (Integer)redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
+            //次数变动 重新等待5秒
+            if (reply2!=i){
+                System.out.println("等待");
+                return   sendAiMsg(reply,fastGptChatSession,role,user,qwExternalContactsId,appKey,qwExternalContacts);
+            }
+            addSaveAiMsg(2,1,messageList.get(0).getContent(),user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),null,null,null);
+            return r;
+        }
+
+    }
+    /** 增加课程信息 **/
+    private void addCourseWatchLog(Long id) {
+        FsCourseWatchLogVO log = fsCourseWatchLogMapper.selectFsCourseWatchLogByExtId(id);
+        if (log!=null){
+            QwExternalContactInfo qwExternalContactInfo = qwExternalContactInfoMapper.selectQwExternalContactInfoByExternalContactId(id);
+            QwExternalContactInfo info = new QwExternalContactInfo();
+//            Date dateToCheck = log.getCreateTime(); // 假设这是你要检查的日期
+//            Date today = new Date(); // 获取当前日期
+           // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 格式化日期
+            String name = log.getCourseName() + log.getTitle();
+            name = name.replaceAll("[【】]", "");
+//            if (sdf.format(dateToCheck).equals(sdf.format(today))) {
+                info.setStudy(name);
+                info.setCourseStatus(log.getLogType()==3?"待看课":log.getLogType()==1?"已完课":log.getLogType()==2?"已完课":"看课中断");
+
+//            }else {
+//                info.setStudy(name);
+//                info.setCourseStatus("待看课");
+//
+//
+//            }
+            if(qwExternalContactInfo!=null){
+                info.setId(qwExternalContactInfo.getId());
+                qwExternalContactInfoMapper.updateQwExternalContactInfo(info);
+            }else {
+                info.setExternalContactId(id);
+                info.setCreateTime(new Date());
+                qwExternalContactInfoMapper.insertQwExternalContactInfo(info);
+
+            }
+        }
+    }
+    /** 组装发送AI内容 **/
+    private void addPromptWord(List<ChatParam.Message> messageList,String count,Long extId,String words,String countInfo,Long sessionId){
+
+        String  str="";
+        List<FastGptChatMsg> msgs=fastGptChatMsgService.selectFastGptChatMsgByMsgSessionId(sessionId);
+        if (!msgs.isEmpty()){
+            Collections.reverse(msgs);
+            str="【历史聊天内容:\n";
+            for (FastGptChatMsg msg : msgs) {
+                Integer sendType = msg.getSendType();
+                String content = msg.getContent();
+                str +=(sendType==1?"用户:":"AI:")+content+"\n";
+            }
+            str+="】\n";
+        }
+
+        // 这里获取后台的提示词进行匹配
+        QwExternalContactInfo info = qwExternalContactInfoMapper.selectQwExternalContactInfoByExternalContactId(extId);
+        if(info==null){
+            info=new QwExternalContactInfo();
+        }
+        if (info!=null){
+            str+="【用户状态信息\n";
+            Field[] fields = info.getClass().getDeclaredFields();
+            for (Field field : fields) {
+                field.setAccessible(true);
+                Excel annotation = field.getAnnotation(Excel.class);
+                if (annotation != null) {
+                    String name = field.getName();
+                    String fieldName = annotation.name();
+                    String[] split = countInfo.split(",");
+                    for (String zName : split) {
+                        if (zName.equals(name)) {
+                            Object value = null;
+                            try {
+                                value = field.get(info);
+                            } catch (IllegalAccessException e) {
+                            }
+                            if (value != null) {
+                                str += fieldName + ": " + value.toString() + "\n";
+                            }else {
+                                str += fieldName + ":  \n";
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        str+="】\n";
+        if (words!=null&&!"".equals(words)){
+            str+="【你的角色信息:以下内容为你的信息状态的补充而非用户信息,相当于放在角色任务里面,问到了需要知晓,但是如果无关的时候请无视此段内容 "+"\""+words+"\""+"】\n";
+        }
+        if (count!=null&&!"".equals(count)){
+            str+="【用户说的话内容(之前的内容仅仅为背景,你知道即可,以下才是用户真实说的话的内容)\n" +
+                    "\""+count+"\""+"\n" +
+                    "】";
+        }
+
+
+        ChatParam.Message message1=new ChatParam.Message();
+        message1.setRole("user");
+        message1.setContent(str);
+        messageList.add(message1);
+    }
+    /** 组装表情 **/
+    public static List<String> countString(String input) {
+
+        List<String> stringList = new ArrayList<>();
+        // 所有的回车都分段发送
+        String[] split = input.split("\n");
+        for (String s : split) {
+            List<String> sList = subCount(s);
+            stringList.addAll(sList);
+        }
+//        if (isEmoji){
+//            Random random = new Random();
+//            String[] emojiMorning = new String[] {
+//                    "😊",  // 微笑
+//                    "☀️",  // 太阳
+//                    "🌹",  // 玫瑰
+//                    "☕️",  // 茶杯
+//                    "💪",  // 强壮
+//                    "❤️"   // 爱心
+//            };
+//            String[] emojiEvening = new String[] {
+//                    "😊",  // 微笑
+//                    "🌟",  // 星星
+//                    "🌹",  // 玫瑰
+//                    "☕️",  // 茶杯
+//                    "💪",  // 强壮
+//                    "❤️"   // 爱心
+//            };
+//            List<String> sebdList = new ArrayList<>();
+//            for (String segment : stringList) {
+//                if(!segment.trim().equals("")){
+//                    LocalTime currentTime = LocalTime.now();
+//                    LocalTime startTime = LocalTime.of(5, 0);
+//                    LocalTime endTime   = LocalTime.of(18, 0);
+//                    int sj = random.nextInt(2);
+//                    // 判断当前时间是否在5点到18点之间
+//                    String emj="";
+//                    if (sj==0){
+//                        if (currentTime.isAfter(startTime) && currentTime.isBefore(endTime)) {
+//                            int i = random.nextInt(1);
+//                            emj=emojiMorning[random.nextInt(emojiMorning.length-1)];
+//                        } else {
+//                            emj=emojiEvening[random.nextInt(emojiEvening.length-1)];
+//                        }
+//                    }
+//                    sebdList.add(segment+emj) ;
+//                }
+//
+//            }
+//            return sebdList;
+//        }
+
+
+        return stringList;
+    }
+    /** 内容分段 **/
+    static List<String> subCount(String s){
+        ArrayList<String> a = new ArrayList<>();
+        if (s.length()>30){
+            String substring = s.substring(30);
+            Pattern pattern = Pattern.compile("([~。!?]+)");
+            String[] segments = pattern.split(substring);
+            Matcher matcher = pattern.matcher(substring);
+            if (matcher.find()&&segments.length>1) {
+                int dh=0;
+                String group = matcher.group();
+                if (group.equals("。")){
+                    group="";
+                    dh=1;
+                }
+                String s1 = s.substring(0, 30) + segments[0] + group;
+                a.addAll(Arrays.asList(s1));
+                if (s.substring(s1.length())!=null&&!s.substring(s1.length()+dh).equals("")){
+                    List<String> add = subCount(s.substring(s1.length()+dh));
+                    a.addAll(add);
+                }
+            }else {
+                a.add(s);
+                return  a;
+            }
+        }else {
+            a.add(s);
+        }
+        return  a;
+    }
+    /** 增加用户信息以及打标签 **/
+    private void addUserInfo(String word,Long extId,FastGptChatSession fastGptChatSession)  {
+        Pattern pattern = Pattern.compile("【用户状态信息(.*?)】", Pattern.DOTALL);
+        Matcher matcher = pattern.matcher(word);
+        while  (matcher.find()) {
+            String trim = matcher.group(1).trim();
+            String[] zd = trim.split("\n");
+            boolean b=false;
+            QwExternalContactInfo info = qwExternalContactInfoMapper.selectQwExternalContactInfoByExternalContactId(extId);
+            if (info==null){
+                info=new QwExternalContactInfo();
+                b=true;
+            }
+            Field[] fields = info.getClass().getDeclaredFields();
+            for (Field field : fields) {
+                field.setAccessible(true);
+                Excel annotation = field.getAnnotation(Excel.class);
+                if (annotation != null) {
+                    //中文名称
+                    String fieldName = annotation.name();
+                    String valueName ="";
+                    Object value = null;
+                    try {
+                        value = field.get(info);
+                    } catch (IllegalAccessException e) {
+                    }
+                    if (value != null) {
+                        valueName= value.toString();
+                    }
+                    for (String s : zd) {
+                        String[] zdName=null;
+                        if (s.contains(":")){
+                            zdName = s.split(":");
+                        }
+                        if (s.contains(":")){
+                            zdName = s.split(":");
+                        }
+                        if (zdName!=null&&zdName.length==2){
+                            String name1 = zdName[0];
+                            String name2 = zdName[1];
+                            if (name1.trim().equals(fieldName)){
+                                String name2Trim = name2.trim();
+                                if (!name2Trim.isEmpty()){
+                                    if(name1.equals("交流状态")){
+                                        if (name2Trim.equals("非首次交流")){
+                                            if (fastGptChatSession.getRemindStatus()!=null&&fastGptChatSession.getRemindStatus()==1){
+                                                FastGptChatSession s1 = new FastGptChatSession();
+                                                s1.setSessionId(fastGptChatSession.getSessionId());
+                                                s1.setRemindStatus(0);
+                                                s1.setRemindCount(0);
+                                                fastGptChatSessionMapper.updateFastGptChatSession(s1);
+                                            }
+                                        }
+                                    }
+                                    if (!valueName.trim().equals(name2Trim)){
+                                        if(name1.equals("学习到的章节")||name1.equals("今日课程完成情况")){
+                                            continue;
+                                        }
+
+                                        if (name2Trim.contains("delete")&&name2Trim.contains(";")){
+                                            name2Trim = name2Trim.replaceAll("delete.*?;", "");
+                                        }else if (name2Trim.contains("delete")){
+                                            name2Trim=" ";
+                                        }
+                                        try {
+                                            // 允许修改私有属性
+                                            field.setAccessible(true);
+                                            // 修改 name 属性的值
+                                            field.set(info, name2Trim);
+                                            b=true;
+                                        } catch (Exception e) {
+                                            System.out.println("修改错误");
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if (b){
+                if (info.getId()!=null){
+                    qwExternalContactInfoMapper.updateQwExternalContactInfo(info);
+                }else {
+                    info.setExternalContactId(extId);
+                    info.setCreateTime(new Date());
+                    qwExternalContactInfoMapper.insertQwExternalContactInfo(info);
+                }
+            }
+        }
+        Pattern tag = Pattern.compile("【标签:(.*?)】", Pattern.DOTALL);
+        Matcher TagMatcher = tag.matcher(word);
+        while  (TagMatcher.find()) {
+            String trimTag = TagMatcher.group(1).trim();
+            if(trimTag!=null&&!trimTag.equals("")){
+                qwTagGroupService.addQwTagByAi(trimTag,extId);
+            }
+
+        }
+
+        Pattern delTag = Pattern.compile("【移除标签:(.*?)】", Pattern.DOTALL);
+        Matcher delTagMatcher = delTag.matcher(word);
+        while  (delTagMatcher.find()) {
+            String deTag = delTagMatcher.group(1).trim();
+            if(deTag!=null&&!deTag.equals("")){
+                qwTagGroupService.delQwTagByAi(deTag,extId);
+            }
+
+        }
+
+        Pattern addTime = Pattern.compile("【开始计时:(.*?)】", Pattern.DOTALL);
+        Matcher addTimeMather = addTime.matcher(word);
+        while  (addTimeMather.find()) {
+            try {
+                String time = addTimeMather.group(1).trim();
+                int t = Integer.parseInt(time);
+                    FastGptChatSession s1 = new FastGptChatSession();
+                    s1.setSessionId(fastGptChatSession.getSessionId());
+                    Calendar calendar = Calendar.getInstance(); // 获取当前时间
+                    calendar.add(Calendar.MINUTE, t); // 增加t分钟
+                    s1.setRemindTime(calendar.getTime());
+                    s1.setRemindStatus(1);
+                    s1.setRemindCount(0);
+                    fastGptChatSessionMapper.updateFastGptChatSession(s1);
+
+            }catch (Exception e){
+                log.info("ai计时错误"+word);
+            }
+
+        }
+    }
+    /** 过滤[] **/
+    private static String replace(String s){
+
+        if(org.springframework.util.StringUtils.isEmpty(s)) return "";
+
+        String regex1 = "<llnnerThoughtBeginl>[\\s\\S]*?<lnnerThoughtEnd|>";
+        // 创建 Pattern 对象
+        Pattern pattern1 = Pattern.compile(regex1);
+        // 创建 Matcher 对象
+        Matcher matcher1 = pattern1.matcher(s);
+        s = matcher1.replaceAll("");
+
+        String regex = "【[\\s\\S]*?】";
+        // 创建 Pattern 对象
+        Pattern pattern = Pattern.compile(regex);
+        // 创建 Matcher 对象
+        Matcher matcher = pattern.matcher(s);
+        // 替换匹配到的内容
+        return matcher.replaceAll("");
+    }
+    /** 过滤[] **/
+    private static String replaceWxEmo(String s){
+
+        if(org.springframework.util.StringUtils.isEmpty(s)) return "";
+        // 替换匹配到的内容
+        return s.replaceAll("\\[.*?]", "").trim();
+    }
+    /** 发送语音过滤 */
+    private  String voiceHomo(String content){
+        List<FastgptChatVoiceHomo> homos = fastgptChatVoiceHomoMapper.selectFastgptChatVoiceHomoList(new FastgptChatVoiceHomo());
+        for (FastgptChatVoiceHomo homo : homos) {
+            if (content.contains(homo.getContent())) {
+                // 如果包含目标字段,则替换
+                content= content.replace(homo.getContent(), homo.getChangeCount());
+            } else {
+            }
+        }
+        return content;
+    }
+    /** 替换违禁词 **/
+    private  String replaceWords(String content){
+        List<FastGptChatReplaceWords> words = fastGptChatReplaceWordsMapper.selectAllFastGptChatReplaceWords();
+        for (FastGptChatReplaceWords word : words) {
+            if (content.contains(word.getContent())) {
+                // 如果包含目标字段,则替换
+                content= content.replace(word.getContent(), word.getChangeCount());
+            } else {
+            }
+        }
+        return content;
+    }
+    /** 发送语音回复 */
+    private static AudioVO getSilk(String count,Long userId) {
+        AudioServiceImpl audioService = new AudioServiceImpl();
+        AudioVO Silk = audioService.TextToVoice(count,userId);
+        return Silk;
+    }
+    /** 发送普通消息 */
+    private void sendWebSocketMsg(String count, QwHookMsgVO msgVo, QwUser user,QwSession session) {
+        //发送socket
+        if (count!=null&& !count.trim().isEmpty()){
+            qwMsgService.addAiMsg(session,count,1,user);
+            QwHookSendMsgParam sendMsgParam=new QwHookSendMsgParam();
+            QwHookSendMsgParam.QwHookSendMsgData sendMsgData=new QwHookSendMsgParam.QwHookSendMsgData();
+            sendMsgParam.setType(101003);
+            sendMsgData.setMsg(replaceWords(count));
+            sendMsgData.setSendId(msgVo.getSender());
+            sendMsgData.setSyncKey("1");
+            sendMsgParam.setData(sendMsgData);
+            SendAIParam sendAIParam = new SendAIParam();
+            sendAIParam.setCmd("aiReplyMsg");
+            sendAIParam.setData(JSONUtil.toJsonStr(sendMsgParam));
+            sendAIParam.setKey(user.getAppKey());
+            redisTemplate.opsForList().leftPush("AiMsg:"+user.getAppKey(), JSON.toJSONString(sendAIParam));
+        }
+
+
+    }
+    /** 发送语音回复 */
+    private void sendWebSocketVoiceMsg(String count, QwHookMsgVO msgVo, QwUser user,QwSession session,Long companyUserId) {
+        //发送socket
+        qwMsgService.addAiMsg(session,count,2,user);
+        AudioVO skl = getSilk(voiceHomo(count),companyUserId);
+        QwHookSendMsgParam sendMsgParam=new QwHookSendMsgParam();
+        QwHookSendMsgParam.QwHookSendMsgData sendMsgData=new QwHookSendMsgParam.QwHookSendMsgData();
+        sendMsgParam.setType(101019);
+        sendMsgData.setVoiceDuration(skl.getDuration());
+        sendMsgData.setVoiceUrl(skl.getUrl());
+        sendMsgData.setSendId(msgVo.getSender());
+        sendMsgData.setSyncKey("1");
+        sendMsgParam.setData(sendMsgData);
+        SendAIParam sendAIParam = new SendAIParam();
+        sendAIParam.setCmd("aiReplyMsg");
+        sendAIParam.setData(JSONUtil.toJsonStr(sendMsgParam));
+        sendAIParam.setKey(user.getAppKey());
+        redisTemplate.opsForList().leftPush("AiMsg:"+user.getAppKey(), JSON.toJSONString(sendAIParam));
+
+    }
+    /** 发送定时任务课程链接 */
+    private void sendTaskUrlLink(String content, String sendId, QwUser user, FastGptChatSession session) {
+        if (content.contains("【发送课程:当天课程】")){
+            FsCourseWatchLogVO fsCourseWatchLogVO = watchLogMapper.selectFsCourseWatchLogByExtIdAndQwUserId(session.getQwExtId().toString(), user.getId());
+            if (fsCourseWatchLogVO!=null){
+                FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
+                param.setVideoId(fsCourseWatchLogVO.getVideoId());
+                param.setQwUserId(String.valueOf(user.getId()));
+                param.setDays(1);
+                param.setCorpId(user.getCorpId());
+                param.setCourseId(fsCourseWatchLogVO.getCourseId());
+                param.setCompanyUserId(user.getCompanyUserId());
+                param.setCompanyId(user.getCompanyId());
+                param.setQwExternalId(session.getQwExtId());
+                param.setSendTime(new Date());
+                R linkUrl = iFsCourseLinkService.createLinkUrlWc(param);
+                qwContactWayService.addWatchLogIfNeeded(Integer.valueOf(fsCourseWatchLogVO.getVideoId()+""),
+                        Integer.valueOf(fsCourseWatchLogVO.getCourseId()+""),
+                        String.valueOf(user.getId()),
+                        String.valueOf(user.getCompanyUserId()),
+                        String.valueOf(user.getCompanyId()),
+                        String.valueOf(session.getQwExtId()));
+                if (linkUrl != null && linkUrl.get("url") != null) {
+                    String s = (String)linkUrl.get("url");
+                    sendWebTaskSocketMsg(s,sendId,user);
+                }
+            }
+        }
+        else if (content.contains("【发送课程")){
+            Pattern c = Pattern.compile("【发送课程:(.*?)】", Pattern.DOTALL);
+            Matcher cMatcher = c.matcher(content);
+            while  (cMatcher.find()) {
+                String trim = cMatcher.group(1).trim();
+                if(trim!=null&&!trim.equals("")){
+                    FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoStringId(trim);
+                    System.out.println("课程:"+fsUserCourseVideo);
+                    FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
+                    param.setVideoId(fsUserCourseVideo.getVideoId());
+                    param.setQwUserId(String.valueOf(user.getId()));
+                    param.setDays(1);
+                    param.setCorpId(user.getCorpId());
+                    param.setCourseId(fsUserCourseVideo.getCourseId());
+                    param.setCompanyUserId(user.getCompanyUserId());
+                    param.setCompanyId(user.getCompanyId());
+                    param.setQwExternalId(session.getQwExtId());
+                    param.setSendTime(new Date());
+                    R linkUrl = iFsCourseLinkService.createLinkUrlWc(param);
+                    qwContactWayService.addWatchLogIfNeeded(Integer.valueOf(fsUserCourseVideo.getVideoId()+""),
+                            Integer.valueOf(fsUserCourseVideo.getCourseId()+""),
+                            String.valueOf(user.getId()),
+                            String.valueOf(user.getCompanyUserId()),
+                            String.valueOf(user.getCompanyId()),
+                            String.valueOf(session.getQwExtId()));
+                    if (linkUrl != null && linkUrl.get("url") != null) {
+                        String s = (String)linkUrl.get("url");
+                        sendWebTaskSocketMsg(s,sendId,user);
+                    }
+                }
+
+            }
+        }
+
+
+
+    }
+    /** 发送定时任务消息 */
+    private void sendWebTaskSocketMsg(String count, String sendId, QwUser user) {
+        //发送socket
+        //文本消息
+        QwHookSendMsgParam sendMsgParam=new QwHookSendMsgParam();
+        QwHookSendMsgParam.QwHookSendMsgData sendMsgData=new QwHookSendMsgParam.QwHookSendMsgData();
+        sendMsgParam.setType(101003);
+        sendMsgData.setMsg(count);
+        sendMsgData.setSendId(sendId);
+        sendMsgData.setSyncKey("1");
+        sendMsgParam.setData(sendMsgData);
+        SendAIParam sendAIParam = new SendAIParam();
+        sendAIParam.setCmd("aiReplyMsg");
+        sendAIParam.setData(JSONUtil.toJsonStr(sendMsgParam));
+        sendAIParam.setKey(user.getAppKey());
+        redisTemplate.opsForList().leftPush("AiMsg:"+user.getAppKey(), JSON.toJSONString(sendAIParam));
+    }
+    @Override
+    public R qwHookNotifyAddMsg(QwMessageGather qwMessageGather,String corpId) {
+        QwUser sendUser=qwUserMapper.selectQwUserByQwUseridAndCorpId(qwMessageGather.getFrom(),corpId);
+
+
+        if (sendUser!=null){
+            List<String> tolist = qwMessageGather.getTolist();
+            if (qwMessageGather.getTolist().isEmpty()){
+                return R.ok();
+            }
+            String ExtId = tolist.get(0);
+            QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(ExtId, sendUser.getCorpId(),sendUser.getQwUserId());
+            if (qwExternalContacts==null){
+                return R.ok();
+            }
+             FastGptChatSession fastGptChatSession = fastGptChatSessionMapper.selectFastGptChatSessionByQwExternalContactsAndUserId(qwExternalContacts.getId(), sendUser.getId());
+            if (fastGptChatSession!=null){
+                saveQwUserMsg(fastGptChatSession,2,qwMessageGather.getText().getContent());
+            }else {
+
+                if(qwExternalContacts.getType()!=null&&qwExternalContacts.getType()==1){
+                    if(sendUser.getFastGptRoleId()!=null){
+                        fastGptChatSession = new FastGptChatSession();
+                        String chatId = UUID.randomUUID().toString();
+                        fastGptChatSession.setChatId(chatId);
+                        fastGptChatSession.setKfId(sendUser.getFastGptRoleId().toString());
+                        fastGptChatSession.setStatus(1);
+                        fastGptChatSession.setRemindCount(0);
+                        fastGptChatSession.setRemindStatus(0);
+                        fastGptChatSession.setCreateTime(new Date());
+                        fastGptChatSession.setQwExtId(qwExternalContacts.getId());
+                        fastGptChatSession.setQwUserId(sendUser.getId());
+                        fastGptChatSession.setIsArtificial(0);
+                        fastGptChatSession.setAvatar(qwExternalContacts.getAvatar());
+                        fastGptChatSession.setNickName(qwExternalContacts.getName());
+                        fastGptChatSession.setCompanyId(sendUser.getCompanyId());
+                        fastGptChatSession.setLastTime(new Date());
+                        fastGptChatSession.setIsReply(0);
+                        fastGptChatSessionMapper.insertFastGptChatSession(fastGptChatSession);
+                        addUserSex(qwExternalContacts);
+                        saveQwUserMsg(fastGptChatSession,2,qwMessageGather.getText().getContent());
+                    }
+                }
+            }
+        }
+        return R.ok();
+    }
+
+    @Override
+    public void expireAiMsg() {
+
+    }
+
+//    @Override
+//    public void expireAiMsg() {
+//        List<QwSopLogs> qwSopLogs = qwSopLogsMapper.selectExpireAiMsg();
+//        if (qwSopLogs==null|| qwSopLogs.isEmpty()){
+//            System.out.println("无");
+//            return;
+//        }
+//        qwSopLogsMapper.batchUpdateQwSopLogsById(qwSopLogs);
+//        List<QwSopLogs> distinctList = new ArrayList<>(qwSopLogs.stream()
+//                .collect(Collectors.toMap(
+//                        QwSopLogs::getExternalId,  // 以 extId 作为 Key
+//                        log -> log,            // Value 是对象本身
+//                        (existing, replacement) -> existing  // 遇到重复时保留已有值(第一个)
+//                ))
+//                .values());
+//        for (QwSopLogs logs : distinctList) {
+//            log.info("转人工:"+logs.getCorpId());
+//            QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(logs.getCorpId());
+//            sendQwAppMsg(logs.getCorpId(),Integer.parseInt(qwCompany.getServerAgentId().trim()),logs.getQwUserid(),"您的客户:"+logs.getExternalUserName()+"因  AI回复内容过期转人工,请及时登录插件确定定位配置准确");
+//        }
+//
+//    }
+}

+ 48 - 0
fs-service/src/main/java/com/fs/fastgptApi/result/AiImgResult.java

@@ -0,0 +1,48 @@
+package com.fs.fastgptApi.result;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class AiImgResult {
+    private List<Choice> choices;
+    private long created;
+    private String id;
+    private String model;
+    private String service_tier;
+    private String object;
+    private Usage usage;
+
+
+    @Data
+    public static class Choice {
+        private String finish_reason;
+        private int index;
+        private Object logprobs; // 或定义一个具体类型
+        private Message message;
+
+    }
+    @Data
+    public static class Message {
+        private String content;
+        private String role;
+
+    }
+
+    @Data
+    public static class Usage {
+        private int completion_tokens;
+        private int prompt_tokens;
+        private int total_tokens;
+        private TokenDetails prompt_tokens_details;
+        private TokenDetails completion_tokens_details;
+
+    }
+    @Data
+    public static class TokenDetails {
+        private int cached_tokens;
+        private int reasoning_tokens; // optional depending on context
+
+    }
+}

+ 102 - 0
fs-service/src/main/java/com/fs/fastgptApi/util/AiImgUtil.java

@@ -0,0 +1,102 @@
+package com.fs.fastgptApi.util;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.fastgptApi.result.AiImgResult;
+import org.springframework.stereotype.Service;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+
+@Service
+public class AiImgUtil {
+
+
+    public String getImageParse(String imageUrl) {
+        try {
+            String requestBody = String.format(
+                    "{" +
+                            "\"model\": \"doubao-vision-lite-32k-241015\"," +
+                            "\"messages\": [{" +
+                            "\"role\": \"user\"," +
+                            "\"content\": [" +
+                            "{" +
+                            "\"type\": \"image_url\"," +
+                            "\"image_url\": {\"url\": \"" + imageUrl + "\"}" +
+                            "}," +
+                            "{" +
+                            "\"type\": \"text\"," +
+                            "\"text\": \"解析一下这张图片\"" +
+                            "}" +
+                            "]" +
+                            "}]" +
+                            "}"
+            );
+
+            // 发送请求
+            String response = sendAiImgHttpRequest(requestBody);
+            System.out.println("API响应: " + response);
+            AiImgResult aiImgResult = JSON.parseObject(response, AiImgResult.class);
+            String content = aiImgResult.getChoices().get(0).getMessage().getContent();
+            System.out.println(content);
+            return content;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+
+
+    private  String sendAiImgHttpRequest(String requestBody) throws IOException {
+        HttpURLConnection connection = null;
+        try {
+            // 配置连接
+            URL url = new URL("https://ark.cn-beijing.volces.com/api/v3/chat/completions");
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("POST");
+            connection.setRequestProperty("Authorization", "Bearer " + "208d3549-8dc9-4ef6-b3fa-5aa358f1ab20");
+            connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+            connection.setDoOutput(true);
+            connection.setConnectTimeout(10000); // 10秒连接超时
+            connection.setReadTimeout(30000);   // 30秒读取超时
+
+            // 发送请求体
+            try (OutputStream os = connection.getOutputStream()) {
+                os.write(requestBody.getBytes(StandardCharsets.UTF_8));
+            }
+
+            // 处理响应
+            int status = connection.getResponseCode();
+            if (status == HttpURLConnection.HTTP_OK) {
+                try (BufferedReader br = new BufferedReader(
+                        new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
+                    StringBuilder response = new StringBuilder();
+                    String line;
+                    while ((line = br.readLine()) != null) {
+                        response.append(line);
+                    }
+                    return response.toString();
+                }
+            } else {
+                // 读取错误流
+                try (BufferedReader br = new BufferedReader(
+                        new InputStreamReader(connection.getErrorStream(), StandardCharsets.UTF_8))) {
+                    StringBuilder errorResponse = new StringBuilder();
+                    String line;
+                    while ((line = br.readLine()) != null) {
+                        errorResponse.append(line);
+                    }
+                    throw new IOException("HTTP错误 " + status + ": " + errorResponse.toString());
+                }
+            }
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+}

+ 54 - 0
fs-service/src/main/java/com/fs/qw/domain/QwUserVoiceLog.java

@@ -0,0 +1,54 @@
+package com.fs.qw.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 企微用户通话记录对象 qw_user_voice_log
+ *
+ * @author fs
+ * @date 2025-05-08
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class QwUserVoiceLog extends BaseEntity{
+
+    /** id */
+    private Long id;
+
+    /** 外部联系人id */
+    @Excel(name = "外部联系人id")
+    private Long extId;
+
+    /** 企微用户id */
+    @Excel(name = "企微用户id")
+    private Long qwUserId;
+
+    /** 时长秒 */
+    @Excel(name = "时长秒")
+    private Long duration;
+
+    /** 标题 */
+    @Excel(name = "标题")
+    private String title;
+
+    /** 1接听 2未接 */
+    @Excel(name = "1接听 2未接")
+    private Long status;
+
+    /** 企微id */
+    @Excel(name = "企微id")
+    private String corpId;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 销售用户id */
+    @Excel(name = "销售用户id")
+    private Long companyUserId;
+
+
+}

+ 6 - 0
fs-service/src/main/java/com/fs/qw/domain/QwWorkTask.java

@@ -51,4 +51,10 @@ public class QwWorkTask extends BaseEntity{
     private Long companyUserId;
 
     private String title;
+
+    /** 通话时长 */
+    @Excel(name = "通话时长")
+    private Long duration;
+
+    private Integer trackType;
 }

+ 73 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwUserVoiceLogMapper.java

@@ -0,0 +1,73 @@
+package com.fs.qw.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.qw.domain.QwUserVoiceLog;
+import com.fs.qw.vo.QwUserVoiceLogTotalVo;
+import com.fs.qw.vo.QwUserVoiceLogVo;
+
+import java.util.List;
+
+/**
+ * 企微用户通话记录Mapper接口
+ *
+ * @author fs
+ * @date 2025-05-08
+ */
+public interface QwUserVoiceLogMapper extends BaseMapper<QwUserVoiceLog>{
+    /**
+     * 查询企微用户通话记录
+     *
+     * @param id 企微用户通话记录主键
+     * @return 企微用户通话记录
+     */
+    QwUserVoiceLogVo selectQwUserVoiceLogById(Long id);
+
+    /**
+     * 查询企微用户通话记录列表
+     *
+     * @param qwUserVoiceLog 企微用户通话记录
+     * @return 企微用户通话记录集合
+     */
+    List<QwUserVoiceLogVo> selectQwUserVoiceLogList(QwUserVoiceLogVo qwUserVoiceLog);
+
+    /**
+     * 新增企微用户通话记录
+     *
+     * @param qwUserVoiceLog 企微用户通话记录
+     * @return 结果
+     */
+    int insertQwUserVoiceLog(QwUserVoiceLog qwUserVoiceLog);
+
+    /**
+     * 修改企微用户通话记录
+     *
+     * @param qwUserVoiceLog 企微用户通话记录
+     * @return 结果
+     */
+    int updateQwUserVoiceLog(QwUserVoiceLog qwUserVoiceLog);
+
+    /**
+     * 删除企微用户通话记录
+     *
+     * @param id 企微用户通话记录主键
+     * @return 结果
+     */
+    int deleteQwUserVoiceLogById(Long id);
+
+    /**
+     * 批量删除企微用户通话记录
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteQwUserVoiceLogByIds(Long[] ids);
+
+
+    /**
+     * 查询统计企微用户通话记录
+     *
+     * @param qwUserVoiceLog 企微用户通话记录
+     * @return 企微用户通话记录集合
+     */
+    List<QwUserVoiceLogTotalVo> selectQwUserVoiceLogTotalList(QwUserVoiceLogTotalVo qwUserVoiceLog);
+}

+ 3 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwWorkTaskMapper.java

@@ -1,6 +1,7 @@
 package com.fs.qw.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.qw.domain.QwUserVoiceLog;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.vo.QwWorkTaskAllListVO;
@@ -117,4 +118,6 @@ public interface QwWorkTaskMapper extends BaseMapper<QwWorkTask>{
             " GROUP BY t.qw_user_id,DATE(t.create_time) "+
             "</script>"})
     List<QwWorkTaskAllListVO> selectQwWorkTaskAllListVO(QwWorkTaskListParam qwWorkTask);
+
+    List<QwWorkTask> selectQwWorkTaskByExtIdAndQwUserId(QwUserVoiceLog qwUserVoiceLog);
 }

+ 68 - 0
fs-service/src/main/java/com/fs/qw/service/IQwUserVoiceLogService.java

@@ -0,0 +1,68 @@
+package com.fs.qw.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.qw.domain.QwUserVoiceLog;
+import com.fs.qw.vo.QwUserVoiceLogTotalVo;
+import com.fs.qw.vo.QwUserVoiceLogVo;
+
+import java.util.List;
+
+/**
+ * 企微用户通话记录Service接口
+ *
+ * @author fs
+ * @date 2025-05-08
+ */
+public interface IQwUserVoiceLogService extends IService<QwUserVoiceLog>{
+    /**
+     * 查询企微用户通话记录
+     *
+     * @param id 企微用户通话记录主键
+     * @return 企微用户通话记录
+     */
+    QwUserVoiceLogVo selectQwUserVoiceLogById(Long id);
+
+    /**
+     * 查询企微用户通话记录列表
+     *
+     * @param qwUserVoiceLog 企微用户通话记录
+     * @return 企微用户通话记录集合
+     */
+    List<QwUserVoiceLogVo> selectQwUserVoiceLogList(QwUserVoiceLogVo qwUserVoiceLog);
+
+    /**
+     * 新增企微用户通话记录
+     *
+     * @param qwUserVoiceLog 企微用户通话记录
+     * @return 结果
+     */
+    int insertQwUserVoiceLog(QwUserVoiceLog qwUserVoiceLog);
+
+    /**
+     * 修改企微用户通话记录
+     *
+     * @param qwUserVoiceLog 企微用户通话记录
+     * @return 结果
+     */
+    int updateQwUserVoiceLog(QwUserVoiceLog qwUserVoiceLog);
+
+    /**
+     * 批量删除企微用户通话记录
+     *
+     * @param ids 需要删除的企微用户通话记录主键集合
+     * @return 结果
+     */
+    int deleteQwUserVoiceLogByIds(Long[] ids);
+
+    /**
+     * 删除企微用户通话记录信息
+     *
+     * @param id 企微用户通话记录主键
+     * @return 结果
+     */
+    int deleteQwUserVoiceLogById(Long id);
+
+    List<QwUserVoiceLogTotalVo> selectQwUserVoiceLogTotalList(QwUserVoiceLogTotalVo qwUserVoiceLog);
+
+    void addQuUserVoiceByIpadCallback(Long id, Long extUserId, Integer recordType, Integer totalSeconds, String uuid);
+}

+ 4 - 0
fs-service/src/main/java/com/fs/qw/service/IQwWorkTaskService.java

@@ -1,6 +1,7 @@
 package com.fs.qw.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.qw.domain.QwUser;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.vo.QwWorkTaskAllListVO;
@@ -79,4 +80,7 @@ public interface IQwWorkTaskService extends IService<QwWorkTask>{
     void addQwWorkByCourseLastTime();
 
     List<QwWorkTaskAllListVO> selectQwWorkTaskAllListVO(QwWorkTaskListParam qwWorkTask);
+
+    void addQwWorkByAiNotifyArtificial(QwUser user, Long extId, String content);
+
 }

+ 190 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwUserVoiceLogServiceImpl.java

@@ -0,0 +1,190 @@
+package com.fs.qw.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.QwUserVoiceLog;
+import com.fs.qw.domain.QwWorkTask;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.mapper.QwUserVoiceLogMapper;
+import com.fs.qw.mapper.QwWorkTaskMapper;
+import com.fs.qw.service.IQwUserVoiceLogService;
+import com.fs.qw.vo.QwUserVoiceLogTotalVo;
+import com.fs.qw.vo.QwUserVoiceLogVo;
+import com.fs.wxwork.dto.WxWorkResponseDTO;
+import com.fs.wxwork.dto.WxWorkVid2UserIdDTO;
+import com.fs.wxwork.dto.WxWorkVid2UserIdRespDTO;
+import com.fs.wxwork.service.WxWorkService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 企微用户通话记录Service业务层处理
+ *
+ * @author fs
+ * @date 2025-05-08
+ */
+@Service
+public class QwUserVoiceLogServiceImpl extends ServiceImpl<QwUserVoiceLogMapper, QwUserVoiceLog> implements IQwUserVoiceLogService {
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    private QwWorkTaskMapper qwWorkTaskMapper;
+
+    @Autowired
+    WxWorkService wxWorkService;
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+    /**
+     * 查询企微用户通话记录
+     *
+     * @param id 企微用户通话记录主键
+     * @return 企微用户通话记录
+     */
+    @Override
+    public QwUserVoiceLogVo selectQwUserVoiceLogById(Long id)
+    {
+        return baseMapper.selectQwUserVoiceLogById(id);
+    }
+
+    /**
+     * 查询企微用户通话记录列表
+     *
+     * @param qwUserVoiceLog 企微用户通话记录
+     * @return 企微用户通话记录
+     */
+    @Override
+    public List<QwUserVoiceLogVo> selectQwUserVoiceLogList(QwUserVoiceLogVo qwUserVoiceLog)
+    {
+        return baseMapper.selectQwUserVoiceLogList(qwUserVoiceLog);
+    }
+
+    /**
+     * 新增企微用户通话记录
+     *
+     * @param qwUserVoiceLog 企微用户通话记录
+     * @return 结果
+     */
+    @Override
+    public int insertQwUserVoiceLog(QwUserVoiceLog qwUserVoiceLog)
+    {
+        qwUserVoiceLog.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertQwUserVoiceLog(qwUserVoiceLog);
+    }
+
+    /**
+     * 修改企微用户通话记录
+     *
+     * @param qwUserVoiceLog 企微用户通话记录
+     * @return 结果
+     */
+    @Override
+    public int updateQwUserVoiceLog(QwUserVoiceLog qwUserVoiceLog)
+    {
+        return baseMapper.updateQwUserVoiceLog(qwUserVoiceLog);
+    }
+
+    /**
+     * 批量删除企微用户通话记录
+     *
+     * @param ids 需要删除的企微用户通话记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteQwUserVoiceLogByIds(Long[] ids)
+    {
+        return baseMapper.deleteQwUserVoiceLogByIds(ids);
+    }
+
+    /**
+     * 删除企微用户通话记录信息
+     *
+     * @param id 企微用户通话记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteQwUserVoiceLogById(Long id)
+    {
+        return baseMapper.deleteQwUserVoiceLogById(id);
+    }
+
+    @Override
+    public List<QwUserVoiceLogTotalVo> selectQwUserVoiceLogTotalList(QwUserVoiceLogTotalVo qwUserVoiceLog) {
+        return baseMapper.selectQwUserVoiceLogTotalList(qwUserVoiceLog);
+    }
+
+    @Override
+    public void addQuUserVoiceByIpadCallback(Long id, Long extUserId, Integer recordType, Integer totalSeconds, String uuid) {
+        QwUser sendUser = qwUserMapper.selectQwUserById(id);
+        if (sendUser!=null){
+            String extId = getExtId(extUserId, uuid, sendUser.getServerId());
+            QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(extId, sendUser.getCorpId(),sendUser.getQwUserId());
+            if (qwExternalContacts==null){
+                return ;
+            }
+
+            QwUserVoiceLog qwUserVoiceLog = new QwUserVoiceLog();
+            qwUserVoiceLog.setCorpId(sendUser.getCorpId());
+            qwUserVoiceLog.setExtId(qwExternalContacts.getId());
+            qwUserVoiceLog.setQwUserId(sendUser.getId());
+            qwUserVoiceLog.setCompanyId(qwExternalContacts.getCompanyId());
+            if(qwExternalContacts.getCompanyUserId() != null){
+                qwUserVoiceLog.setCompanyUserId(qwExternalContacts.getCompanyUserId());
+            }
+
+            if(recordType == 2){//2是未接通的处理
+                qwUserVoiceLog.setStatus(Long.valueOf(recordType));
+                qwUserVoiceLog.setTitle("通话拒接");
+            }else{
+                qwUserVoiceLog.setStatus(1L);
+                if(totalSeconds != null){
+                    qwUserVoiceLog.setDuration(Long.valueOf(totalSeconds));
+                }else{
+                    qwUserVoiceLog.setDuration(0L);
+                }
+                qwUserVoiceLog.setTitle("通话已接听");
+            }
+            qwUserVoiceLog.setCreateTime(new Date());
+
+            int insert = baseMapper.insertQwUserVoiceLog(qwUserVoiceLog);
+            //插入成功的情况下就处理定时任务
+            if(insert == 1){
+                List<QwWorkTask> qwWorkTaskList = qwWorkTaskMapper.selectQwWorkTaskByExtIdAndQwUserId(qwUserVoiceLog);
+                if(qwWorkTaskList != null && !qwWorkTaskList.isEmpty()){
+                    for (QwWorkTask qwWorkTask : qwWorkTaskList) {
+                        if(qwUserVoiceLog.getStatus() == 1L){//接通电话
+                            qwWorkTask.setTrackType(3);//1正常处理 2 未接听 3 已接听
+                            qwWorkTask.setDuration(qwUserVoiceLog.getDuration());
+                        }else{
+                            qwWorkTask.setTrackType(2);//1正常处理 2 未接听 3 已接听
+                        }
+                        qwWorkTask.setStatus(1);//0 待处理 1 已处理 3 过期
+                        qwWorkTask.setUpdateTime(new Date());
+                        qwWorkTaskMapper.updateQwWorkTask(qwWorkTask);
+                    }
+                }
+            }
+        }
+    }
+
+    String getExtId(Long id,String uid,Long serverId){
+        WxWorkVid2UserIdDTO wxWorkVid2UserIdDTO = new WxWorkVid2UserIdDTO();
+        wxWorkVid2UserIdDTO.setUser_id(Arrays.asList(id));
+        wxWorkVid2UserIdDTO.setUuid(uid);
+        WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> WxWorkVid2UserIdRespDTO = wxWorkService.Vid2UserId(wxWorkVid2UserIdDTO,serverId);
+        List<WxWorkVid2UserIdRespDTO> data = WxWorkVid2UserIdRespDTO.getData();
+        if (data==null&& data.isEmpty()){
+            System.out.println("未获取到extId");
+            return "";
+        }
+        com.fs.wxwork.dto.WxWorkVid2UserIdRespDTO dto = data.get(0);
+        return dto.getOpenid();
+    }
+}

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

@@ -7,6 +7,7 @@ import com.fs.course.mapper.FsUserCourseVideoMapper;
 import com.fs.course.vo.FsCourseWatchLogTaskVO;
 import com.fs.course.vo.FsQwCourseWatchLogVO;
 import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwUser;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwWorkTaskMapper;
@@ -388,7 +389,20 @@ public class QwWorkTaskServiceImpl extends ServiceImpl<QwWorkTaskMapper, QwWorkT
 
         }
     }
-
+    @Override
+    public void addQwWorkByAiNotifyArtificial(QwUser user, Long extId, String content) {
+        QwWorkTask qwWorkTask = new QwWorkTask();
+        qwWorkTask.setStatus(0);
+        qwWorkTask.setExtId(extId);
+        qwWorkTask.setTitle(content);
+        qwWorkTask.setType(4);
+        qwWorkTask.setScore(50);
+        qwWorkTask.setCompanyUserId(user.getCompanyUserId());
+        qwWorkTask.setCompanyId(user.getCompanyId());
+        qwWorkTask.setQwUserId(user.getId());
+        qwWorkTask.setCreateTime(DateUtils.getNowDate());
+        qwWorkTaskMapper.insertQwWorkTask(qwWorkTask);
+    }
     @Override
     public List<QwWorkTaskAllListVO> selectQwWorkTaskAllListVO(QwWorkTaskListParam qwWorkTask) {
         return qwWorkTaskMapper.selectQwWorkTaskAllListVO(qwWorkTask);

+ 228 - 0
fs-service/src/main/java/com/fs/qw/vo/QwUserVoiceLogTotalVo.java

@@ -0,0 +1,228 @@
+package com.fs.qw.vo;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwUser;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+public class QwUserVoiceLogTotalVo extends BaseEntity {
+
+    /** id */
+    private Long id;
+
+    /** 外部联系人id */
+    //@Excel(name = "外部联系人id")
+    private Long extId;
+
+    //@Excel(name = "外部联系人名称")
+    private String extName;
+
+    /** 企微用户id */
+    //@Excel(name = "企微用户id")
+    private Long qwUserId;
+
+    @Excel(name = "企微用户名称")
+    private String qwUserName;
+
+    /** 时长秒 */
+    @Excel(name = "时长秒")
+    private Long duration;
+
+    /** 标题 */
+    //@Excel(name = "标题")
+    private String title;
+
+    /** 1接听 2未接 */
+    //@Excel(name = "接听状态,1接听 2未接")
+    private Long status;
+
+    /** 企微id */
+    //@Excel(name = "企微id")
+    private String corpId;
+
+    /** 公司id */
+    //@Excel(name = "公司id")
+    private Long companyId;
+
+    //@Excel(name = "公司名称")
+    private String companyName;
+
+    /** 销售用户id */
+    //@Excel(name = "销售用户id")
+    private Long companyUserId;
+
+    //@Excel(name = "销售用户名称")
+    private String companyUserName;
+
+    //接通数量
+    @Excel(name = "接通数量")
+    private Long connectCount;
+    //未接通数量
+    @Excel(name = "未接通数量")
+    private Long noConnectCount;
+
+    private QwExternalContact qwExternalContact;
+
+    private Company company;
+
+    private CompanyUser companyUser;
+
+    private QwUser qwUser;
+
+
+    public Long getConnectCount() {
+        return connectCount;
+    }
+
+    public void setConnectCount(Long connectCount) {
+        this.connectCount = connectCount;
+    }
+
+    public Long getNoConnectCount() {
+        return noConnectCount;
+    }
+
+    public void setNoConnectCount(Long noConnectCount) {
+        this.noConnectCount = noConnectCount;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getExtId() {
+        return extId;
+    }
+
+    public void setExtId(Long extId) {
+        this.extId = extId;
+    }
+
+    public String getExtName() {
+        return extName;
+    }
+
+    public void setExtName(String extName) {
+        this.extName = extName;
+    }
+
+    public Long getQwUserId() {
+        return qwUserId;
+    }
+
+    public void setQwUserId(Long qwUserId) {
+        this.qwUserId = qwUserId;
+    }
+
+    public String getQwUserName() {
+        return qwUserName;
+    }
+
+    public void setQwUserName(String qwUserName) {
+        this.qwUserName = qwUserName;
+    }
+
+    public Long getDuration() {
+        return duration;
+    }
+
+    public void setDuration(Long duration) {
+        this.duration = duration;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public Long getStatus() {
+        return status;
+    }
+
+    public void setStatus(Long status) {
+        this.status = status;
+    }
+
+    public String getCorpId() {
+        return corpId;
+    }
+
+    public void setCorpId(String corpId) {
+        this.corpId = corpId;
+    }
+
+    public Long getCompanyId() {
+        return companyId;
+    }
+
+    public void setCompanyId(Long companyId) {
+        this.companyId = companyId;
+    }
+
+    public String getCompanyName() {
+        return companyName;
+    }
+
+    public void setCompanyName(String companyName) {
+        this.companyName = companyName;
+    }
+
+    public Long getCompanyUserId() {
+        return companyUserId;
+    }
+
+    public void setCompanyUserId(Long companyUserId) {
+        this.companyUserId = companyUserId;
+    }
+
+    public String getCompanyUserName() {
+        return companyUserName;
+    }
+
+    public void setCompanyUserName(String companyUserName) {
+        this.companyUserName = companyUserName;
+    }
+
+    public QwExternalContact getQwExternalContact() {
+        return qwExternalContact;
+    }
+
+    public void setQwExternalContact(QwExternalContact qwExternalContact) {
+        this.qwExternalContact = qwExternalContact;
+    }
+
+    public Company getCompany() {
+        return company;
+    }
+
+    public void setCompany(Company company) {
+        this.company = company;
+    }
+
+    public CompanyUser getCompanyUser() {
+        return companyUser;
+    }
+
+    public void setCompanyUser(CompanyUser companyUser) {
+        this.companyUser = companyUser;
+    }
+
+    public QwUser getQwUser() {
+        return qwUser;
+    }
+
+    public void setQwUser(QwUser qwUser) {
+        this.qwUser = qwUser;
+    }
+}

+ 71 - 0
fs-service/src/main/java/com/fs/qw/vo/QwUserVoiceLogVo.java

@@ -0,0 +1,71 @@
+package com.fs.qw.vo;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwUser;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class QwUserVoiceLogVo extends BaseEntity {
+
+    /** id */
+    private Long id;
+
+    /** 外部联系人id */
+    //@Excel(name = "外部联系人id")
+    private Long extId;
+
+    @Excel(name = "外部联系人名称")
+    private String extName;
+
+    /** 企微用户id */
+    //@Excel(name = "企微用户id")
+    private Long qwUserId;
+
+    @Excel(name = "企微用户名称")
+    private String qwUserName;
+
+    /** 时长秒 */
+    @Excel(name = "时长秒")
+    private Long duration;
+
+    /** 标题 */
+    @Excel(name = "标题")
+    private String title;
+
+    /** 1接听 2未接 */
+    @Excel(name = "接听状态,1接听 2未接")
+    private Long status;
+
+    /** 企微id */
+    @Excel(name = "企微id")
+    private String corpId;
+
+    /** 公司id */
+    //@Excel(name = "公司id")
+    private Long companyId;
+
+    @Excel(name = "公司名称")
+    private String companyName;
+
+    /** 销售用户id */
+    //@Excel(name = "销售用户id")
+    private Long companyUserId;
+
+    @Excel(name = "销售用户名称")
+    private String companyUserName;
+
+    private QwExternalContact qwExternalContact;
+
+    private Company company;
+
+    private CompanyUser companyUser;
+
+    private QwUser qwUser;
+
+}

+ 158 - 0
fs-service/src/main/resources/mapper/qw/QwUserVoiceLogMapper.xml

@@ -0,0 +1,158 @@
+<?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.qw.mapper.QwUserVoiceLogMapper">
+
+    <resultMap type="QwUserVoiceLog" id="QwUserVoiceLogResult">
+        <result property="id"    column="id"    />
+        <result property="extId"    column="ext_id"    />
+        <result property="qwUserId"    column="qw_user_id"    />
+        <result property="duration"    column="duration"    />
+        <result property="title"    column="title"    />
+        <result property="status"    column="status"    />
+        <result property="corpId"    column="corp_id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="createTime"    column="create_time"    />
+    </resultMap>
+
+    <resultMap type="com.fs.qw.vo.QwUserVoiceLogTotalVo" id="QwUserVoiceLogVoTotalResult">
+        <result property="id"    column="id"    />
+        <result property="qwUserId"    column="qw_user_id"    />
+        <result property="duration"    column="duration"    />
+        <result property="connectCount"    column="connectCount"    />
+        <result property="noConnectCount"    column="noConnectCount"    />
+        <result property="createTime"     column="create_time"     />
+        <association property="qwUser" javaType="com.fs.qw.domain.QwUser" autoMapping="true">
+            <id column="qw_uer_id" property="qwUserId"></id>
+            <result column="qw_user_name" property="qwUserName"></result>
+        </association>
+    </resultMap>
+
+    <resultMap type="com.fs.qw.vo.QwUserVoiceLogVo" id="QwUserVoiceLogVoResult" extends="QwUserVoiceLogResult">
+        <association property="qwExternalContact" javaType="com.fs.qw.domain.QwExternalContact" autoMapping="true">
+            <id column="id" property="id"></id>
+            <result column="name" property="name"></result>
+        </association>
+        <association property="company" javaType="com.fs.company.domain.Company" autoMapping="true">
+            <id column="company_id" property="companyId"></id>
+            <result column="company_name" property="companyName"></result>
+        </association>
+        <association property="companyUser" javaType="com.fs.company.domain.CompanyUser" autoMapping="true">
+            <id column="user_id" property="userId"></id>
+            <result column="user_name" property="userName"></result>
+        </association>
+
+        <association property="qwUser" javaType="com.fs.qw.domain.QwUser" autoMapping="true">
+            <id column="qw_uer_id" property="qwUserId"></id>
+            <result column="qw_user_name" property="qwUserName"></result>
+        </association>
+
+    </resultMap>
+
+    <sql id="selectQwUserVoiceLogVo">
+        select id, ext_id, qw_user_id, duration, title, status, corp_id, company_id, company_user_id, create_time from qw_user_voice_log
+    </sql>
+
+    <select id="selectQwUserVoiceLogList" resultMap="QwUserVoiceLogVoResult">
+        select uvl.id, ext_id, uvl.qw_user_id, duration, title, uvl.status, uvl.corp_id, uvl.company_id, uvl.company_user_id, uvl.create_time,qec.`name`,c.company_name,cu.user_name,qu.qw_user_name
+        from qw_user_voice_log uvl
+        left join qw_external_contact qec on uvl.ext_id = qec.id
+        left join company c on uvl.company_id = c.company_id
+        left join company_user cu on uvl.company_user_id = cu.user_id
+        left join qw_user qu on uvl.qw_user_id = qu.id
+        <where>
+            <if test="extName != null "> and qec.name like concat(#{extName}, '%')</if>
+            <if test="qwUserName != null "> and qu.qw_user_name like concat(#{qwUserName}, '%')</if>
+            <if test="title != null  and title != ''"> and uvl.title = #{title}</if>
+            <if test="status != null "> and uvl.status = #{status}</if>
+            <if test="corpId != null  and corpId != ''"> and uvl.corp_id = #{corpId}</if>
+            <if test="companyId != null "> and c.company_id = #{companyId}</if>
+            <if test="companyName != null "> and c.company_name like concat(#{companyName}, '%')</if>
+            <if test="companyUserId != null "> and cu.user_id = #{companyUserId}</if>
+            <if test="companyUserName != null  and companyUserName != ''"> and cu.user_name like concat(#{companyUserName}, '%')</if>
+            <if test="beginTime != null and endTime != null">
+                AND date_format(uvl.create_time,'%Y-%m-%d') &gt;= #{beginTime}
+                AND date_format(uvl.create_time,'%Y-%m-%d') &lt;= #{endTime}
+            </if>
+        </where>
+    </select>
+
+    <select id="selectQwUserVoiceLogById" parameterType="Long" resultMap="QwUserVoiceLogResult">
+        <include refid="selectQwUserVoiceLogVo"/>
+        where id = #{id}
+    </select>
+    <select id="selectQwUserVoiceLogTotalList" resultMap="QwUserVoiceLogVoTotalResult">
+
+        SELECT
+        SUM(duration) duration,qu.qw_user_name,
+        COUNT(CASE WHEN uvl.status=1 THEN 1 END) AS connectCount,
+        COUNT(CASE WHEN uvl.status=2 THEN 1 END) AS noConnectCount
+        FROM qw_user_voice_log uvl
+        LEFT JOIN qw_user qu ON uvl.qw_user_id = qu.id
+        where  uvl.company_id = #{companyId}
+        <if test="qwUserName != null ">and qu.qw_user_name like concat(#{qwUserName}, '%')</if>
+        <if test="beginTime != null and endTime != null">
+            AND date_format(uvl.create_time,'%Y-%m-%d') &gt;= #{beginTime}
+            AND date_format(uvl.create_time,'%Y-%m-%d') &lt;= #{endTime}
+        </if>
+
+        group by qu.qw_user_name
+
+    </select>
+
+
+    <insert id="insertQwUserVoiceLog" parameterType="QwUserVoiceLog" useGeneratedKeys="true" keyProperty="id">
+        insert into qw_user_voice_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="extId != null">ext_id,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="duration != null">duration,</if>
+            <if test="title != null">title,</if>
+            <if test="status != null">status,</if>
+            <if test="corpId != null">corp_id,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="createTime != null">create_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="extId != null">#{extId},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="duration != null">#{duration},</if>
+            <if test="title != null">#{title},</if>
+            <if test="status != null">#{status},</if>
+            <if test="corpId != null">#{corpId},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="createTime != null">#{createTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateQwUserVoiceLog" parameterType="QwUserVoiceLog">
+        update qw_user_voice_log
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="extId != null">ext_id = #{extId},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
+            <if test="duration != null">duration = #{duration},</if>
+            <if test="title != null">title = #{title},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="corpId != null">corp_id = #{corpId},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteQwUserVoiceLogById" parameterType="Long">
+        delete from qw_user_voice_log where id = #{id}
+    </delete>
+
+    <delete id="deleteQwUserVoiceLogByIds" parameterType="String">
+        delete from qw_user_voice_log where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 8 - 0
fs-service/src/main/resources/mapper/qw/QwWorkTaskMapper.xml

@@ -136,4 +136,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{id}
         </foreach>
     </delete>
+
+    <select id="selectQwWorkTaskByExtIdAndQwUserId" resultMap="QwWorkTaskResult">
+        <include refid="selectQwWorkTaskVo"/>
+        where status = 0 and `type` in(1,2,3)
+        <if test="extId != null">and ext_id = #{extId}</if>
+        <if test="qwUserId != null">and qw_user_id = #{qwUserId}</if>
+        <if test="createTime != null">and date_format(create_time,'%Y-%m-%d') = date_format(#{createTime},'%Y-%m-%d')</if>
+    </select>
 </mapper>