瀏覽代碼

Merge remote-tracking branch 'origin/master'

ct 1 周之前
父節點
當前提交
e5aa60acb8
共有 27 個文件被更改,包括 485 次插入33 次删除
  1. 17 5
      fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  2. 1 0
      fs-company/src/main/java/com/fs/qw/QwQwWorkTaskController.java
  3. 28 0
      fs-company/src/main/java/com/fs/qw/qw/QwWorkTaskController.java
  4. 68 4
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  5. 160 0
      fs-qw-api-msg/src/main/java/com/fs/app/util/AudioUtils.java
  6. 5 2
      fs-qw-task/src/main/java/com/fs/app/task/CourseWatchLogScheduler.java
  7. 2 1
      fs-service-system/src/main/java/com/fs/course/param/newfs/FsCourseSortLinkParam.java
  8. 9 4
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  9. 14 1
      fs-service-system/src/main/java/com/fs/fastGpt/service/AiHookService.java
  10. 26 1
      fs-service-system/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  11. 19 0
      fs-service-system/src/main/java/com/fs/qw/mapper/QwWorkTaskMapper.java
  12. 20 0
      fs-service-system/src/main/java/com/fs/qw/param/QwWorkTaskListParam.java
  13. 3 0
      fs-service-system/src/main/java/com/fs/qw/service/IQwWorkTaskService.java
  14. 4 0
      fs-service-system/src/main/java/com/fs/qw/service/impl/QwMsgServiceImpl.java
  15. 6 0
      fs-service-system/src/main/java/com/fs/qw/service/impl/QwWorkTaskServiceImpl.java
  16. 22 0
      fs-service-system/src/main/java/com/fs/qw/vo/QwWorkTaskAllListVO.java
  17. 3 0
      fs-service-system/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java
  18. 2 0
      fs-service-system/src/main/java/com/fs/sop/service/ISopUserLogsInfoService.java
  19. 5 0
      fs-service-system/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  20. 31 0
      fs-service-system/src/main/java/com/fs/wxwork/dto/WxDownloadFileDTO.java
  21. 4 0
      fs-service-system/src/main/java/com/fs/wxwork/dto/WxWorkMessageDTO.java
  22. 8 0
      fs-service-system/src/main/java/com/fs/wxwork/service/WxWorkService.java
  23. 12 0
      fs-service-system/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java
  24. 11 11
      fs-service-system/src/main/resources/application-config-dev.yml
  25. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/WxUserController.java
  26. 2 1
      fs-user-app/src/main/java/com/fs/core/config/DataSourceConfig.java
  27. 2 2
      fs-user-app/src/main/resources/application.yml

+ 17 - 5
fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -5,11 +5,13 @@ import com.fs.app.config.ImageStorageConfig;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.common.utils.StringUtils;
+import com.fs.course.domain.FsUserCoursePeriod;
 import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.param.newfs.FsCourseSortLinkParam;
 import com.fs.course.param.newfs.FsUserCourseListParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
 import com.fs.course.service.IFsCourseLinkService;
+import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.FsUserCourseParticipationRecordVO;
@@ -25,8 +27,6 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
-
-import java.io.File;
 import java.io.InputStream;
 import java.util.HashMap;
 import java.util.List;
