Просмотр исходного кода

Merge remote-tracking branch 'origin/企微聊天' into 企微聊天

yh 2 дней назад
Родитель
Сommit
e3d77e995e

+ 271 - 0
fs-company/src/main/java/com/fs/company/controller/company/TestController.java

@@ -0,0 +1,271 @@
+package com.fs.company.controller.company;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.fs.common.core.redis.RedisCacheT;
+import com.fs.common.utils.date.DateUtil;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwIpadServerMapper;
+import com.fs.qw.mapper.QwPushCountMapper;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.service.impl.AsyncSopTestService;
+import com.fs.qw.vo.QwSopCourseFinishTempSetting;
+import com.fs.qw.vo.QwSopTempSetting;
+import com.fs.sop.domain.QwSopLogs;
+import com.fs.sop.mapper.QwSopLogsMapper;
+import com.fs.sop.service.IQwSopLogsService;
+import com.fs.sop.service.impl.QwSopLogsServiceImpl;
+import com.fs.system.mapper.SysConfigMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@Slf4j
+@RestController()
+@RequestMapping("/test")
+public class TestController {
+    private final QwUserMapper qwUserMapper;
+    private final QwSopLogsMapper qwSopLogsMapper;
+    //private final IpadSendServer sendServer;
+    private final SysConfigMapper sysConfigMapper;
+    private final IQwSopLogsService qwSopLogsService;
+    private final AsyncSopTestService asyncSopTestService;
+    private final QwIpadServerMapper qwIpadServerMapper;
+    private final RedisCacheT<Long> redisCache;
+    private final QwPushCountMapper qwPushCountMapper;
+    //    private final QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper;
+    private final IFsCourseWatchLogService watchLogService;
+    private final List<QwUser> qwUserList = Collections.synchronizedList(new ArrayList<>());
+    private final Map<Long, Long> qwMap = new ConcurrentHashMap<>();
+
+    public TestController(QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, SysConfigMapper sysConfigMapper, IQwSopLogsService qwSopLogsService, AsyncSopTestService asyncSopTestService, QwIpadServerMapper qwIpadServerMapper, RedisCacheT<Long> redisCache, QwPushCountMapper qwPushCountMapper, IFsCourseWatchLogService watchLogService) {
+        this.qwUserMapper = qwUserMapper;
+        this.qwSopLogsMapper = qwSopLogsMapper;
+//        this.sendServer = sendServer;
+        this.sysConfigMapper = sysConfigMapper;
+        this.qwSopLogsService = qwSopLogsService;
+        this.asyncSopTestService = asyncSopTestService;
+        this.qwIpadServerMapper = qwIpadServerMapper;
+        this.redisCache = redisCache;
+        this.qwPushCountMapper = qwPushCountMapper;
+        this.watchLogService = watchLogService;
+    }
+
+    @PostMapping("/sendCourse")
+    public void sendCourse()
+    {
+//        List<QwUser> qwUsers = qwUserMapper.selectList(new QueryWrapper<QwUser>().eq("id",26296L).in("server_id", 9));
+        QwUser qwUser = qwUserMapper.selectQwUserById(10482L);
+        long start1 = System.currentTimeMillis();
+        // 获取当前企微的app待发送记录
+        List<QwSopLogs> qwSopLogList = qwSopLogsMapper.selectAllAppEByQwUserId(qwUser.getId());
+        if (qwSopLogList.isEmpty()) {
+            return;
+        }
+        List<String> typeList = Arrays.asList("9", "15", "16");
+        // 获取企微用户
+        QwUser user = qwUserMapper.selectById(qwUser.getId());
+        long end1 = System.currentTimeMillis();
+        log.info("SendAppMsg-销售:{}, 消息:{}, 耗时: {}, 时间:{}", user.getQwUserName(), qwSopLogList.size(), end1 - start1, qwMap.get(qwUser.getId()));
+        long start3 = System.currentTimeMillis();
+        Map<String, Integer> pushCountMap = new HashMap<>();
+        qwPushCountMapper.selectQwPushCountListByType(typeList).forEach(e -> {String key = e.getCompanyId() != null ? String.valueOf(e.getCompanyId()) : String.valueOf(e.getType());
+            pushCountMap.put(key, Math.toIntExact(e.getPushCount()));});
+        // 循环待发送消息
+        for (QwSopLogs qwSopLogs : qwSopLogList) {
+            long start2 = System.currentTimeMillis();
+            QwSopCourseFinishTempSetting setting = JSON.parseObject(qwSopLogs.getContentJson(), QwSopCourseFinishTempSetting.class);
+            // 判断消息状态是否满足发送条件
+            if (!isSendLogs(qwSopLogs, setting, user)) {
+                continue;
+            }
+            String key = "qw:logs:pad:send:id:" + qwSopLogs.getId();
+            Long time = redisCache.getCacheObject(key);
+            // 判断这个消息有没有进入过发送,如果进了就不要再发了,防止重复发送,,,,, TODO 千万不能动!!!!!
+//            if (redisCache.getCacheObject(key) != null) {
+//                log.error("{}已有发送:{}, :{}", qwUser.getQwUserName(), qwSopLogs.getId(), time);
+//                continue;
+//            }
+            List<QwSopTempSetting.Content.Setting> settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class);
+            List<QwSopCourseFinishTempSetting.Setting> allContent = setting.getSetting();
+
+            if (setting.getType() != 4 && !CollectionUtils.isEmpty(settings) && settings.stream().anyMatch(e -> typeList.contains(e.getContentType()))) {
+                // 发送前次数限制校验
+                Long qwUserId = qwUser.getId();
+                Long customerId = qwSopLogs.getFsUserId();
+                Long companyId = qwSopLogs.getCompanyId();
+                boolean txtSendStatus = true;
+                boolean mp3SendStatus = true;
+                boolean courseSendStatus = true;
+
+
+                redisCache.setCacheObject(key, System.currentTimeMillis(), 3, TimeUnit.HOURS);
+
+                // 推送 APP
+                if (!setting.getSetting().isEmpty()) {
+                    try {
+//                        settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
+                        List<QwSopCourseFinishTempSetting.Setting> linkList = allContent.stream().filter(e -> "9".equals(e.getContentType())&& !Integer.valueOf(2).equals(e.getSendStatus())).collect(Collectors.toList());
+
+                        if (!linkList.isEmpty()) {
+                            courseSendStatus = asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(linkList, qwSopLogs.getCorpId(), user.getCompanyUserId(), qwSopLogs.getFsUserId(), qwSopLogs.getId());
+                        }
+                        //app文本消息
+                        log.info("开始发送app文本消息消息开始,消息{},用户{}", com.alibaba.fastjson.JSONObject.toJSONString(allContent), user.getQwUserName());
+//                        settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "15".equals(e.getContentType())).collect(Collectors.toList());
+                        List<QwSopCourseFinishTempSetting.Setting> txtList = allContent.stream().filter(e -> "15".equals(e.getContentType())&& e.getSendStatus() == null).collect(Collectors.toList());
+
+                        if (!txtList.isEmpty()) {
+                            txtSendStatus = asyncSopTestService.asyncSendMsgBySopAppTxtNormalIM(txtList, qwSopLogs.getCorpId(), qwUser.getCompanyUserId(), qwSopLogs.getFsUserId(), qwSopLogs.getId());
+                        }
+                        //app语音消息
+                        log.info("开始发送app语音消息消息开始,消息{},用户{}", com.alibaba.fastjson.JSONObject.toJSONString(allContent), user.getQwUserName());
+//                        settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "16".equals(e.getContentType())).collect(Collectors.toList());
+                        List<QwSopCourseFinishTempSetting.Setting> voiceList = allContent.stream().filter(e -> "16".equals(e.getContentType())&& e.getSendStatus() == null).collect(Collectors.toList());
+                        if (!voiceList.isEmpty()) {
+                            mp3SendStatus = asyncSopTestService.asyncSendMsgBySopAppMP3NormalIM(voiceList, qwSopLogs.getCorpId(), qwUser.getCompanyUserId(), qwSopLogs.getFsUserId(), qwSopLogs.getId());
+                        }
+                        // 发送成功后记录次数
+                        // 发送成功后记录次数(只记录真正发送的 content)
+
+
+
+                    } catch (Exception e) {
+                        log.error("推送APP失败", e);
+                    }
+                }
+//                qwSopLogs.setSend(true);
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                QwSopLogs updateQwSop = new QwSopLogs();
+                if (courseSendStatus&&txtSendStatus&&mp3SendStatus){
+                    updateQwSop.setAppSendRemark("APP全部发送成功");
+                    updateQwSop.setAppSendStatus(1);
+                }else if(!courseSendStatus&&!txtSendStatus&&!mp3SendStatus){
+                    updateQwSop.setAppSendRemark("APP全部发送失败");
+                    updateQwSop.setAppSendStatus(2);
+                }else {
+                    updateQwSop.setAppSendRemark("APP部分发送失败");
+                    updateQwSop.setAppSendStatus(2);
+                }
+                Set<String> contentTypes = allContent.stream()
+                        .map(QwSopCourseFinishTempSetting.Setting::getContentType)
+                        .collect(Collectors.toSet());
+
+                if (contentTypes.stream().allMatch(typeList::contains)) {
+                    updateQwSop.setReceivingStatus(1L);
+                    updateQwSop.setSendStatus(1L);
+                }
+                updateQwSop.setId(qwSopLogs.getId());
+                updateQwSop.setRealSendTime(sdf.format(new Date()));
+                updateQwSop.setContentJson(JSON.toJSONString(setting));
+//                long end2 = System.currentTimeMillis();
+                int i = qwSopLogsService.updateQwSopLogsSendType(updateQwSop);
+//                log.info("SendAppMsg-销售:{}, 修改条数{}, 发送方消息完成:{}, 耗时: {}", user.getQwUserName(), i, qwSopLogs.getId(), end2 - start2);
+            }
+        }
+        try {
+            Thread.sleep(500);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.warn("SendAppMsg-休眠被中断:{}", qwUser.getQwUserName());
+        }
+        long end3 = System.currentTimeMillis();
+        log.info("SendAppMsg-销售执行完成:{}, 耗时:{}", user.getQwUserName(), end3 - start3);
+    }
+
+    public boolean isSendLogs(QwSopLogs qwSopLogs, QwSopCourseFinishTempSetting setting, QwUser qwUser) {
+        if(qwSopLogs.getIsHaveApp()!=1){
+            log.info("不包含app课程:{}, LOGID: {}", qwUser.getQwUserName(), qwSopLogs.getId());
+            return false;
+        }
+        if(redisCache.getCacheObject("qw:user:id:" + qwUser.getId()) != null){
+            log.info("频率异常不发送:{}", qwUser.getQwUserName());
+            return false;
+        }
+
+        boolean noSop = qwSopLogs.getSendType() != 3 && qwSopLogs.getSendType() != 7;
+
+        if (qwSopLogs.getExpiryTime() == null && noSop) {
+            // 作废消息
+            log.warn("SOP_LOG_ID:{}, SOP任务被删除", qwSopLogs.getId());
+            qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "SOP任务被删除");
+            return false;
+        }
+
+        LocalDateTime sendTime = DateUtil.stringToLocalDateTime(qwSopLogs.getSendTime());
+        LocalDateTime expiryDateTime;
+
+        // 判断是否过期
+        if(qwSopLogs.getSendType() == 3 || qwSopLogs.getSendType() == 7){
+            expiryDateTime = sendTime.plusHours(16);
+        }else{
+            expiryDateTime = sendTime.plusHours(qwSopLogs.getExpiryTime());
+        }
+
+        if (LocalDateTime.now().isAfter(expiryDateTime)  ) {
+            // 作废消息
+            log.warn("SOP_LOG_ID:{}, 已过期,不发送", qwSopLogs.getId());
+            qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "已过期,不发送");
+            return false;
+        }
+
+        if (setting.getCourseType() == null && noSop && setting.getType() == 2) {
+            log.warn("SOP_LOG_ID:{}, 模板未选消息类型,不发送", qwSopLogs.getId());
+            qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "模板未选消息类型,不发送");
+            return false;
+        }
+        Long cacheValue = redisCache.getCacheObject("sopCourse:video:isPause:" + setting.getVideoId());
+        Long isPause = (cacheValue != null) ? cacheValue : 0l;
+        log.info("SOP_LOG_ID:{},判断课程({})当前状态:{}", qwSopLogs.getId(), setting.getVideoId(), isPause);
+        if (isPause == 1){
+            log.info("SOP_LOG_ID:{}, 课程暂停,不发送", qwSopLogs.getId());
+            qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "课程暂停,AI不发送");
+            return false;
+        }
+
+        if (qwSopLogs.getSendType() != 16 && noSop) {
+            // 客户的信息
+//            QwExternalContactHParam contactHParam = new QwExternalContactHParam();
+//            contactHParam.setUserId(qwUser.getQwUserId().trim());
+//            contactHParam.setExternalUserId(qwSopLogs.getExternalUserId().trim());
+//            contactHParam.setCorpId(qwUser.getCorpId().trim());
+            Integer courseType = setting.getCourseType();
+            if (setting.getType() == 2 && courseType != 0) {// 课程消息,进行复杂的条件判断
+//                log.debug("企微查询:{}", contactHParam);
+//                Long qwExternalContactId = qwExternalContactMapper.getQwExternalContactId(contactHParam);
+                FsCourseWatchLog watchLog = watchLogService.getWatchCourseLogVideoBySop(
+                        setting.getVideoId().longValue(),
+                        String.valueOf(qwUser.getId()),
+                        qwSopLogs.getExternalId()
+                );
+                log.debug("ID:{}-看课记录参数:videoID:{}, qwUserID:{}, extID:{}", qwSopLogs.getId(), setting.getVideoId().longValue(), qwUser.getId(), qwSopLogs.getExternalId());
+                log.debug("ID:{}-看课记录:{}", qwSopLogs.getId(), watchLog);
+                String logId = qwSopLogs.getId();
+                if (watchLog != null) {
+                    //新逻辑
+                    if (!QwSopLogsServiceImpl.isCourseTypeValid(courseType, watchLog.getLogType())) {
+                        // 作废消息
+                        log.warn("SOP_LOG_ID:{}, 看课状态未满足,不发送", qwSopLogs.getId());
+                        qwSopLogsService.updateQwSopLogsByWatchLogType(logId, "看课状态未满足,不发送");
+                        return false;
+                    }
+                } else {
+                    log.warn("SOP_LOG_ID:{}, 无观看记录,不发送", qwSopLogs.getId());
+                    qwSopLogsService.updateQwSopLogsByWatchLogType(logId, "无观看记录,不发送");
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+}

+ 86 - 0
fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java

@@ -996,4 +996,90 @@ public class IpadSendServer {
 
         return record;
     }
+
+    public boolean isSendAppLogs(QwSopLogs qwSopLogs, QwSopCourseFinishTempSetting setting, QwUser qwUser) {
+        if(qwSopLogs.getIsHaveApp()!=1){
+            log.info("不包含app课程:{}, LOGID: {}", qwUser.getQwUserName(), qwSopLogs.getId());
+            return false;
+        }
+        if(redisCache.getCacheObject("qw:user:id:" + qwUser.getId()) != null){
+            log.info("频率异常不发送:{}", qwUser.getQwUserName());
+            return false;
+        }
+
+        boolean noSop = qwSopLogs.getSendType() != 3 && qwSopLogs.getSendType() != 7;
+
+        if (qwSopLogs.getExpiryTime() == null && noSop) {
+            // 作废消息
+            log.info("SOP_LOG_ID:{}, SOP任务被删除", qwSopLogs.getId());
+            qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "SOP任务被删除");
+            return false;
+        }
+
+        LocalDateTime sendTime = DateUtil.stringToLocalDateTime(qwSopLogs.getSendTime());
+        LocalDateTime expiryDateTime;
+
+        // 判断是否过期
+        if(qwSopLogs.getSendType() == 3 || qwSopLogs.getSendType() == 7){
+            expiryDateTime = sendTime.plusHours(12);
+        }else{
+            expiryDateTime = sendTime.plusHours(qwSopLogs.getExpiryTime());
+        }
+
+        if (LocalDateTime.now().isAfter(expiryDateTime)  ) {
+            // 作废消息
+            log.info("SOP_LOG_ID:{}, 已过期,不发送", qwSopLogs.getId());
+            qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "已过期,不发送");
+            return false;
+        }
+
+        if (setting.getCourseType() == null && noSop && setting.getType() == 2) {
+            log.info("SOP_LOG_ID:{}, 模板未选消息类型,不发送", qwSopLogs.getId());
+            qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "模板未选消息类型,不发送");
+            return false;
+        }
+        Integer cacheValue = redisCache.getCacheObject("sopCourse:video:isPause:" + setting.getVideoId());
+        int isPause = (cacheValue != null) ? cacheValue : 0;
+        log.info("SOP_LOG_ID:{},判断课程({})当前状态:{}", qwSopLogs.getId(), setting.getVideoId(), isPause);
+        if (isPause == 1){
+            log.info("SOP_LOG_ID:{}, 课程暂停,不发送", qwSopLogs.getId());
+            qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "课程暂停,AI不发送");
+            return false;
+        }
+
+        if (qwSopLogs.getSendType() != 12 && noSop) {
+            // 客户的信息
+//            QwExternalContactHParam contactHParam = new QwExternalContactHParam();
+//            contactHParam.setUserId(qwUser.getQwUserId().trim());
+//            contactHParam.setExternalUserId(qwSopLogs.getExternalUserId().trim());
+//            contactHParam.setCorpId(qwUser.getCorpId().trim());
+            Integer courseType = setting.getCourseType();
+            if (setting.getType() == 2 && courseType != 0) {// 课程消息,进行复杂的条件判断
+//                log.debug("企微查询:{}", contactHParam);
+//                Long qwExternalContactId = qwExternalContactMapper.getQwExternalContactId(contactHParam);
+                FsCourseWatchLog watchLog = watchLogService.getWatchCourseLogVideoBySop(
+                        setting.getVideoId().longValue(),
+                        String.valueOf(qwUser.getId()),
+                        qwSopLogs.getExternalId()
+                );
+                log.debug("ID:{}-看课记录参数:videoID:{}, qwUserID:{}, extID:{}", qwSopLogs.getId(), setting.getVideoId().longValue(), qwUser.getId(), qwSopLogs.getExternalId());
+                log.debug("ID:{}-看课记录:{}", qwSopLogs.getId(), watchLog);
+                String logId = qwSopLogs.getId();
+                if (watchLog != null) {
+                    //新逻辑
+                    if (!QwSopLogsServiceImpl.isCourseTypeValid(courseType, watchLog.getLogType())) {
+                        // 作废消息
+                        log.info("SOP_LOG_ID:{}, 看课状态未满足,不发送", qwSopLogs.getId());
+                        qwSopLogsService.updateQwSopLogsByWatchLogType(logId, "看课状态未满足,不发送");
+                        return false;
+                    }
+                } else {
+                    log.info("SOP_LOG_ID:{}, 无观看记录,不发送", qwSopLogs.getId());
+                    qwSopLogsService.updateQwSopLogsByWatchLogType(logId, "无观看记录,不发送");
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
 }

+ 298 - 0
fs-ipad-task/src/main/java/com/fs/app/task/SendAppMsg.java

@@ -0,0 +1,298 @@
+package com.fs.app.task;
+
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.fs.app.service.IpadSendServer;
+import com.fs.common.core.redis.RedisCacheT;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.PubFun;
+import com.fs.qw.domain.QwIpadServer;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.*;
+import com.fs.qw.service.impl.AsyncSopTestService;
+import com.fs.qw.vo.QwSopCourseFinishTempSetting;
+import com.fs.qw.vo.QwSopTempSetting;
+import com.fs.sop.domain.QwSopLogs;
+import com.fs.sop.mapper.QwSopLogsMapper;
+import com.fs.sop.service.IQwSopLogsService;
+import com.fs.system.mapper.SysConfigMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@Component
+@Slf4j
+public class SendAppMsg {
+
+    private final QwUserMapper qwUserMapper;
+    private final QwSopLogsMapper qwSopLogsMapper;
+    private final IpadSendServer sendServer;
+    private final SysConfigMapper sysConfigMapper;
+    private final IQwSopLogsService qwSopLogsService;
+    private final AsyncSopTestService asyncSopTestService;
+    private final QwIpadServerMapper qwIpadServerMapper;
+    private final RedisCacheT<Long> redisCache;
+    private final QwPushCountMapper qwPushCountMapper;
+    @Value("${group-no}")
+    private String groupNo;
+    private final List<QwUser> qwUserList = Collections.synchronizedList(new ArrayList<>());
+    private final Map<Long, Long> qwMap = new ConcurrentHashMap<>();
+
+    @Autowired
+    @Qualifier("customThreadPool")
+    private ThreadPoolTaskExecutor customThreadPool;
+
+
+    public SendAppMsg(QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, IpadSendServer sendServer, SysConfigMapper sysConfigMapper, IQwSopLogsService qwSopLogsService, AsyncSopTestService asyncSopTestService, QwIpadServerMapper qwIpadServerMapper, RedisCacheT<Long> redisCache, QwPushCountMapper qwPushCountMapper) {
+        this.qwUserMapper = qwUserMapper;
+        this.qwSopLogsMapper = qwSopLogsMapper;
+        this.sendServer = sendServer;
+        this.sysConfigMapper = sysConfigMapper;
+        this.qwSopLogsService = qwSopLogsService;
+        this.asyncSopTestService = asyncSopTestService;
+        this.qwIpadServerMapper = qwIpadServerMapper;
+        this.redisCache = redisCache;
+        this.qwPushCountMapper = qwPushCountMapper;
+    }
+
+    private List<QwUser> getQwUserList() {
+        if (qwUserList.isEmpty()) {
+            List<QwIpadServer> serverList = qwIpadServerMapper.selectList(new QueryWrapper<QwIpadServer>().eq("group_no", groupNo));
+            if (serverList.isEmpty()) {
+                return new ArrayList<>();
+            }
+            List<Long> serverIds = PubFun.listToNewList(serverList, QwIpadServer::getId);
+            //离线在线的app消息一起发送
+            List<QwUser> qwUsers = qwUserMapper.selectList(new QueryWrapper<QwUser>().in("server_id", serverIds));
+            //List<QwUser> onLineQwUsers = qwUserMapper.selectList(new QueryWrapper<QwUser>().eq("send_msg_type", 1).eq("server_status", 1).eq("ipad_status", 1).in("server_id", serverIds));
+            qwUserList.addAll(qwUsers);
+            //qwUserList.addAll(onLineQwUsers);
+        }
+        return qwUserList;
+    }
+
+
+    @Scheduled(fixedRate = 50000) // 每50秒执行一次
+    public void refulsQwUserList() {
+        qwUserList.clear();
+    }
+
+    @Scheduled(fixedDelay = 60000) // 每20秒执行一次
+    public void sendMsg2() {
+        if (StringUtils.isEmpty(groupNo)) {
+            log.error("corpId为空不执行");
+            return;
+        }
+
+        // 获取 pad 发送的企微
+        getQwUserList().forEach(e -> {
+            // 如果没有值就执行后面的方法 并且入值
+            qwMap.computeIfAbsent(e.getId(), k -> {
+                // 线程启动
+                CompletableFuture.runAsync(() -> {
+                    try {
+                        log.info("SendAppMsg-开始任务:{}", e.getQwUserName());
+                        // 开始任务
+                        processUser(e);
+                    } catch (Exception exception) {
+                        log.error("发送错误:", exception);
+                    } finally {
+                        log.info("SendAppMsg-删除任务:{}", e.getQwUserName());
+                        qwMap.remove(e.getId());
+                    }
+                }, customThreadPool);
+                return System.currentTimeMillis(); // 占位值
+            });
+        });
+    }
+
+    /**
+     * 发送任务执行
+     *
+     * @param qwUser 发送企微
+     * @param
+     * @param
+     * @param
+     */
+    private void processUser(QwUser qwUser) {
+        long start1 = System.currentTimeMillis();
+        // 获取当前企微的app待发送记录
+        List<QwSopLogs> qwSopLogList = qwSopLogsMapper.selectAllAppEByQwUserId(qwUser.getId());
+        if (qwSopLogList.isEmpty()) {
+            return;
+        }
+        List<String> typeList = Arrays.asList("9", "15", "16");
+        // 获取企微用户
+        QwUser user = qwUserMapper.selectById(qwUser.getId());
+        long end1 = System.currentTimeMillis();
+        log.info("SendAppMsg-销售:{}, 消息:{}, 耗时: {}, 时间:{}", user.getQwUserName(), qwSopLogList.size(), end1 - start1, qwMap.get(qwUser.getId()));
+        long start3 = System.currentTimeMillis();
+        Map<String, Integer> pushCountMap = new HashMap<>();
+        qwPushCountMapper.selectQwPushCountListByType(typeList).forEach(e -> {String key = e.getCompanyId() != null ? String.valueOf(e.getCompanyId()) : String.valueOf(e.getType());
+            pushCountMap.put(key, Math.toIntExact(e.getPushCount()));});
+        // 循环待发送消息
+        for (QwSopLogs qwSopLogs : qwSopLogList) {
+            long start2 = System.currentTimeMillis();
+            QwSopCourseFinishTempSetting setting = JSON.parseObject(qwSopLogs.getContentJson(), QwSopCourseFinishTempSetting.class);
+            // 判断消息状态是否满足发送条件
+            if (!sendServer.isSendAppLogs(qwSopLogs, setting, user)) {
+                continue;
+            }
+            String key = "qw:logs:pad:send:app:id:" + qwSopLogs.getId();
+            Long time = redisCache.getCacheObject(key);
+            // 判断这个消息有没有进入过发送,如果进了就不要再发了,防止重复发送,,,,, TODO 千万不能动!!!!!
+            if (redisCache.getCacheObject(key) != null) {
+                log.error("{}已有发送:{}, :{}", qwUser.getQwUserName(), qwSopLogs.getId(), time);
+                continue;
+            }
+            List<QwSopTempSetting.Content.Setting> settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class);
+            List<QwSopCourseFinishTempSetting.Setting> allContent = setting.getSetting();
+
+            if (setting.getType() != 4 && !CollectionUtils.isEmpty(settings) && settings.stream().anyMatch(e -> typeList.contains(e.getContentType()))) {
+                // 发送前次数限制校验
+                Long qwUserId = qwUser.getId();
+                Long customerId = qwSopLogs.getFsUserId();
+                Long companyId = qwSopLogs.getCompanyId();
+                boolean txtSendStatus = true;
+                boolean mp3SendStatus = true;
+                boolean courseSendStatus = true;
+//                for (QwSopCourseFinishTempSetting.Setting content : allContent) {
+//                    String contentType = content.getContentType();
+//                    if (!typeList.contains(contentType)) {
+//                        continue;
+//                    }
+//
+//                    Integer pushCount = pushCountMap.containsKey(String.valueOf(companyId))
+//                            ? pushCountMap.get(String.valueOf(companyId))
+//                            : pushCountMap.getOrDefault(contentType, -99);
+//
+//                    if (pushCount == -99) {
+//                        continue; // 没有限制
+//                    }
+//
+//                    int sentCount = qwRestrictionPushRecordImMapper
+//                            .selectQwRestrictionPushRecordIm(qwUserId, customerId, Integer.valueOf(contentType), DateUtils.toStartTime(), DateUtils.toEndTime());
+//
+//                    if (sentCount >= pushCount) {
+//                        // 达到上限,标记 content
+//                        content.setSendStatus(2);
+//                        content.setSendRemarks("发送次数达到上限");
+//
+//                        log.warn("SendAppMsg-销售{} 客户{} 类型{} 已达发送上限({})",
+//                                user.getQwUserName(), customerId, contentType, pushCount);
+//                        if ("9".equals(contentType)) courseSendStatus = false;
+//                        if ("15".equals(contentType)) txtSendStatus = false;
+//                        if ("16".equals(contentType)) mp3SendStatus = false;
+//                        continue;
+//                    }
+//                }
+
+                redisCache.setCacheObject(key, System.currentTimeMillis(), 3, TimeUnit.HOURS);
+
+                // 推送 APP
+                if (!setting.getSetting().isEmpty()) {
+                    try {
+//                        settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
+                        List<QwSopCourseFinishTempSetting.Setting> linkList = allContent.stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
+
+                        if (!linkList.isEmpty()) {
+                            courseSendStatus = asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(linkList, qwSopLogs.getCorpId(), user.getCompanyUserId(), qwSopLogs.getFsUserId(), qwSopLogs.getId());
+                        }
+                        //app文本消息
+                        log.info("开始发送app文本消息消息开始,消息{},用户{}", com.alibaba.fastjson.JSONObject.toJSONString(allContent), user.getQwUserName());
+                        List<QwSopCourseFinishTempSetting.Setting> txtList = allContent.stream().filter(e -> "15".equals(e.getContentType())).collect(Collectors.toList());
+
+                        if (!txtList.isEmpty()) {
+                            txtSendStatus = asyncSopTestService.asyncSendMsgBySopAppTxtNormalIM(txtList, qwSopLogs.getCorpId(), qwUser.getCompanyUserId(), qwSopLogs.getFsUserId(), qwSopLogs.getId());
+                        }
+                        //app语音消息
+                        log.info("开始发送app语音消息消息开始,消息{},用户{}", com.alibaba.fastjson.JSONObject.toJSONString(allContent), user.getQwUserName());
+                        List<QwSopCourseFinishTempSetting.Setting> voiceList = allContent.stream().filter(e -> "16".equals(e.getContentType())).collect(Collectors.toList());
+                        if (!voiceList.isEmpty()) {
+                            mp3SendStatus = asyncSopTestService.asyncSendMsgBySopAppMP3NormalIM(voiceList, qwSopLogs.getCorpId(), qwUser.getCompanyUserId(), qwSopLogs.getFsUserId(), qwSopLogs.getId());
+                        }
+                        // 发送成功后记录次数
+                        // 发送成功后记录次数(只记录真正发送的 content)
+//                        for (QwSopCourseFinishTempSetting.Setting content : allContent) {
+//                            if (!typeList.contains(content.getContentType())) {
+//                                continue;
+//                            }
+//                            if (content.getSendStatus() != null && content.getSendStatus() == 2) {
+//                                // 达到上限的,不记录发送次数
+//                                continue;
+//                            }
+//                            QwRestrictionPushRecordIm record = new QwRestrictionPushRecordIm();
+//                            record.setType(Integer.valueOf(content.getContentType()));
+//                            record.setQwUserId(qwUserId);
+//                            record.setQwExternalId(customerId);
+//                            record.setCompanyId(companyId);
+//                            record.setStatus(1);
+//                            record.setCreateTime(DateUtils.getTime());
+//                            record.setTime(System.currentTimeMillis());
+//                            qwRestrictionPushRecordImMapper.insert(record);
+//                        }
+
+
+                    } catch (Exception e) {
+                        log.error("推送APP失败", e);
+                    }
+                }
+//                qwSopLogs.setSend(true);
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                QwSopLogs updateQwSop = new QwSopLogs();
+                if (courseSendStatus&&txtSendStatus&&mp3SendStatus){
+                    updateQwSop.setAppSendRemark("APP全部发送成功");
+                    updateQwSop.setAppSendStatus(1);
+                }else if(!courseSendStatus&&!txtSendStatus&&!mp3SendStatus){
+                    updateQwSop.setAppSendRemark("APP全部发送失败");
+                    updateQwSop.setAppSendStatus(2);
+                }else {
+                    updateQwSop.setAppSendRemark("APP部分发送失败");
+                    updateQwSop.setAppSendStatus(2);
+                }
+                Set<String> contentTypes = allContent.stream()
+                        .map(QwSopCourseFinishTempSetting.Setting::getContentType)
+                        .collect(Collectors.toSet());
+
+                if (contentTypes.stream().allMatch(typeList::contains)) {
+                    if (updateQwSop.getAppSendStatus()==1){
+                        updateQwSop.setReceivingStatus(1L);
+                        updateQwSop.setSendStatus(1L);
+                    }else {
+                        updateQwSop.setReceivingStatus(2L);
+                        updateQwSop.setSendStatus(2L);
+                    }
+                }
+                updateQwSop.setId(qwSopLogs.getId());
+                updateQwSop.setRealSendTime(sdf.format(new Date()));
+                updateQwSop.setContentJson(JSON.toJSONString(setting));
+//                long end2 = System.currentTimeMillis();
+                int i = qwSopLogsService.updateQwSopLogsSendType(updateQwSop);
+//                log.info("SendAppMsg-销售:{}, 修改条数{}, 发送方消息完成:{}, 耗时: {}", user.getQwUserName(), i, qwSopLogs.getId(), end2 - start2);
+            }
+        }
+        try {
+            Thread.sleep(200);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.warn("SendAppMsg-休眠被中断:{}", qwUser.getQwUserName());
+        }
+        long end3 = System.currentTimeMillis();
+        log.info("SendAppMsg-销售执行完成:{}, 耗时:{}", user.getQwUserName(), end3 - start3);
+    }
+}

+ 29 - 25
fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -279,36 +279,40 @@ public class SendMsg {
                 }
             }
             // 推送 APP