@@ -51,6 +51,9 @@ public class FsUserCourseVideoController extends AppBaseController {
     @Autowired
     private ImageStorageConfig imageConfig;
 
+    @Autowired
+    private IFsUserCoursePeriodService fsUserCoursePeriodService;
+
     @Login
     @GetMapping("/pageList")
     @ApiOperation("课程分页列表")
@@ -141,10 +144,19 @@ public class FsUserCourseVideoController extends AppBaseController {
             log.info("获取的logo图片路径,fileUrl:{}", path);
             InputStream inputStream = fsUserCourseService.handleImage("", path);
 
-            if (StringUtils.isEmpty(param.getImgUrl())) {
-                return R.error(400, "课程封面不能为空!");
+            // 获取营期的课程风格url
+            String imgUrl;
+            FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(param.getPeriodId());
+            if (fsUserCoursePeriod != null) {
+                imgUrl = fsUserCoursePeriod.getCourseStyle();
+            } else {
+                imgUrl = param.getImgUrl();
             }
-            String base64Image = fsUserCourseService.createCourseImageQR(realLink, param.getImgUrl(), inputStream, "png", param.getTitle(), param.getDuration());
+            if(StringUtils.isEmpty(imgUrl)){
+                return R.error(400, "营期风格图片或课程封面不能为空!");
+            }
+
+            String base64Image = fsUserCourseService.createCourseImageQR(realLink, imgUrl, inputStream, "png", param.getTitle(), param.getDuration());
             // 返回Base64编码的图片字符串
             Map<String, Object> map = new HashMap<>();
             map.put("url", base64Image);

+ 1 - 0
fs-company/src/main/java/com/fs/qw/QwQwWorkTaskController.java

@@ -17,6 +17,7 @@ import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.service.IHyWorkTaskService;
 import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;

+ 28 - 0
fs-company/src/main/java/com/fs/qw/qw/QwWorkTaskController.java

@@ -14,6 +14,7 @@ import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -58,6 +59,33 @@ public class QwWorkTaskController extends BaseController
         return getDataTable(list);
     }
 
+    @GetMapping("/glList")
+    public TableDataInfo glList(QwWorkTaskListParam qwWorkTask)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwWorkTask.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwWorkTaskListVO> list = qwWorkTaskService.selectQwWorkTaskListVO(qwWorkTask);
+        for (QwWorkTaskListVO qwWorkTaskListVO : list) {
+            qwWorkTaskListVO.setLogs(fsCourseWatchLogMapper.selectFsCourseWatchLog7DayByExtId(qwWorkTaskListVO.getExtId()));
+        }
+        return getDataTable(list);
+    }
+
+    @GetMapping("/allList")
+    public TableDataInfo allList(QwWorkTaskListParam qwWorkTask)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwWorkTask.setCompanyId(loginUser.getCompany().getCompanyId());
+        if (qwWorkTask.getSTime()==null||qwWorkTask.getETime()==null){
+            return new TableDataInfo();
+        }
+        List<QwWorkTaskAllListVO> list = qwWorkTaskService.selectQwWorkTaskAllListVO(qwWorkTask);
+
+        return getDataTable(list);
+    }
+
     /**
      * 导出企微任务看板列表
      */

+ 68 - 4
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -2,7 +2,9 @@ package com.fs.app.controller;
 
 import com.alibaba.fastjson.JSON;
 import com.fs.app.socket.QwImSocket;
+import com.fs.app.util.AudioUtils;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.uuid.IdUtils;
 import com.fs.fastGpt.service.AiHookService;
 import com.fs.qw.domain.QwUser;
@@ -182,10 +184,14 @@ public class QwMsgController {
                         aiHookService.qwHookNotifyAddMsg(id,receiver,content,wxWorkMsgResp.getUuid());
                     }
 
-                    // 保存聊天消息
-                    QwMessageListVO message = aiHookService.saveQwMsg(id, userId, content, wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 1);
-                    QwImSocket.broadcast(message);
-
+                    // 处理文本消息
+                    if (wxWorkMessageDTO.getMsgtype() == 2 || wxWorkMessageDTO.getMsgtype() == 0) {
+                        processTextMessage(id, userId, content, wxWorkMsgResp, sendType);
+                    }
+                    // 语音消息
+                    if (wxWorkMessageDTO.getMsgtype() == 16) {
+                        processVoiceMessage(serverId, content, wxWorkMessageDTO, wxWorkMsgResp, id, userId, sendType);
+                    }
                 }
                 //语音通话
                 if (wxWorkMessageDTO.getMsgtype()==40){
@@ -242,6 +248,64 @@ public class QwMsgController {
         return map;
     }
 
+    /**
+     * 处理文本消息
+     * @param id                企微用户ID
+     * @param userId            消息发送者ID
+     * @param content           消息内容
+     * @param wxWorkMsgResp     回调信息对象
+     * @param sendType          发送者类型 1客户 2销售
+     */
+    private void processTextMessage(Long id, Long userId, String content, WxWorkMsgResp wxWorkMsgResp, Integer sendType) {
+        // 保存聊天消息
+        QwMessageListVO message = aiHookService.saveQwMsg(id, userId, content, wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 1);
+        QwImSocket.broadcast(message);
+    }
+
+    /**
+     * 处理语音消息
+     * @param serverId          服务器ID
+     * @param wxWorkMessageDTO  消息DTO
+     * @param content           翻译后的内容
+     * @param wxWorkMsgResp     回调信息对象
+     * @param id                企微用户ID
+     * @param userId            消息发送者ID
+     * @param sendType          发送者类型 1客户 2销售
+     */
+    private void processVoiceMessage(Long serverId, String content, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, Long id, Long userId, Integer sendType) {
+        String voiceFileName = IdUtils.fastSimpleUUID() + ".silk";
+        WxWorkResponseDTO<String> fileUrlResp =
+                aiHookService.getFileUrl(wxWorkMsgResp.getUuid(), wxWorkMessageDTO.getVoice_id(), wxWorkMessageDTO.getAes_key(), 5, voiceFileName, wxWorkMessageDTO.getVoice_size(), serverId);
+        if (fileUrlResp.getErrcode() != 0) {
+            log.warn("获取语音地址失败: {}", fileUrlResp.getErrmsg());
+            return;
+        }
+
+        // silk转map3
+        String url = AudioUtils.convertSilk2Mp3(fileUrlResp.getData());
+        if (StringUtils.isBlank(url)) {
+            log.warn("转换silk语音格式失败");
+            return;
+        }
+
+        // 转换内容为空时再尝试一次
+        if (StringUtils.isBlank(content)) {
+            WxwSpeechToTextEntityDTO ste = new WxwSpeechToTextEntityDTO();
+            ste.setMsgid(wxWorkMessageDTO.getMsg_id());
+            ste.setUuid(wxWorkMsgResp.getUuid());
+            WxWorkResponseDTO<WxwSpeechToTextEntityRespDTO> dto = wxWorkService.SpeechToTextEntity(ste, serverId);
+            content = dto.getData().getText();
+        }
+
+        JSONObject json = new JSONObject();
+        json.put("url", url);
+        json.put("content", content);
+
+        // 保存聊天消息
+        QwMessageListVO message = aiHookService.saveQwMsg(id, userId, json.toString(), wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 4);
+        QwImSocket.broadcast(message);
+    }
+
     /**
      * 处理图片消息
      * @param serverId          服务器ID

+ 160 - 0
fs-qw-api-msg/src/main/java/com/fs/app/util/AudioUtils.java

@@ -0,0 +1,160 @@
+package com.fs.app.util;
+
+import com.fs.common.utils.uuid.IdUtils;
+import com.fs.system.oss.CloudStorageService;
+import com.fs.system.oss.OSSFactory;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.springframework.http.HttpStatus;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class AudioUtils {
+
+    /**
+     * silk转换为mp3
+     * @param silkUrl silk语音链接地址
+     * @return  mp3链接地址
+     */
+    public static String convertSilk2Mp3(String silkUrl) {
+        String uniqueId = IdUtils.fastSimpleUUID();
+        Path uploadDirPath = Paths.get(System.getProperty("java.io.tmpdir"), "/");
+        Path downloadedSilkFilePath = uploadDirPath.resolve(uniqueId + ".silk");
+        Path pcmFilePath = uploadDirPath.resolve(uniqueId + ".pcm");
+        Path mp3FilePath = uploadDirPath.resolve(uniqueId + ".mp3");
+
+        try {
+            // 1. 从网络下载 SILK 文件
+            downloadFile(silkUrl, downloadedSilkFilePath);
+            log.info("SILK file downloaded to: {}", downloadedSilkFilePath);
+
+            // 2. 使用 silk-v3-decoder 解码 SILK 到 PCM
+            List<String> silkDecodeCommand = new ArrayList<>();
+            silkDecodeCommand.add("silk_v3_decoder");
+            silkDecodeCommand.add(downloadedSilkFilePath.toString());
+            silkDecodeCommand.add(pcmFilePath.toString());
+
+            ProcessBuilder silkDecoderPb = new ProcessBuilder(silkDecodeCommand);
+            silkDecoderPb.redirectErrorStream(true); // 将错误流合并到标准输出
+            Process silkDecoderProcess = silkDecoderPb.start();
+
+            String silkDecoderOutput = readInputStreamToString(silkDecoderProcess.getInputStream());
+
+            boolean silkDecoderExited = silkDecoderProcess.waitFor(60, TimeUnit.SECONDS);
+            if (!silkDecoderExited || silkDecoderProcess.exitValue() != 0) {
+                log.error("silk conversion failed or timed out. error: {}", silkDecoderOutput);
+                return null;
+            }
+            log.info("SILK decoder to PCM successfully.");
+
+            // 3. 使用 FFmpeg 将 PCM 转码为 MP3
+            Process ffmpegProcess = getFfmpegProcess(pcmFilePath, mp3FilePath);
+            String ffmpegOutput = readInputStreamToString(ffmpegProcess.getInputStream());
+
+            boolean ffmpegExited = ffmpegProcess.waitFor(120, TimeUnit.SECONDS);
+            if (!ffmpegExited || ffmpegProcess.exitValue() != 0) {
+                log.error("ffmpeg conversion failed or timed out. error: {}", ffmpegOutput);
+                return null;
+            }
+            log.info("ffmpeg conversion to MP3 successfully.");
+
+            // 4. 上传oss
+            String fileName = mp3FilePath.getFileName().toString();
+            String suffix = fileName.substring(fileName.lastIndexOf("."));
+            CloudStorageService storage = OSSFactory.build();
+            return storage.uploadSuffix(Files.newInputStream(mp3FilePath), suffix);
+
+        } catch (IOException | InterruptedException | NullPointerException e) {
+            log.error("Conversion error: {}", e.getMessage());
+            return null;
+        } finally {
+            // 清理临时文件 (重要!)
+            try {
+                if (Files.exists(downloadedSilkFilePath)) Files.delete(downloadedSilkFilePath);
+                if (Files.exists(pcmFilePath)) Files.delete(pcmFilePath);
+                if (Files.exists(mp3FilePath)) Files.delete(mp3FilePath);
+            } catch (IOException e) {
+                log.error("Error cleaning up temporary files:: {}", e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * 执行ffmpeg
+     * @param pcmFilePath   pcm文件
+     * @param mp3FilePath   mp3地址
+     * @return  process
+     * @throws IOException exception
+     */
+    private static Process getFfmpegProcess(Path pcmFilePath, Path mp3FilePath) throws IOException {
+        List<String> ffmpegCommand = new ArrayList<>();
+        ffmpegCommand.add("ffmpeg");
+        ffmpegCommand.add("-y");
+        ffmpegCommand.add("-f");
+        ffmpegCommand.add("s16le");
+        ffmpegCommand.add("-ar");
+        ffmpegCommand.add("24000"); // 注意:这里假设是 24kHz,如果你的 SILK 文件是其他采样率,请调整
+        ffmpegCommand.add("-ac");
+        ffmpegCommand.add("1");
+        ffmpegCommand.add("-i");
+        ffmpegCommand.add(pcmFilePath.toString());
+        ffmpegCommand.add(mp3FilePath.toString());
+
+        ProcessBuilder ffmpegPb = new ProcessBuilder(ffmpegCommand);
+        ffmpegPb.redirectErrorStream(true);
+        return ffmpegPb.start();
+    }
+
+    /**
+     * 处理文件流
+     * @param is 输入流
+     * @return  输出
+     * @throws IOException exception
+     */
+    private static String readInputStreamToString(InputStream is) throws IOException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int len;
+        while ((len = is.read(buffer)) != -1) {
+            bos.write(buffer, 0, len);
+        }
+        return bos.toString("UTF-8"); // 使用 UTF-8 编码
+    }
+
+    /**
+     * 下载网络文件
+     * @param fileUrl       网络文件
+     * @param destination   临时文件
+     * @throws IOException  exception
+     */
+    private static void downloadFile(String fileUrl, Path destination) throws IOException {
+        try (CloseableHttpClient httpClient = HttpClients.createDefault();
+             CloseableHttpResponse response = httpClient.execute(new HttpGet(fileUrl));
+             InputStream inputStream = response.getEntity().getContent();
+             FileOutputStream outputStream = new FileOutputStream(destination.toFile())) {
+
+            if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) {
+                throw new IOException("Failed to download file from " + fileUrl + ", HTTP Status: " + response.getStatusLine().getStatusCode());
+            }
+
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, bytesRead);
+            }
+        }
+    }
+}