-            if (!setting.getSetting().isEmpty()) {
-                new Thread(() -> {
-                    try {
-                        List<QwSopTempSetting.Content.Setting> settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
-                        if (!settings.isEmpty()) {
-                            asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(settings, qwSopLogs.getCorpId(), user.getCompanyUserId(), qwSopLogs.getFsUserId());
-                        }
+//            if (!setting.getSetting().isEmpty()) {
+//                new Thread(() -> {
+//                    try {
+//                        List<QwSopTempSetting.Content.Setting> settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
+//                        if (!settings.isEmpty()) {
+//                            asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(settings, qwSopLogs.getCorpId(), user.getCompanyUserId(), qwSopLogs.getFsUserId());
+//                        }
+//
+//                        //app文本消息
+//                        log.info("开始发送app文本消息消息开始,消息{},用户{}", JSONObject.toJSONString(settings), user.getQwUserName());
+//                        settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "15".equals(e.getContentType())).collect(Collectors.toList());
+//
+//                        if (!settings.isEmpty()) {
+//                            asyncSopTestService.asyncSendMsgBySopAppTxtNormalIM(settings, qwSopLogs.getCorpId(), qwUser.getCompanyUserId(), qwSopLogs.getFsUserId());
+//                        }
+//                        //app语音消息
+//                        log.info("开始发送app语音消息消息开始,消息{},用户{}", JSONObject.toJSONString(settings), user.getQwUserName());
+//                        settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "16".equals(e.getContentType())).collect(Collectors.toList());
+//                        if (!settings.isEmpty()) {
+//                            asyncSopTestService.asyncSendMsgBySopAppMP3NormalIM(settings, qwSopLogs.getCorpId(), qwUser.getCompanyUserId(), qwSopLogs.getFsUserId());
+//                        }
+//                    } catch (Exception e) {
+//                        log.error("推送APP失败", e);
+//                    }
+//                }).start();
+//            }
 
-                        //app文本消息
-                        log.info("开始发送app文本消息消息开始,消息{},用户{}", JSONObject.toJSONString(settings), user.getQwUserName());
-                        settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "15".equals(e.getContentType())).collect(Collectors.toList());
-
-                        if (!settings.isEmpty()) {
-                            asyncSopTestService.asyncSendMsgBySopAppTxtNormalIM(settings, qwSopLogs.getCorpId(), qwUser.getCompanyUserId(), qwSopLogs.getFsUserId());
-                        }
-                        //app语音消息
-                        log.info("开始发送app语音消息消息开始,消息{},用户{}", JSONObject.toJSONString(settings), user.getQwUserName());
-                        settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "16".equals(e.getContentType())).collect(Collectors.toList());
-                        if (!settings.isEmpty()) {
-                            asyncSopTestService.asyncSendMsgBySopAppMP3NormalIM(settings, qwSopLogs.getCorpId(), qwUser.getCompanyUserId(), qwSopLogs.getFsUserId());
-                        }
-                    } catch (Exception e) {
-                        log.error("推送APP失败", e);
-                    }
-                }).start();
-            }
             qwSopLogs.setSend(true);
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             QwSopLogs updateQwSop = new QwSopLogs();
             updateQwSop.setId(qwSopLogs.getId());