+ 5 - 2
fs-qw-task/src/main/java/com/fs/app/task/CourseWatchLogScheduler.java

@@ -23,6 +23,9 @@ public class CourseWatchLogScheduler {
     private final AtomicBoolean isRunning2 = new AtomicBoolean(false);
 
     private final AtomicBoolean isRunning3 = new AtomicBoolean(false);
+
+    private final AtomicBoolean isRunning4 = new AtomicBoolean(false);
+
     @Autowired
     private FsCourseWatchLogMapper courseWatchLogMapper;
 
@@ -126,7 +129,7 @@ public class CourseWatchLogScheduler {
     @Scheduled(fixedRate = 60000) // 每分钟执行一次
     public void checkFsUserWatchStatus() {
         // 尝试设置标志为 true,表示任务开始执行
-        if (!isRunning1.compareAndSet(false, true)) {
+        if (!isRunning4.compareAndSet(false, true)) {
             log.warn("WXH5-检查会员看课中任务执行 - 上一个任务尚未完成,跳过此次执行");
             return;
         }
@@ -139,7 +142,7 @@ public class CourseWatchLogScheduler {
             log.error("WXH5-检查会员看课中任务执行完成 - 定时任务执行失败", e);
         } finally {
             // 重置标志为 false,表示任务已完成
-            isRunning1.set(false);
+            isRunning4.set(false);
         }
 
     }

+ 2 - 1
fs-service-system/src/main/java/com/fs/course/param/newfs/FsCourseSortLinkParam.java

@@ -3,6 +3,7 @@ package com.fs.course.param.newfs;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
+import javax.validation.constraints.NotBlank;
 
 @Data
 @ApiModel(description = "生成课程短链/海报入参")
@@ -32,7 +33,7 @@ public class FsCourseSortLinkParam {
     @ApiModelProperty(value = "视频时长,生成海报时必传")
     private String duration;
 
-    @ApiModelProperty(value = "营期id")
+    @ApiModelProperty(value = "营期id", required = true)
     private Long periodId;
 
     @ApiModelProperty(value = "营期课程id")

+ 9 - 4
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -37,6 +37,7 @@ import com.fs.qwApi.param.QwAddContactWayParam;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
+import com.fs.sop.service.ISopUserLogsInfoService;
 import com.fs.store.domain.FsUser;
 import com.fs.store.domain.FsUserCompanyUser;
 import com.fs.store.domain.FsUserIntegralLogs;
@@ -91,6 +92,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Autowired
     private FsCourseWatchLogMapper courseWatchLogMapper;
 
+
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
+
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
     @Autowired
@@ -469,6 +474,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             }
             log.setUpdateTime(new Date());
             courseWatchLogMapper.updateFsCourseWatchLog(log);
+
+            iSopUserLogsInfoService.updateSopUserInfoByExternalId(externalContact.getId(),param.getUserId());
+
             return R.ok();
 
         }else {
@@ -483,10 +491,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             user.setQwExtId(param.getQwExternalId());
             fsUserMapper.updateFsUser(user);
 
-            List<String> list= sopUserLogsInfoMapper.selectSopUserLogsInfoByExtId(contact.getId());
-            if (list!=null&& !list.isEmpty()){
-                sopUserLogsInfoMapper.updateSopUserLogsInfoFsUserIdById(list,param.getUserId());
-            }
+            iSopUserLogsInfoService.updateSopUserInfoByExternalId(externalContact.getId(),param.getUserId());
 
             //绑定上之后 更新观看记录
             //看课记录中userId为0绑定userId

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

@@ -28,7 +28,7 @@ public interface AiHookService {
      * @param uuid     UUID
      * @param sendType 发送者类型 1用户 2客服
      * @param json     消息json
-     * @param msgType  消息类型 1文本 2图片 3动态表情
+     * @param msgType  消息类型 1文本 2图片 3动态表情 4语音
      */
     QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json, int msgType);
 
@@ -44,4 +44,17 @@ public interface AiHookService {
      * @return  WxWorkResponseDTO
      */
     WxWorkResponseDTO<String> getFileUrl(String uuid, String fileId, String aesKey, String authKey, String fileName, Integer fileSize, Long serverId);
+
+    /**
+     * 获取文件地址
+     * @param uuid      uuid
+     * @param fileId    fileId
+     * @param aesKey    aesKey
+     * @param fileType  fileType
+     * @param fileName  fileName
+     * @param fileSize  fileSize
+     * @param serverId  serverId
+     * @return  WxWorkResponseDTO
+     */
+    WxWorkResponseDTO<String> getFileUrl(String uuid, String fileId, String aesKey, Integer fileType, String fileName, Integer fileSize, Long serverId);
 }

+ 26 - 1
fs-service-system/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -1269,7 +1269,7 @@ public class AiHookServiceImpl implements AiHookService {
      * @param uuid     UUID
      * @param sendType 发送者类型 1用户 2客服
      * @param json     消息json
-     * @param msgType  消息类型 1文本 2图片 3动态表情
+     * @param msgType  消息类型 1文本 2图片 3动态表情 4语音
      */
     @Transactional(rollbackFor = Exception.class)
     @Override
@@ -1349,6 +1349,8 @@ public class AiHookServiceImpl implements AiHookService {
             type = "image";
         } else if (msgType == 3) {
             type = "emotionDynamic";
+        } else if (msgType == 4) {
+            type = "voice";
         }
         listVO.setType(type);
         listVO.setStatus("succeed");
@@ -1383,6 +1385,29 @@ public class AiHookServiceImpl implements AiHookService {
         return wxWorkService.downloadWeChatFile(weChatFileDTO, serverId);
     }
 
+    /**
+     * 获取文件地址
+     * @param uuid      uuid
+     * @param fileId    fileId
+     * @param aesKey    aesKey
+     * @param fileType  fileType
+     * @param fileName  fileName
+     * @param fileSize  fileSize
+     * @param serverId  serverId
+     * @return  WxWorkResponseDTO
+     */
+    @Override
+    public WxWorkResponseDTO<String> getFileUrl(String uuid, String fileId, String aesKey, Integer fileType, String fileName, Integer fileSize, Long serverId) {
+        WxDownloadFileDTO downloadFileDTO = new WxDownloadFileDTO();
+        downloadFileDTO.setUuid(uuid);
+        downloadFileDTO.setFileid(fileId);
+        downloadFileDTO.setAes_key(aesKey);
+        downloadFileDTO.setFiletype(fileType);
+        downloadFileDTO.setFile_name(fileName);
+        downloadFileDTO.setSize(fileSize);
+        return wxWorkService.downloadFile(downloadFileDTO, serverId);
+    }
+
     /**
      * 查询外部联系人
      * @param userId    用户ID

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

@@ -4,6 +4,7 @@ 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;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import com.fs.qw.vo.UserVOs;
 import org.apache.ibatis.annotations.Param;
@@ -132,4 +133,22 @@ public interface QwWorkTaskMapper extends BaseMapper<QwWorkTask>{
     List<QwWorkTask> selectQwWorkTaskByExtIdAndQwUserId(QwUserVoiceLog qwUserVoiceLog);
 
     List<UserVOs> getQwUserList(@Param("userId") Long userId, @Param("qwUserId") String qwUserId);
+
+    @Select({"<script> " +
+            "select t.qw_user_id,qw.qw_user_name,ANY_VALUE(cu.nick_name) companyUserName,DATE(t.create_time) createTime,\n" +
+            "\t\tSUM(CASE WHEN t.status = 0 THEN 1 ELSE 0 END) AS status0,\n" +
+            "    SUM(CASE WHEN t.status = 1 THEN 1 ELSE 0 END) AS status1,\n" +
+            "    SUM(CASE WHEN t.status = 2 THEN 1 ELSE 0 END) AS status2,\n" +
+            "    SUM(CASE WHEN t.status = 3 THEN 1 ELSE 0 END) AS status3\n" +
+            "\t\tfrom qw_work_task t  LEFT JOIN qw_user qw ON qw.id = t.qw_user_id LEFT JOIN company_user cu on cu.user_id=t.company_user_id  where  t.company_id=#{companyId} " +
+            "    <if test=\"companyUserId != null \"> and t.company_user_id = #{companyUserId}</if>\n" +
+            "    <if test=\"companyUserName != null and companyUserName != ''\"> and cu.nick_name = #{companyUserName}</if>\n" +
+            "    <if test=\"qwUserName != null and qwUserName != ''\"> and qw.qw_user_name = #{qwUserName}</if>\n" +
+            " " +
+            "    <if test=\"sTime != null \">  and DATE(t.create_time) &gt;= DATE(#{sTime})</if>\n" +
+            "    <if test=\"eTime != null \">  and DATE(t.create_time) &lt;= DATE(#{eTime})</if>\n" +
+            " " +
+            " GROUP BY t.qw_user_id,DATE(t.create_time) "+
+            "</script>"})
+    List<QwWorkTaskAllListVO> selectQwWorkTaskAllListVO(QwWorkTaskListParam qwWorkTask);
 }

+ 20 - 0
fs-service-system/src/main/java/com/fs/qw/param/QwWorkTaskListParam.java

@@ -1,8 +1,12 @@
 package com.fs.qw.param;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
+import java.util.Date;
+
 @Data
 public class QwWorkTaskListParam {
     private Long id;
@@ -41,6 +45,22 @@ public class QwWorkTaskListParam {
 
     private String title;
 
+    private String companyUserName;
+
+
+
+    private Long deptId;
+
+    private String qwUserName;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date eTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date sTime;
+
     private Long pageNum;
     private Long pageSize;
+
+
 }

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

@@ -4,6 +4,7 @@ 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;
 import com.fs.qw.vo.QwWorkTaskListVO;
 
 import java.util.List;
@@ -110,4 +111,6 @@ public interface IQwWorkTaskService extends IService<QwWorkTask>{
     List<QwWorkTask> selectQwWorkTaskListByMap(Map<String, Object> params);
 
     void addQwWorkByAiNotifyArtificial(QwUser user, Long extId, String content);
+
+    List<QwWorkTaskAllListVO> selectQwWorkTaskAllListVO(QwWorkTaskListParam qwWorkTask);
 }

+ 4 - 0
fs-service-system/src/main/java/com/fs/qw/service/impl/QwMsgServiceImpl.java

@@ -448,6 +448,8 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
                 listVO.setType("image");
             } else if (qwMsg.getMsgType() == 3) {
                 listVO.setType("emotionDynamic");
+            } else if (qwMsg.getMsgType() == 4) {
+                listVO.setType("voice");
             }
             listVO.setMsgId(qwMsg.getMsgId());
             listVO.setLastContent(qwMsgs.get(0).getContent());
@@ -482,6 +484,8 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
                 type = "image";
             } else if (record.getMsgType() == 3) {
                 type = "emotionDynamic";
+            } else if (record.getMsgType() == 4) {
+                type = "voice";
             }
             listVO.setType(type);
             listVO.setStatus("succeed");

+ 6 - 0
fs-service-system/src/main/java/com/fs/qw/service/impl/QwWorkTaskServiceImpl.java

@@ -18,6 +18,7 @@ import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwWorkTaskMapper;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.SopUserLogsInfo;
@@ -471,6 +472,11 @@ public class QwWorkTaskServiceImpl extends ServiceImpl<QwWorkTaskMapper, QwWorkT
         qwWorkTaskMapper.insertQwWorkTask(qwWorkTask);
     }
 
+    @Override
+    public List<QwWorkTaskAllListVO> selectQwWorkTaskAllListVO(QwWorkTaskListParam qwWorkTask) {
+        return qwWorkTaskMapper.selectQwWorkTaskAllListVO(qwWorkTask);
+    }
+
     /**
      * 根据SOP执行日志和特定条件,为符合要求的外部联系人添加企业微信工作任务。
      * <p>

+ 22 - 0
fs-service-system/src/main/java/com/fs/qw/vo/QwWorkTaskAllListVO.java

@@ -0,0 +1,22 @@
+package com.fs.qw.vo;
+
+import lombok.Data;
+
+@Data
+public class QwWorkTaskAllListVO {
+    /** 外部联系人id */
+
+    private String qwUserName;
+
+    private String companyUserName;
+
+    private Integer status0;
+
+    private Integer status1;
+
+    private Integer status2;
+
+    private Integer status3;
+
+    private String createTime;
+}

+ 3 - 0
fs-service-system/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java

@@ -192,6 +192,7 @@ public interface SopUserLogsInfoMapper {
     @Update("update sop_user_logs_info set fs_user_id=#{fsUserId} where external_id =#{externalId}")
     int updateQwExternalContactChangeUserId(@Param("externalId") Long externalId,@Param("fsUserId") Long fsUserId);
 
+
     @DataSource(DataSourceType.SOP)
     @Select("SELECT external_id  FROM `sop_user_logs_info` where sop_id = #{sopId}  and Date(create_time) = Date(#{minDay})")
     List<SopUserLogsInfo> selectDayBySopId(@Param("sopId")String sopId, @Param("minDay")String minDay);
@@ -206,9 +207,11 @@ public interface SopUserLogsInfoMapper {
 
     @DataSource(DataSourceType.SOP)
     List<SopUserLogsInfo> repeatProject(@Param("projects") List<Integer> projects, @Param("externalUserID") String externalUserID);
+
     @DataSource(DataSourceType.SOP)
     @Select("SELECT id  FROM sop_user_logs_info where external_id = #{extId} ")
     List<String> selectSopUserLogsInfoByExtId(@Param("extId")Long extId );
+
     @DataSource(DataSourceType.SOP)
     void updateSopUserLogsInfoFsUserIdById(@Param("data")List<String> list,  @Param("userId") Long userId);
 }

+ 2 - 0
fs-service-system/src/main/java/com/fs/sop/service/ISopUserLogsInfoService.java

@@ -21,6 +21,8 @@ public interface ISopUserLogsInfoService {
      */
     void update(SopUserLogsInfo info);
 
+    void updateSopUserInfoByExternalId(Long qwExternalId,Long userId);
+
     /**
      * 根据ID查询记录
      * @param id 主键ID

+ 5 - 0
fs-service-system/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -141,6 +141,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         sopUserLogsInfoMapper.updateById(info);
     }
 
+    @Override
+    public void updateSopUserInfoByExternalId(Long qwExternalId, Long userId) {
+        sopUserLogsInfoMapper.updateQwExternalContactChangeUserId(qwExternalId,userId);
+    }
+
     @Override
     public SopUserLogsInfo getById(String id) {
         return sopUserLogsInfoMapper.selectById(id);

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

@@ -0,0 +1,31 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxDownloadFileDTO {
+    /**
+     * uuid
+     */
+    private String uuid;
+    /**
+     * 文件id
+     */
+    private String fileid;
+    /**
+     * aes_key
+     */
+    private String aes_key;
+    /**
+     * 下载文件类型1原图 2 中图 3缩略图 4视频 5文件语音
+     */
+    private Integer filetype;
+    /**
+     * 文件名
+     */
+    private String file_name;
+    /**
+     * 文件大小
+     */
+    private Integer size;
+}

+ 4 - 0
fs-service-system/src/main/java/com/fs/wxwork/dto/WxWorkMessageDTO.java

@@ -29,4 +29,8 @@ public class WxWorkMessageDTO {
 
     // 动态表情
     private String url;
+
+    // 语音
+    private String voice_id;
+    private Integer voice_size;
 }

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

@@ -222,6 +222,14 @@ public interface WxWorkService {
      */
     WxWorkResponseDTO<String> downloadWeChatFile(WxwDownloadWeChatFileDTO param, Long serverId);
 
+    /**
+     * CDN下载文件重置版本
+     * @param param     参数
+     * @param serverId  服务器ID
+     * @return  WxWorkResponseDTO
+     */
+    WxWorkResponseDTO<String> downloadFile(WxDownloadFileDTO param, Long serverId);
+
     /**
      * CDN上传网络图片
      * @param param     参数

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

@@ -291,6 +291,18 @@ public class WxWorkServiceImpl implements WxWorkService {
         return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<String>>() {});
     }
 
+    /**
+     * CDN下载文件重置版本
+     * @param param     参数
+     * @param serverId  服务器ID
+     * @return  WxWorkResponseDTO
+     */
+    @Override
+    public WxWorkResponseDTO<String> downloadFile(WxDownloadFileDTO param, Long serverId) {
+        String url = getUrl(serverId) + "/DownloadFile";
+        return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<String>>() {});
+    }
+
     /**
      * CDN上传网络图片
      * @param param     参数

+ 11 - 11
fs-service-system/src/main/resources/application-config-dev.yml

@@ -71,18 +71,18 @@ wx:
         msgDataFormat: JSON
 
 ##  云联融智优选小程序,暂时使用
-#      - appid: wxd70f99287830cb51   #云联融智优选(暂时用于测试销售app)
-#        secret: 6e2684b3d48e6363018d4eedb8dae3e5
-#        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
-#        aesKey:
-#        msgDataFormat: JSON
-
-      - appid: wxb9b453d37c5fad45   #福本源小程序
-        secret: 45ee94e8c48edbafdcca2131a5e9d48d
+      - appid: wxd70f99287830cb51   #云联融智优选(暂时用于测试销售app)
+        secret: 6e2684b3d48e6363018d4eedb8dae3e5
         token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
         aesKey:
         msgDataFormat: JSON
 
+#      - appid: wxb9b453d37c5fad45   #福本源小程序
+#        secret: 45ee94e8c48edbafdcca2131a5e9d48d
+#        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
+#        aesKey:
+#        msgDataFormat: JSON
+
   pay:
     appId: wx93ce67750e3cfba3 #微信公众号或者小程序等的appid
     mchId: 1703311381 #微信支付商户号
@@ -114,9 +114,9 @@ aifabu:  #爱链接
 tencent_cloud_config:
   secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
   secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
-  bucket: 1323137866
+  bucket: hylj-1323137866
   app_id: 1323137866
-  region: chongqing
-  proxy: hzyy
+  region: ap-chongqing
+  proxy: hylj
 cloud_host:
   company_name: 润天

+ 1 - 1
fs-user-app/src/main/java/com/fs/app/controller/WxUserController.java

@@ -68,7 +68,7 @@ public class WxUserController extends AppBaseController{
             return R.error("code不存在");
         }
 
-        final WxMaService wxService = WxMaConfiguration.getMaService(maProperties.getConfigs().get(0).getAppid());
+        final WxMaService wxService = WxMaConfiguration.getMaService(maProperties.getConfigs().get(1).getAppid());
         try {
             // 获取微信会话信息
             WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(param.getCode());

+ 2 - 1
fs-user-app/src/main/java/com/fs/core/config/DataSourceConfig.java

@@ -41,7 +41,8 @@ public class DataSourceConfig {
     public DynamicDataSource dataSource(@Qualifier("sopDataSource") DataSource sopDataSource,
                                         @Qualifier("masterDataSource") DataSource masterDataSource) {
         Map<Object, Object> targetDataSources = new HashMap<>();
-        targetDataSources.put(DataSourceType.SOP.name(), sopDataSource); // Ensure matching key
+        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
+        targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
         return new DynamicDataSource(masterDataSource, targetDataSources);
     }
 

+ 2 - 2
fs-user-app/src/main/resources/application.yml

@@ -6,6 +6,6 @@ server:
 
 spring:
   profiles:
-    active: druid-fby
-    include: common,config-fby
+    active: dev
+    include: common,config-dev