+            updateQwSop.setId(qwSopLogs.getId());updateQwSop.setIsHaveApp(qwSopLogs.getIsHaveApp());
+            updateQwSop.setAppSendStatus(qwSopLogs.getAppSendStatus());
+            updateQwSop.setAppSendRemark(qwSopLogs.getAppSendRemark());
             // 是否全部发送失败
             if (setting.getSetting().stream().allMatch(e -> e.getSendStatus() == 2)) {
                 updateQwSop.setReceivingStatus(0L);

+ 10 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -1404,6 +1404,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     setting.setAppLinkUrl(linkByApp.getAppMsgLink().replaceAll("^[\\s\\u2005]+", ""));
                     setting.setCourseUrl(setting.getLinkImageUrl());
                     setting.setTitle(setting.getLinkDescribe()); //小节名称
+                    sopLogs.setIsHaveApp(1);
+                    sopLogs.setAppSendStatus(0);
 
                     break;
                 //自定义小程序
@@ -1500,6 +1502,14 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     setting.setMiniprogramPage(linkBy);
                     setting.setContentType("14");
                     break;
+                case "15":
+                    sopLogs.setIsHaveApp(1);
+                    sopLogs.setAppSendStatus(0);
+                    break;
+                case "16":
+                    sopLogs.setIsHaveApp(1);
+                    sopLogs.setAppSendStatus(0);
+                    break;
                 case "17":
                     try {
                         String sroth5link;

+ 28 - 3
fs-service/src/main/java/com/fs/gtPush/service/impl/uniPush2ServiceImpl.java

@@ -17,6 +17,7 @@ import com.fs.gtPush.domain.UniPushLog;
 import com.fs.gtPush.service.UniPushLogService;
 import com.fs.gtPush.service.uniPush2Service;
 import com.fs.im.config.IMConfig;
+import com.fs.im.dto.OpenImResponseDTO;
 import com.fs.im.service.OpenIMService;
 import lombok.extern.slf4j.Slf4j;
 import com.fs.system.domain.SysConfig;
@@ -65,12 +66,36 @@ public class uniPush2ServiceImpl implements uniPush2Service {
         return JSONUtil.toBean(result, PushResult.class);
     }
     @Override
-    public void pushSopAppLinkMsgByExternalIM(String cropId, String linkTile, String linkDescribe,String linkImageUrl, String link, Long companyUserId,Long fsUserId) throws JsonProcessingException {
+    public OpenImResponseDTO pushSopAppLinkMsgByExternalIM(String cropId, String linkTile, String linkDescribe, String linkImageUrl, String link, Long companyUserId, Long fsUserId) throws JsonProcessingException {
+
+        if (companyUserId == null || fsUserId == null || fsUserId == 0) {
+            OpenImResponseDTO errorResponse = new OpenImResponseDTO();
+            errorResponse.setErrCode(-1);
+            errorResponse.setErrMsg("参数错误:用户未绑定销售");
+            errorResponse.setErrDlt("缺少必要参数");
+            return errorResponse;
+        }
 
-        if (companyUserId!=null&&fsUserId!=null && fsUserId!=0){
-            openIMService.sendCourse(fsUserId,companyUserId,link,linkDescribe,linkImageUrl,cropId);
+        FsUser fsUser = userService.selectFsUserByUserId(fsUserId);
+        if (fsUser == null) {
+            OpenImResponseDTO errorResponse = new OpenImResponseDTO();
+            errorResponse.setErrCode(-2);
+            errorResponse.setErrMsg("未找到对应的用户信息");
+            errorResponse.setErrDlt("用户ID: " + fsUserId);
+            return errorResponse;
         }
 
+//        if (StringUtils.isEmpty(fsUser.getHistoryApp())) {
+//            OpenImResponseDTO errorResponse = new OpenImResponseDTO();
+//            errorResponse.setErrCode(-3);
+//            errorResponse.setErrMsg("用户未绑定APP");
+//            errorResponse.setErrDlt("用户历史APP信息为空");
+//            return errorResponse;
+//        }
+
+        OpenImResponseDTO openImResponseDTO = openIMService.sendCourse(fsUserId, companyUserId, link, linkDescribe, linkImageUrl, cropId);
+        return openImResponseDTO;
+
     }
 
     @Override

+ 3 - 1
fs-service/src/main/java/com/fs/gtPush/service/uniPush2Service.java

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fs.common.core.domain.R;
 import com.fs.gtPush.domain.PushReqBean;
 import com.fs.gtPush.domain.PushResult;
+import com.fs.im.dto.OpenImResponseDTO;
 
 public interface uniPush2Service {
     PushResult pushMessage(PushReqBean push);
@@ -11,6 +12,7 @@ public interface uniPush2Service {
     void pushOne(Long userId,Long businessId,String purl,String title,String content, Float type, Integer desType);
 
     PushReqBean getParam(Long userId,String purl,String title,String content,Float type,Integer desType,String imJsonString);
-    void pushSopAppLinkMsgByExternalIM(String cropId,String linkTile,String linkDescribe,String linkImageUrl,String link,Long companyUserId,Long fsUserId) throws JsonProcessingException;
+//    void pushSopAppLinkMsgByExternalIM(String cropId,String linkTile,String linkDescribe,String linkImageUrl,String link,Long companyUserId,Long fsUserId) throws JsonProcessingException;
+    OpenImResponseDTO pushSopAppLinkMsgByExternalIM(String cropId, String linkTile, String linkDescribe, String linkImageUrl, String link, Long companyUserId, Long fsUserId) throws JsonProcessingException;
     void pushIm(Long userId, Long businessId, String purl, String title, String content, Float type, Integer desType,String imJsonString);
 }

+ 11 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwPushCountMapper.java

@@ -75,4 +75,15 @@ public interface QwPushCountMapper extends BaseMapper<QwPushCount> {
      */
     int deleteQwPushCountByIds(Long[] ids);
 
+    @Select({
+            "<script>",
+            "SELECT id, type, push_count, company_id, status",
+            "FROM qw_push_count",
+            "WHERE type IN",
+            "<foreach collection='typeList' item='type' open='(' separator=',' close=')'>",
+            "#{type}",
+            "</foreach>",
+            "</script>"
+    })
+    List<QwPushCount> selectQwPushCountListByType(@Param("typeList") List<String> typeList);
 }

+ 194 - 64
fs-service/src/main/java/com/fs/qw/service/impl/AsyncSopTestService.java

@@ -4,33 +4,34 @@ import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSON;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fs.common.utils.PubFun;
+import com.fs.common.utils.StringUtils;
 import com.fs.course.domain.FsCourseSopAppLink;
 import com.fs.course.mapper.FsCourseSopAppLinkMapper;
 import com.fs.gtPush.service.uniPush2Service;
+import com.fs.his.domain.FsUser;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.im.dto.OpenImMsgDTO;
+import com.fs.im.dto.OpenImResponseDTO;
 import com.fs.im.service.OpenIMService;
 import com.fs.qw.domain.QwSopUpdateStatus;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.result.QwFilterSopCustomersResult;
+import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qw.vo.QwSopRuleTimeVO;
 import com.fs.qw.vo.QwSopTempSetting;
-import com.fs.sop.domain.QwSop;
-import com.fs.sop.domain.QwSopTemp;
-import com.fs.sop.domain.SopUserLogs;
-import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.domain.*;
 import com.fs.sop.mapper.*;
 import com.fs.sop.params.DeleteQwSopParam;
 import com.fs.sop.params.QwSopTagsParam;
 import com.fs.sop.params.SopUserLogsList;
+import com.fs.sop.service.IQwSopLogsService;
 import com.fs.sop.service.ISopUserLogsService;
 import com.fs.sop.vo.VoiceVo;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.param.CompanyWxUserSopParam;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang.StringUtils;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
@@ -63,6 +64,7 @@ public class AsyncSopTestService {
     private final FsCourseSopAppLinkMapper fsCourseSopAppLinkMapper;
     private final uniPush2Service push2Service;
     private final OpenIMService openIMService;
+    private final IQwSopLogsService qwSopLogsService;
     /**
      * 立即执行SOP任务
      */
@@ -510,25 +512,54 @@ public class AsyncSopTestService {
     /**
      * 异步录入 发送有app的客户 之 正常sop版
      */
-    @Async("scheduledExecutorService")
-    public void  asyncSendMsgBySopAppLinkNormalIM(List<QwSopTempSetting.Content.Setting> setting,String cropId,Long companyUserId,Long fsUserId){
+//    @Async("scheduledExecutorService")
+    public boolean asyncSendMsgBySopAppLinkNormalIM(
+            List<QwSopCourseFinishTempSetting.Setting> setting,
+            String cropId,
+            Long companyUserId,
+            Long fsUserId,
+            String logId) {
+
+        boolean success = true;
+        String remark = "APP发送成功";
+
+        for (QwSopCourseFinishTempSetting.Setting item : setting) {
+            item.setSendStatus(2);
+            item.setSendRemarks("APP发送失败");
 
-        setting.forEach(item->{
             try {
-                String linkImageUrl = item.getLinkImageUrl();
-                String linkTitle = item.getLinkTitle();
-                if (StringUtils.isBlank(linkImageUrl)) {
-                    linkImageUrl = item.getCourseUrl();
-                }
-                if (StringUtils.isBlank(linkTitle)) {
-                    linkTitle = item.getTitle();
+                OpenImResponseDTO resp =
+                        push2Service.pushSopAppLinkMsgByExternalIM(
+                                cropId,
+                                item.getLinkTitle(),
+                                item.getLinkDescribe(),
+                                item.getLinkImageUrl(),
+                                item.getAppLinkUrl(),
+                                companyUserId,
+                                fsUserId
+                        );
+                if (resp != null && resp.getErrCode() != null && resp.getErrCode() == 0) {
+                    item.setSendStatus(1);
+                    item.setSendRemarks("发送成功");
+                } else {
+                    success = false;
+                    remark = "APP发送失败";
+                    if (resp != null) {
+                        item.setSendRemarks(resp.getErrMsg());
+                    }
                 }
-                push2Service.pushSopAppLinkMsgByExternalIM(cropId, linkTitle,item.getLinkDescribe(), linkImageUrl,item.getAppLinkUrl(),companyUserId,fsUserId);
-            } catch (JsonProcessingException e) {
-                e.printStackTrace();
+
+            } catch (Exception e) {
+                success = false;
+                remark = "APP发送失败";
+                item.setSendRemarks("异常:" + e.getMessage());
+                log.error("APP课程发送异常 logId={}", logId, e);
             }
-        });
+        }
 
+//        updateAppSendStatus(logId, success, remark);
+        log.info("APP课程发送完成,logId={},结果={}", logId, remark);
+        return success;
     }
 
 
@@ -544,61 +575,160 @@ public class AsyncSopTestService {
 
     }
 
-    @Async("scheduledExecutorService")
-    public void  asyncSendMsgBySopAppTxtNormalIM(List<QwSopTempSetting.Content.Setting> setting,String cropId,Long companyUserId,Long fsUserId){
+    public boolean asyncSendMsgBySopAppTxtNormalIM(
+            List<QwSopCourseFinishTempSetting.Setting> setting,
+            String cropId,
+            Long companyUserId,
+            Long fsUserId,
+            String logId) {
 
-        setting.forEach(item->{
-            try {
-                log.info("执行发送app文本消息:{}",item);
-                OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
-                openImMsgDTO.setSendID("C"+companyUserId);
-                openImMsgDTO.setRecvID("U"+fsUserId);
-                openImMsgDTO.setContentType(101);
-                openImMsgDTO.setSessionType(1);
-                OpenImMsgDTO.Content imContent = new OpenImMsgDTO.Content();
-                imContent.setContent(item.getValue());
-                openImMsgDTO.setContent(imContent);
-                openIMService.openIMSendMsg(openImMsgDTO);
-            } catch (Exception e) {
-                throw new RuntimeException(e);
+        boolean success = true;
+        String remark = "APP发送成功";
+
+        try {
+            FsUser fsUser = fsUserMapper.selectFsUserByUserId(fsUserId);
+
+            for (QwSopCourseFinishTempSetting.Setting item : setting) {
+
+                // ===== 兜底:默认失败 =====
+                item.setSendStatus(2);
+                item.setSendRemarks("APP发送失败");
+
+                // ===== 用户校验 =====
+//                if (fsUser == null || StringUtils.isEmpty(fsUser.getHistoryApp())) {
+                if (fsUser == null) {
+                    item.setSendRemarks("未找到对应的用户信息");
+                    success = false;
+                    remark = "APP发送失败";
+                    continue;
+                }
+
+                try {
+                    OpenImMsgDTO dto = new OpenImMsgDTO();
+                    dto.setSendID("C" + companyUserId);
+                    dto.setRecvID("U" + fsUserId);
+                    dto.setContentType(101);
+                    dto.setSessionType(1);
+
+                    OpenImMsgDTO.Content content = new OpenImMsgDTO.Content();
+                    content.setContent(item.getValue());
+                    dto.setContent(content);
+
+                    OpenImResponseDTO resp = openIMService.openIMSendMsg(dto);
+
+                    if (resp != null && resp.getErrCode() != null && resp.getErrCode() == 0) {
+                        item.setSendStatus(1);
+                        item.setSendRemarks("发送成功");
+                    } else {
+                        success = false;
+                        remark = "APP发送失败";
+                        if (resp != null) {
+                            item.setSendRemarks(resp.getErrMsg());
+                        }
+                    }
+
+                } catch (Exception e) {
+                    success = false;
+                    remark = "APP发送失败";
+                    item.setSendRemarks("异常:" + e.getMessage());
+                    log.error("APP文本发送异常,logId={}", logId, e);
+                }
             }
-        });
 
+        } catch (Exception e) {
+            success = false;
+            remark = "APP发送失败";
+            log.error("APP文本发送外层异常,logId={}", logId, e);
+        }
+
+    //        updateAppSendStatus(logId, success, remark);
+        log.info("APP文本发送完成,logId={},结果={}", logId, remark);
+        return success;
     }
 
-    @Async("scheduledExecutorService")
-    public void  asyncSendMsgBySopAppMP3NormalIM(List<QwSopTempSetting.Content.Setting> setting,String cropId,Long companyUserId,Long fsUserId){
+        public boolean asyncSendMsgBySopAppMP3NormalIM(
+                List<QwSopCourseFinishTempSetting.Setting> setting,
+                String cropId,
+                Long companyUserId,
+                Long fsUserId,
+                String logId) {
+
+            boolean success = true;
+            String remark = "APP发送成功";
 
-        setting.forEach(item->{
             try {
-                if(StrUtil.isEmpty(item.getVoiceUrl())){
-                    log.info("执行发送app文本消息:{}",item);
-                    OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
-                    openImMsgDTO.setSendID("C"+companyUserId);
-                    openImMsgDTO.setRecvID("U"+fsUserId);
-                    openImMsgDTO.setContentType(101);
-                    openImMsgDTO.setSessionType(1);
-                    OpenImMsgDTO.Content imContent = new OpenImMsgDTO.Content();
-                    imContent.setContent(item.getValue());
-                    openImMsgDTO.setContent(imContent);
-                    openIMService.openIMSendMsg(openImMsgDTO);
-                }else {
-                    log.info("执行发送app语音消息:{}",item);
-                    OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
-                    openImMsgDTO.setSendID("C"+companyUserId);
-                    openImMsgDTO.setRecvID("U"+fsUserId);
-                    openImMsgDTO.setContentType(103);
-                    openImMsgDTO.setSessionType(1);
-                    OpenImMsgDTO.Content imContent = new OpenImMsgDTO.Content();
-                    imContent.setSourceUrl(item.getVoiceUrl());
-                    imContent.setDuration(Integer.parseInt(item.getVoiceDuration()));
-                    openImMsgDTO.setContent(imContent);
-                    openIMService.openIMSendMsg(openImMsgDTO);
+                FsUser fsUser = fsUserMapper.selectFsUserByUserId(fsUserId);
+
+                for (QwSopCourseFinishTempSetting.Setting item : setting) {
+
+                    // ===== 兜底:默认失败 =====
+                    item.setSendStatus(2);
+                    item.setSendRemarks("APP发送失败");
+
+                    // ===== 用户校验 =====
+//                    if (fsUser == null || StringUtils.isEmpty(fsUser.getHistoryApp())) {
+                    if (fsUser == null ) {
+                        item.setSendRemarks("未找到对应的用户信息");
+                        success = false;
+                        remark = "APP发送失败";
+                        continue;
+                    }
+
+                    try {
+                        OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
+                        openImMsgDTO.setSendID("C" + companyUserId);
+                        openImMsgDTO.setRecvID("U" + fsUserId);
+                        openImMsgDTO.setSessionType(1);
+
+                        OpenImMsgDTO.Content imContent = new OpenImMsgDTO.Content();
+
+                        // ===== 没有语音 URL,当文本发 =====
+                        if (StrUtil.isEmpty(item.getVoiceUrl())) {
+
+                            openImMsgDTO.setContentType(101);
+                            imContent.setContent(item.getValue());
+
+                        }
+                        // ===== 有语音 URL,按语音发 =====
+                        else {
+
+                            openImMsgDTO.setContentType(103);
+                            imContent.setSourceUrl(item.getVoiceUrl());
+                            imContent.setDuration(Integer.parseInt(item.getVoiceDuration()));
+                        }
+
+                        openImMsgDTO.setContent(imContent);
+
+                        OpenImResponseDTO resp = openIMService.openIMSendMsg(openImMsgDTO);
+
+                        if (resp != null && resp.getErrCode() != null && resp.getErrCode() == 0) {
+                            item.setSendStatus(1);
+                            item.setSendRemarks("发送成功");
+                        } else {
+                            success = false;
+                            remark = "APP发送失败";
+                            if (resp != null) {
+                                item.setSendRemarks(resp.getErrMsg());
+                            }
+                        }
+
+                    } catch (Exception e) {
+                        success = false;
+                        remark = "APP发送失败";
+                        item.setSendRemarks("异常:" + e.getMessage());
+                        log.error("APP语音/文本发送异常,logId={}", logId, e);
+                    }
                 }
+
             } catch (Exception e) {
-                throw new RuntimeException(e);
+                success = false;
+                remark = "APP发送失败";
+                log.error("APP语音发送外层异常,logId={}", logId, e);
             }
-        });
 
+        //        updateAppSendStatus(logId, success, remark);
+            log.info("APP语音发送完成,logId={},结果={}", logId, remark);
+            return success;
     }
+
 }

+ 11 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSopLogs.java

@@ -102,6 +102,17 @@ public class QwSopLogs implements Serializable {
     @TableField(exist = false)
     private Integer expiryTime;
 
+    /**
+     * 是否包含app消息1包含0不包含
+     */
+    private int isHaveApp;
+
+    /**
+     * app发送状态0未发送1成功2失败3不发送
+     */
+    private int appSendStatus;
+    private String appSendRemark;
+
     // 构造函数
 //    public QwSopLogs() {
 //        this.id = UUID.randomUUID().toString();

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

@@ -354,4 +354,7 @@ public interface QwSopLogsMapper extends BaseMapper<QwSopLogs> {
             "ORDER BY qw_user_key" +
             "</script>")
     List<QwApiSopLogToken> countQwApiAopLogToken(@Param("data") String dateStr);
+
+    @DataSource(DataSourceType.SOP)
+    List<QwSopLogs> selectAllAppEByQwUserId(Long id);
 }

+ 4 - 4
fs-service/src/main/java/com/fs/sop/service/impl/QwSopLogsServiceImpl.java

@@ -1111,10 +1111,10 @@ public class QwSopLogsServiceImpl extends ServiceImpl<QwSopLogsMapper, QwSopLogs
                                 List<QwSopTempSetting.Content.Setting> setting = content.getSetting().stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
 
                                 //有app的异步推送消息
-                                if (!setting.isEmpty()) {
-//                                    asyncSopTestService.asyncSendMsgBySopAppLinkNormal(setting, param.getExternalId());
-                                    asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(setting, param.getCorpId(),qwUser.getCompanyUserId(),log.getFsUserId());
-                                }
+//                                if (!setting.isEmpty()) {
+////                                    asyncSopTestService.asyncSendMsgBySopAppLinkNormal(setting, param.getExternalId());
+//                                    asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(setting, param.getCorpId(),qwUser.getCompanyUserId(),log.getFsUserId());
+//                                }
 
                                 if (log.getExpiryTime() == null) {
                                     // 作废消息

+ 22 - 1
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -753,8 +753,14 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
                                 st.setMiniprogramPage(linkByMiniApp);
                                 break;
+                            case "15":
+                                sopLogs.setIsHaveApp(1);
+                                sopLogs.setAppSendStatus(0);
+                                break;
                             case "16":
                                 createVoiceUrl(st, companyUserId, qwSop);
+                                sopLogs.setIsHaveApp(1);
+                                sopLogs.setAppSendStatus(0);
                                 break;
                             case "17": //h5看课
                                 try {
@@ -1000,6 +1006,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 st.setMiniprogramPage(linkByMiniApp);
                                 break;
                             case "15":
+                                sopLogs.setIsHaveApp(1);
+                                sopLogs.setAppSendStatus(0);
                                 String txt2 = StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText();
                                 st.setValue(st.getValue().replaceAll("#客服称呼#", txt2).replaceAll("#销售称呼#", txt2));
                                 try {
@@ -1009,6 +1017,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 }
                                 break;
                             case "16":
+                                sopLogs.setIsHaveApp(1);
+                                sopLogs.setAppSendStatus(0);
                                 if (qwUser.getCompanyUserId() != null) {
                                     createVoiceUrl(st, String.valueOf(qwUser.getCompanyUserId()), qwSop);
                                 }
@@ -1200,7 +1210,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                     Long.valueOf(qwUserId), companyUserId, companyId, item.getExternalId(), config,qwUser.getQwUserName(),contact.getFsUserId());
                             st.setLinkUrl(linkByApp.getSortLink().replaceAll("^[\\s\\u2005]+", ""));
                             st.setAppLinkUrl(linkByApp.getAppMsgLink().replaceAll("^[\\s\\u2005]+", ""));
-
+                            sopLogs.setIsHaveApp(1);
+                            sopLogs.setAppSendStatus(0);
                             break;
                         //自定义小程序
                         case "10":
@@ -1334,6 +1345,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                     .replaceAll("#客服称呼#", txt)
                                     .replaceAll("#销售称呼#", txt)
                                     .replaceAll("#客户称呼#", StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus()) ? "同学" : contact.getStageStatus()));
+                            sopLogs.setIsHaveApp(1);
+                            sopLogs.setAppSendStatus(0);
                             try {
                                 replaceContent(st.getContentType(), st.getValue(), st::setValue, words); // 替换 value
                             } catch (Exception e) {
@@ -1345,6 +1358,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             try {
                                 if (qwUser.getCompanyUserId() != null) {
                                     createVoiceUrlToIm(st, String.valueOf(qwUser.getCompanyUserId()), qwSop);
+                                    sopLogs.setIsHaveApp(1);
+                                    sopLogs.setAppSendStatus(0);
                                 }
                             } catch (Exception e) {
                                 throw new RuntimeException(e);
@@ -1835,6 +1850,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             qwUser.getId(), companyUserId, companyId, item.getExternalId(), config, qwUser.getQwUserName(), contact.getFsUserId());
                     st.setLinkUrl(linkByApp.getSortLink());
                     st.setAppLinkUrl(linkByApp.getAppMsgLink());
+                    sopLogs.setIsHaveApp(1);
+                    sopLogs.setAppSendStatus(0);
                     break;
 
                 //自定义小程序
@@ -1893,6 +1910,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     break;
                 case "15":
                     //app文本
+                    sopLogs.setIsHaveApp(1);
+                    sopLogs.setAppSendStatus(0);
                     try {
                         qwSop = qwSopMapper.selectQwSopById(param.getSopId());
                         createVoiceUrl(st, companyUserId, qwSop);
@@ -1914,6 +1933,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     } catch (Exception e) {
                         throw new RuntimeException(e);
                     }
+                    sopLogs.setIsHaveApp(1);
+                    sopLogs.setAppSendStatus(0);
                     break;
                 case "14":
                     LuckyBag luckyBag = luckyBagMapper.selectLuckyBagById(st.getLuckyBagId());

+ 41 - 5
fs-service/src/main/resources/mapper/sop/QwSopLogsMapper.xml

@@ -29,10 +29,15 @@
         <result property="sort"    column="sort"    />
         <result property="qwUserKey"    column="qw_user_key"    />
         <result property="userLogsId"    column="user_logs_id"    />
+        <result property="isHaveApp"    column="is_have_app"    />
+        <result property="appSendStatus"    column="app_send_status"    />
+        <result property="appSendRemark"    column="app_send_remark"    />
     </resultMap>
 
     <sql id="selectQwSopLogsVo">
-        select id,sop_id,customer_id, qw_userid,corp_id,external_user_id,external_id,external_user_name, log_type, content_json, send_status,receiving_status,msg_id, send_time, real_send_time, company_id, send_type,remark,fs_user_id,take_records from qw_sop_logs
+        select id,sop_id,customer_id, qw_userid,corp_id,external_user_id,external_id,external_user_name, log_type,
+               content_json, send_status,receiving_status,msg_id, send_time, real_send_time, company_id, send_type,
+               remark,fs_user_id,take_records,is_have_app,app_send_status, app_send_remark from qw_sop_logs
     </sql>
 
     <select id="selectQwSopLogsListVO" parameterType="QwSopLogs" resultMap="QwSopLogsResult">
@@ -102,6 +107,9 @@
             <if test="data.sort != null">sort,</if>
             <if test="data.userLogsId != null">user_logs_id,</if>
             <if test="data.qwUserKey != null">qw_user_key,</if>
+            <if test="data.isHaveApp != null">is_have_app,</if>
+            <if test="data.appSendStatus != null">app_send_status,</if>
+            <if test="data.appSendRemark != null">app_send_remark,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="data.qwUserid != null">#{data.qwUserid},</if>
@@ -126,6 +134,9 @@
             <if test="data.sort != null">#{data.sort},</if>
             <if test="data.userLogsId != null">#{data.userLogsId},</if>
             <if test="data.qwUserKey != null">#{data.qwUserKey},</if>
+            <if test="data.isHaveApp != null">#{data.isHaveApp},</if>
+            <if test="data.appSendStatus != null">#{data.appSendStatus},</if>
+            <if test="data.appSendRemark != null">#{data.appSendRemark},</if>
         </trim>
     </insert>
 
@@ -148,6 +159,9 @@
             <if test="data.fsUserId != null ">fs_user_id = #{data.fsUserId},</if>
             <if test="data.sort != null ">sort = #{data.sort},</if>
             <if test="data.qwUserKey != null ">qw_user_key = #{data.qwUserKey},</if>
+            <if test="data.isHaveApp != null and data.isHaveApp!=0">is_have_app = #{data.isHaveApp},</if>
+            <if test="data.appSendStatus != null and data.appSendStatus!=0">app_send_status = #{data.appSendStatus},</if>
+            <if test="data.appSendRemark != null and data.appSendRemark!=''">app_send_remark = #{data.appSendRemark},</if>
         </trim>
         where id = #{data.id}
     </update>
@@ -592,7 +606,7 @@
         qw_userid, external_user_id,external_id, external_user_name, log_type,
         content_json, send_status, send_time, real_send_time, send_type,
         company_id, receiving_status, msg_id, sop_id, remark,
-        corp_id, customer_id,fs_user_id,sort,qw_user_key
+        corp_id, customer_id,fs_user_id,sort,qw_user_key,is_have_app,app_send_status
         )
         VALUES
         <foreach collection="qwSopLogs" item="log" separator=",">
@@ -616,7 +630,9 @@
             #{log.customerId},
             #{log.fsUserId},
             #{log.sort},
-            #{log.qwUserKey}
+            #{log.qwUserKey},
+            #{log.isHaveApp},
+            #{log.appSendStatus}
             )
         </foreach>
     </insert>
@@ -627,7 +643,7 @@
         qw_userid, external_user_id,external_id, external_user_name, log_type,
         content_json, send_status, send_time, real_send_time, send_type,
         company_id, receiving_status, msg_id, sop_id, remark,
-        corp_id, customer_id, fs_user_id, expiration_time,sort,user_logs_id,take_records,qw_user_key
+        corp_id, customer_id, fs_user_id, expiration_time,sort,user_logs_id,take_records,qw_user_key,is_have_app,app_send_status
         )
         VALUES
         <foreach collection="qwSopLogs" item="log" separator=",">
@@ -654,7 +670,9 @@
             #{log.sort},
             #{log.userLogsId},
             #{log.takeRecords},
-            #{log.qwUserKey}
+            #{log.qwUserKey},
+            #{log.isHaveApp},
+            #{log.appSendStatus}
             )
         </foreach>
     </insert>
@@ -863,4 +881,22 @@
         ]]>
         order by ql.sort DESC ,ql.send_time asc limit 30
     </select>
+    <select id="selectAllAppEByQwUserId" resultType="com.fs.sop.domain.QwSopLogs">
+        SELECT
+            ql.*,
+            qs.name,
+            qs.expiry_time AS expiryTime
+        FROM qw_sop_logs ql FORCE INDEX (idx_app_send_force)
+        LEFT JOIN qw_sop qs ON qs.id = ql.sop_id
+        WHERE ql.qw_user_key = #{id}
+          AND ql.log_type = 2
+          AND ql.send_type > 1
+          AND ql.is_have_app = 1
+          AND ql.app_send_status = 0
+        <![CDATA[
+          AND ql.send_time <= now()
+        ]]>
+        ORDER BY ql.sort DESC, ql.send_time ASC
+            LIMIT 800;
+    </select>
 </mapper>