lmx пре 1 недеља
родитељ
комит
ce956ab0f8
20 измењених фајлова са 1475 додато и 28 уклоњено
  1. 35 0
      fs-admin/src/main/java/com/fs/live/controller/LiveController.java
  2. 106 0
      fs-company/src/main/java/com/fs/company/controller/live/LiveWatchLogController.java
  3. 210 6
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  4. 5 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  5. 62 0
      fs-service/src/main/java/com/fs/live/domain/LiveTagConfig.java
  6. 79 0
      fs-service/src/main/java/com/fs/live/domain/LiveWatchLog.java
  7. 61 0
      fs-service/src/main/java/com/fs/live/mapper/LiveTagConfigMapper.java
  8. 74 0
      fs-service/src/main/java/com/fs/live/mapper/LiveWatchLogMapper.java
  9. 59 0
      fs-service/src/main/java/com/fs/live/param/LiveIsAddKfParam.java
  10. 7 0
      fs-service/src/main/java/com/fs/live/service/ILiveService.java
  11. 61 0
      fs-service/src/main/java/com/fs/live/service/ILiveWatchLogService.java
  12. 3 0
      fs-service/src/main/java/com/fs/live/service/ILiveWatchUserService.java
  13. 11 1
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  14. 94 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchLogServiceImpl.java
  15. 184 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java
  16. 102 16
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  17. 4 4
      fs-service/src/main/resources/application-druid-bjzm-test.yml
  18. 111 0
      fs-service/src/main/resources/mapper/live/LiveTagConfigMapper.xml
  19. 195 0
      fs-service/src/main/resources/mapper/live/LiveWatchLogMapper.xml
  20. 12 1
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveWatchUserController.java

+ 35 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveController.java

@@ -9,12 +9,18 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.vo.CompanyVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.hisStore.task.LiveTask;
 import com.fs.hisStore.task.MallStoreTask;
 import com.fs.live.domain.Live;
 import com.fs.live.service.ILiveService;
 import com.fs.live.vo.LiveListVo;
+import com.fs.qw.domain.QwTagGroup;
+import com.fs.qw.service.IQwTagGroupService;
+import com.fs.qw.service.impl.QwUserServiceImpl;
+import com.fs.qw.vo.QwOptionsVO;
+import com.fs.qw.vo.QwTagGroupListVO;
 import com.hc.openapi.tool.fastjson.JSON;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -41,6 +47,12 @@ public class LiveController extends BaseController {
     @Autowired
     private TokenService tokenService;
 
+    @Autowired
+    QwUserServiceImpl qwUserService;
+
+    @Autowired
+    private IQwTagGroupService qwTagGroupService;
+
 
     /**
      * 查询直播列表
@@ -186,4 +198,27 @@ public class LiveController extends BaseController {
     }
 
 
+    /**
+     * 获取公司下拉列表
+     * @return
+     */
+    @GetMapping("/getCompanyDropList")
+    public R getCompanyDropList(){
+        List<CompanyVO> companyDropList = liveService.getCompanyDropList();
+        return R.ok().put("data",companyDropList);
+    }
+
+    @GetMapping("/getQwCorpList/{companyId}")
+    public R getQwCorpList(@PathVariable Long companyId){
+        List<QwOptionsVO> qwOptionsVOS = qwUserService.selectQwCompanyListOptionsVOByCompanyId(companyId);
+        return R.ok().put("data",qwOptionsVOS);
+    }
+
+    @GetMapping("/getTagsListByCorpId")
+    public TableDataInfo getTagsListByCorpId(QwTagGroup qwTagGroup){
+        startPage();
+        List<QwTagGroupListVO> list = qwTagGroupService.selectQwTagGroupListVO(qwTagGroup);
+        return getDataTable(list);
+    }
+
 }

+ 106 - 0
fs-company/src/main/java/com/fs/company/controller/live/LiveWatchLogController.java

@@ -0,0 +1,106 @@
+package com.fs.company.controller.live;
+
+import java.util.List;
+
+import com.fs.common.core.domain.R;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.live.domain.LiveWatchLog;
+import com.fs.live.service.ILiveWatchLogService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 直播看课记录Controller
+ * 
+ * @author fs
+ * @date 2025-12-12
+ */
+@RestController
+@RequestMapping("/live/liveWatchLog")
+public class LiveWatchLogController extends BaseController
+{
+    @Autowired
+    private ILiveWatchLogService liveWatchLogService;
+
+    /**
+     * 查询直播看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveWatchLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(LiveWatchLog liveWatchLog)
+    {
+        startPage();
+        List<LiveWatchLog> list = liveWatchLogService.selectLiveWatchLogList(liveWatchLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出直播看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveWatchLog:export')")
+    @Log(title = "直播看课记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(LiveWatchLog liveWatchLog)
+    {
+        List<LiveWatchLog> list = liveWatchLogService.selectLiveWatchLogList(liveWatchLog);
+        ExcelUtil<LiveWatchLog> util = new ExcelUtil<LiveWatchLog>(LiveWatchLog.class);
+        return util.exportExcel(list, "直播看课记录数据");
+    }
+
+    /**
+     * 获取直播看课记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveWatchLog:query')")
+    @GetMapping(value = "/{logId}")
+    public AjaxResult getInfo(@PathVariable("logId") Long logId)
+    {
+        return AjaxResult.success(liveWatchLogService.selectLiveWatchLogByLogId(logId));
+    }
+
+    /**
+     * 新增直播看课记录
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveWatchLog:add')")
+    @Log(title = "直播看课记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody LiveWatchLog liveWatchLog)
+    {
+        return toAjax(liveWatchLogService.insertLiveWatchLog(liveWatchLog));
+    }
+
+    /**
+     * 修改直播看课记录
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveWatchLog:edit')")
+    @Log(title = "直播看课记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody LiveWatchLog liveWatchLog)
+    {
+        return toAjax(liveWatchLogService.updateLiveWatchLog(liveWatchLog));
+    }
+
+    /**
+     * 删除直播看课记录
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveWatchLog:remove')")
+    @Log(title = "直播看课记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{logIds}")
+    public AjaxResult remove(@PathVariable Long[] logIds)
+    {
+        return toAjax(liveWatchLogService.deleteLiveWatchLogByLogIds(logIds));
+    }
+
+}

+ 210 - 6
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -20,6 +20,8 @@ import com.fs.course.domain.*;
 import com.fs.course.mapper.*;
 import com.fs.course.service.IFsCourseLinkService;
 import com.fs.course.service.IFsUserCompanyBindService;
+import com.fs.live.domain.LiveWatchLog;
+import com.fs.live.mapper.LiveWatchLogMapper;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
@@ -148,12 +150,14 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private final BlockingQueue<FsCourseWatchLog> watchLogsQueue = new LinkedBlockingQueue<>(20000);
     private final BlockingQueue<FsCourseLink> linkQueue = new LinkedBlockingQueue<>(20000);
     private final BlockingQueue<FsCourseSopAppLink> sopAppLinks = new LinkedBlockingQueue<>(20000);
+    private final BlockingQueue<LiveWatchLog> zmLiveWatchQueue = new LinkedBlockingQueue<>(20000);
 
     // Executors for consumer threads
     private ExecutorService qwSopLogsExecutor;
     private ExecutorService watchLogsExecutor;
     private ExecutorService courseLinkExecutor;
     private ExecutorService courseSopAppLinkExecutor;
+    private ExecutorService zmLiveWatchLogExecutor;
     @Autowired
     private IQwGroupChatService qwGroupChatService;
     @Autowired
@@ -184,6 +188,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     @Autowired
     private IQwSopTempVoiceService sopTempVoiceService;
 
+    @Autowired
+    LiveWatchLogMapper liveWatchLogMapper;
+
     @PostConstruct
     public void init() {
         loadCourseConfig();
@@ -230,11 +237,17 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             return t;
         });
 
+        zmLiveWatchLogExecutor = Executors.newSingleThreadExecutor(r -> {
+            Thread t = new Thread(r, "zmLiveWatchLogConsumer");
+            t.setDaemon(true);
+            return t;
+        });
 
         qwSopLogsExecutor.submit(this::consumeQwSopLogs);
         watchLogsExecutor.submit(this::consumeWatchLogs);
         courseLinkExecutor.submit(this::consumeCourseLink);
         courseSopAppLinkExecutor.submit(this::consumeCourseSopAppLink);
+        zmLiveWatchLogExecutor.submit(this::consumeZmLiveWatchQueue);
     }
 
     // Scheduled tasks to refresh configurations and domain names periodically
@@ -265,6 +278,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         watchLogsExecutor.shutdown();
         courseLinkExecutor.shutdown();
         courseSopAppLinkExecutor.shutdown();
+        zmLiveWatchLogExecutor.shutdown();
         try {
             if (!qwSopLogsExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
                 qwSopLogsExecutor.shutdownNow();
@@ -278,11 +292,15 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             if (!courseSopAppLinkExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
                 courseSopAppLinkExecutor.shutdownNow();
             }
+            if (!zmLiveWatchLogExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
+                zmLiveWatchLogExecutor.shutdownNow();
+            }
         } catch (InterruptedException e) {
             qwSopLogsExecutor.shutdownNow();
             watchLogsExecutor.shutdownNow();
             courseLinkExecutor.shutdownNow();
             courseSopAppLinkExecutor.shutdownNow();
+            zmLiveWatchLogExecutor.shutdownNow();
             Thread.currentThread().interrupt();
         }
     }
@@ -873,7 +891,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                                       Integer grade, Integer sendMsgType ,List<Company> companies ) {
         switch (type) {
             case 1:
-                handleNormalMessage(sopLogs, content,companyUserId);
+                handleNormalMessage(sopLogs, content,companyUserId,companyId,isGroupChat,qwUserId,groupChat,externalId,logVo);
                 break;
             case 2:
                 handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
@@ -902,9 +920,73 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         enqueueQwSopLogs(sopLogs);
     }
 
-    private void handleNormalMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,String companyUserId) {
+    private void handleNormalMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content, String companyUserId, String companyId,
+                                     boolean isGroupChat,String qwUserId,QwGroupChat groupChat,String externalId,SopUserLogsVo logVo) {
 
-        sopLogs.setContentJson(JSON.toJSONString(content));
+        // 深拷贝 Content 对象,避免使用 JSON
+        QwSopTempSetting.Content clonedContent = deepCopyContent(content);
+        if (clonedContent == null) {
+            log.error("Failed to clone content, skipping handleCourseMessage.");
+            return;
+        }
+
+        List<QwSopTempSetting.Content.Setting> settings = clonedContent.getSetting();
+        if (settings == null || settings.isEmpty()) {
+            log.error("Cloned content settings are empty, skipping.");
+            return;
+        }
+        // 顺序处理每个 Setting,避免过多的并行导致线程开销
+        for (QwSopTempSetting.Content.Setting setting : settings) {
+            switch (setting.getContentType()) {
+                //直播小程序单独
+                case "12":
+                    String sortLiveLink;
+                    sortLiveLink = "/pages_course/living.html?companyId=" + companyId + "&companyUserId=" + companyUserId + "&liveId=" + setting.getLiveId() + "&corpId=" + logVo.getCorpId()+"&qwUserId=" + qwUserId;
+                    String json = configService.selectConfigByKey("his.config");
+                    FSSysConfig sysConfig = JSON.parseObject(json, FSSysConfig.class);
+                    if (isGroupChat) {
+                        try {
+                            groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
+                                Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
+                                GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
+                                if (vo != null && vo.getId() != null) {
+                                    sopLogs.setFsUserId(vo.getFsUserId());
+                                    //写入直播待看课记录
+                                    createLiveWatchLogAndEnQueue(companyId, companyUserId, vo.getId().toString(), setting.getLiveId(), sysConfig.getAppId(), 2, qwUserId,logVo.getCorpId());
+                                }
+                            });
+                            sortLiveLink += "&chatId=" + groupChat.getChatId();
+                        } catch (Exception e) {
+                            log.error("直播小程序群聊新增报错,{}", e.getMessage(), e);
+                        }
+                    } else {
+                        try {
+                            createLiveWatchLogAndEnQueue(companyId, companyUserId, externalId, setting.getLiveId(), sysConfig.getAppId(), 1, qwUserId,logVo.getCorpId());
+                            sortLiveLink += "&externalId=" + externalId;
+                        } catch (Exception e) {
+                            log.error("直播小程序个人新增报错,{}", e.getMessage(), e);
+                        }
+                    }
+
+                    String miniprogramLiveTitle = setting.getMiniprogramTitle();
+                    int maxLiveLength = 17;
+                    setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
+                    setting.setMiniprogramAppid(sysConfig.getAppId());
+                    setting.setMiniprogramPage(sortLiveLink);
+                    setting.setContentType("4");
+                    try {
+                        setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : setting.getMiniprogramPicUrl());
+                    } catch (Exception e) {
+                        log.error("赋值-小程序封面地址失败-" + e);
+                    }
+
+                    break;
+                default:
+                    break;
+            }
+        }
+        sopLogs.setContentJson(JSON.toJSONString(clonedContent));
+//        sopLogs.setContentJson(JSON.toJSONString(content));
         enqueueQwSopLogs(sopLogs);
     }
 
@@ -1102,14 +1184,37 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 //直播小程序单独
                 case "12":
                     String sortLiveLink;
-                    sortLiveLink = "/pages_course/living?companyId=" + companyId + "&companyUserId=" + companyUserId + "&liveId=" + setting.getLiveId();
+                    sortLiveLink = "/pages_course/living.html?companyId=" + companyId + "&companyUserId=" + companyUserId + "&liveId=" + setting.getLiveId()+"&corpId=" +logVo.getCorpId()+"&qwUserId=" + qwUserId;
+                    String json = configService.selectConfigByKey("his.config");
+                    FSSysConfig sysConfig= JSON.parseObject(json,FSSysConfig.class);
+                    if(isGroupChat){
+                        try{
+                            groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
+                                Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
+                                GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
+                                if (vo != null && vo.getId() != null) {
+                                    sopLogs.setFsUserId(vo.getFsUserId());
+                                    //写入直播待看课记录
+                                    createLiveWatchLogAndEnQueue(companyId,companyUserId,vo.getId().toString(), setting.getLiveId(),sysConfig.getAppId(),2,qwUserId,logVo.getCorpId());
+                                }
+                            });
+                            sortLiveLink += "&chatId=" + groupChat.getChatId();
+                        }catch(Exception e){
+                            log.error("直播小程序群聊新增报错,{}", e.getMessage(),e);
+                        }
+                    }else{
+                        try{
+                            createLiveWatchLogAndEnQueue(companyId,companyUserId,externalId, setting.getLiveId(),sysConfig.getAppId(),2,qwUserId,logVo.getCorpId());
+                            sortLiveLink += "&externalId=" + externalId;
+                        }catch(Exception e){
+                            log.error("直播小程序个人新增报错,{}", e.getMessage(),e);
+                        }
+                    }
 
 
                     String miniprogramLiveTitle = setting.getMiniprogramTitle();
                     int maxLiveLength = 17;
                     setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
-                    String json = configService.selectConfigByKey("his.config");
-                    FSSysConfig sysConfig= JSON.parseObject(json,FSSysConfig.class);
                     setting.setMiniprogramAppid(sysConfig.getAppId());
                     setting.setMiniprogramPage(sortLiveLink);
                     setting.setContentType("4");
@@ -1499,6 +1604,46 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         enqueueWatchLog(watchLog);
     }
 
+    /**
+     * 直播看课记录处理
+     * @param companyId
+     * @param companyUserId
+     * @param externalId
+     * @param liveId
+     * @param appId
+     * @param logSource
+     * @param qwUserId
+     * @param corpId
+     */
+    public void createLiveWatchLogAndEnQueue(String companyId,String companyUserId,String externalId,Long liveId,String appId,Integer logSource,String qwUserId,String corpId){
+        // 写入对应数据源的记录表
+        LiveWatchLog itemLiveWatchLog = new LiveWatchLog();
+        itemLiveWatchLog.setLiveId(liveId);
+        itemLiveWatchLog.setLogType(3);
+        itemLiveWatchLog.setSopCreateTime(new Date());
+        itemLiveWatchLog.setCompanyId(Long.valueOf(companyId));
+        itemLiveWatchLog.setCompanyUserId(Long.valueOf(companyUserId));
+        itemLiveWatchLog.setSendAppId(appId);
+        itemLiveWatchLog.setLogSource(logSource);
+        itemLiveWatchLog.setQwUserId(qwUserId);
+        itemLiveWatchLog.setExternalContactId(Long.valueOf(externalId));
+        itemLiveWatchLog.setCorpId(corpId);
+        enqueueZmLiveWatchLog(itemLiveWatchLog);
+    }
+
+    private void enqueueZmLiveWatchLog(LiveWatchLog liveWatchLog) {
+        try {
+            boolean offered = zmLiveWatchQueue.offer(liveWatchLog, 5, TimeUnit.SECONDS);
+            if (!offered) {
+                log.error("LiveWatchLog 队列已满,无法添加日志: {}", JSON.toJSONString(liveWatchLog));
+                // 处理队列已满的情况,例如记录到失败队列或持久化存储
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.error("插入 LiveWatchLog 队列时被中断: {}", e.getMessage(), e);
+        }
+    }
+
     /**
      * 时间字符串转Date时间
      * @param dateString
@@ -1675,6 +1820,35 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         }
     }
 
+    /**
+     * 消费 FsCourseSopAppLink 队列并进行批量插入
+     */
+    private void consumeZmLiveWatchQueue() {
+        List<LiveWatchLog> batch = new ArrayList<>(BATCH_SIZE);
+        while (running || !zmLiveWatchQueue.isEmpty()) {
+            try {
+                LiveWatchLog livewatchLog = zmLiveWatchQueue.poll(1, TimeUnit.SECONDS);
+                if (livewatchLog != null) {
+                    batch.add(livewatchLog);
+                }
+                if (batch.size() >= BATCH_SIZE || (!batch.isEmpty() && livewatchLog == null)) {
+                    if (!batch.isEmpty()) {
+                        batchInsertLiveWatchLog(new ArrayList<>(batch));
+                        batch.clear();
+                    }
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.error("zmLiveWatchQueue 消费线程被中断: {}", e.getMessage(), e);
+            }
+        }
+
+        // 处理剩余的数据
+        if (!batch.isEmpty()) {
+            batchInsertLiveWatchLog(batch);
+        }
+    }
+
     /**
      * 消费 FsCourseWatchLog 队列并进行批量插入
      */
@@ -1782,6 +1956,36 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         }
     }
 
+    /**
+     * 批量插入 卓美直播看课记录
+     */
+    @Transactional
+    @Retryable(
+            value = {Exception.class},
+            maxAttempts = 3,
+            backoff = @Backoff(delay = 2000)
+    )
+    public void batchInsertLiveWatchLog(List<LiveWatchLog> liveWatchLogToInsert) {
+        try {
+            List<LiveWatchLog> lastInsertList = new ArrayList<>();
+            //判断是否存在数据 liveId + his_qw_external_contact_id 唯一
+            for (LiveWatchLog liveWatchLog : liveWatchLogToInsert) {
+                //判断是否存在数据 存在的数据直接更新发送时间
+                if(liveWatchLogMapper.updateLiveWatchLogCondition(liveWatchLog) > 0){
+                    continue;
+                }
+                lastInsertList.add(liveWatchLog);
+            }
+            if(!lastInsertList.isEmpty()){
+                liveWatchLogMapper.insertLiveWatchLogBatch(lastInsertList);
+            }
+//            log.info("批量插入 LiveWatchLog 完成,共插入 {} 条记录。", liveWatchLogToInsert.size());
+        } catch (Exception e) {
+            log.error("批量插入 LiveWatchLog 失败: {}", e.getMessage(), e);
+            // 可选:将失败的数据记录到失败队列或持久化存储以便后续重试
+        }
+    }
+
 
     @Override
     public void updateSopLogsByCancel() {

+ 5 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java

@@ -245,4 +245,9 @@ public interface CompanyMapper
 
     @Select("select company_id from company where live_show=1")
     List<Long> selectLiveShowCompanyId();
+
+    @Select("select company_id,company_name from company where \n" +
+            " `status` != 0   " +
+            " and is_del != 1 ")
+    List<CompanyVO> getCompanyDropList();
 }

+ 62 - 0
fs-service/src/main/java/com/fs/live/domain/LiveTagConfig.java

@@ -0,0 +1,62 @@
+package com.fs.live.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 直播间标签配置对象 live_tag_config
+ *
+ * @author fs
+ * @date 2025-12-13
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class LiveTagConfig extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 直播间id */
+    @Excel(name = "直播间id")
+    private Long liveId;
+
+    /** 企微主体id */
+    @Excel(name = "企微主体id")
+    private String corpId;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 标记标签行为类型,数据字典live_mark_type */
+    @Excel(name = "标记标签行为类型,数据字典live_mark_type")
+    private Long markType;
+
+    /** 企微标签id */
+    @Excel(name = "企微标签id")
+    private Long qwTagId;
+
+    @Excel(name = "企微标签名称")
+    private String  qwTagName;
+
+    /** 创建人id */
+    @Excel(name = "创建人id")
+    private Long createUserId;
+
+    /** 创建人 */
+    @Excel(name = "创建人")
+    private String createUserName;
+
+    /** 更新人id */
+    @Excel(name = "更新人id")
+    private Long updateUserId;
+
+    /** 更新人 */
+    @Excel(name = "更新人")
+    private String updateUserName;
+
+
+}

+ 79 - 0
fs-service/src/main/java/com/fs/live/domain/LiveWatchLog.java

@@ -0,0 +1,79 @@
+package com.fs.live.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 直播看课记录对象 live_watch_log
+ *
+ * @author fs
+ * @date 2025-12-12
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class LiveWatchLog extends BaseEntity{
+
+    /** 日志id */
+    private Long logId;
+
+    /** 用户userId */
+    @Excel(name = "用户userId")
+    private Long userId;
+
+    /** 直播间id */
+    @Excel(name = "直播间id")
+    private Long liveId;
+
+    /** 记录类型 1看课中 2完课 3待看课 4看课中断 */
+    @Excel(name = "记录类型 1看课中 2完课 3待看课 4看课中断")
+    private Integer logType;
+
+    /** 外部联系人id */
+    @Excel(name = "外部联系人id")
+    private Long externalContactId;
+
+    /** 销售id */
+    @Excel(name = "销售id")
+    private Long companyUserId;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 完课时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "完课时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date finishTime;
+
+    /** sop最后创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "sop最后创建时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date sopCreateTime;
+
+    /** 发送小程序appid */
+    @Excel(name = "发送小程序appid")
+    private String sendAppId;
+
+    /** 日志创建来源:1、个人sop,2、群聊sop,3、一键群发 */
+    @Excel(name = "日志创建来源:1、个人sop,2、群聊sop,3、一键群发")
+    private Integer logSource;
+
+    /** 分享人企微id */
+    @Excel(name = "分享人企微id")
+    private String qwUserId;
+    /**
+     * 查看直播类型:1、直播,2、回放
+     */
+    private Integer watchType;
+
+    /**
+     * 企微主体id
+     */
+    private String corpId;
+
+}

+ 61 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveTagConfigMapper.java

@@ -0,0 +1,61 @@
+package com.fs.live.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.live.domain.LiveTagConfig;
+
+/**
+ * 直播间标签配置Mapper接口
+ * 
+ * @author fs
+ * @date 2025-12-13
+ */
+public interface LiveTagConfigMapper extends BaseMapper<LiveTagConfig>{
+    /**
+     * 查询直播间标签配置
+     * 
+     * @param id 直播间标签配置主键
+     * @return 直播间标签配置
+     */
+    LiveTagConfig selectLiveTagConfigById(Long id);
+
+    /**
+     * 查询直播间标签配置列表
+     * 
+     * @param liveTagConfig 直播间标签配置
+     * @return 直播间标签配置集合
+     */
+    List<LiveTagConfig> selectLiveTagConfigList(LiveTagConfig liveTagConfig);
+
+    /**
+     * 新增直播间标签配置
+     * 
+     * @param liveTagConfig 直播间标签配置
+     * @return 结果
+     */
+    int insertLiveTagConfig(LiveTagConfig liveTagConfig);
+
+    /**
+     * 修改直播间标签配置
+     * 
+     * @param liveTagConfig 直播间标签配置
+     * @return 结果
+     */
+    int updateLiveTagConfig(LiveTagConfig liveTagConfig);
+
+    /**
+     * 删除直播间标签配置
+     * 
+     * @param id 直播间标签配置主键
+     * @return 结果
+     */
+    int deleteLiveTagConfigById(Long id);
+
+    /**
+     * 批量删除直播间标签配置
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteLiveTagConfigByIds(Long[] ids);
+}

+ 74 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveWatchLogMapper.java

@@ -0,0 +1,74 @@
+package com.fs.live.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
+import com.fs.live.domain.LiveWatchLog;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 直播间观看用户Mapper接口
+ * 
+ * @author fs
+ * @date 2025-01-18
+ */
+public interface LiveWatchLogMapper extends BaseMapper<LiveWatchLog> {
+    /**
+     * 查询直播看课记录
+     *
+     * @param logId 直播看课记录主键
+     * @return 直播看课记录
+     */
+    LiveWatchLog selectLiveWatchLogByLogId(Long logId);
+
+    /**
+     * 查询直播看课记录列表
+     *
+     * @param liveWatchLog 直播看课记录
+     * @return 直播看课记录集合
+     */
+    List<LiveWatchLog> selectLiveWatchLogList(LiveWatchLog liveWatchLog);
+
+    /**
+     * 新增直播看课记录
+     *
+     * @param liveWatchLog 直播看课记录
+     * @return 结果
+     */
+    int insertLiveWatchLog(LiveWatchLog liveWatchLog);
+
+    /**
+     * 修改直播看课记录
+     *
+     * @param liveWatchLog 直播看课记录
+     * @return 结果
+     */
+    int updateLiveWatchLog(LiveWatchLog liveWatchLog);
+
+    /**
+     * 删除直播看课记录
+     *
+     * @param logId 直播看课记录主键
+     * @return 结果
+     */
+    int deleteLiveWatchLogByLogId(Long logId);
+
+    /**
+     * 批量删除直播看课记录
+     *
+     * @param logIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteLiveWatchLogByLogIds(Long[] logIds);
+
+    void insertLiveWatchLogBatch(@Param("liveWatchLogs")List<LiveWatchLog> liveWatchLogs);
+
+    int updateLiveWatchLogCondition(@Param("liveWatchLog") LiveWatchLog liveWatchLog);
+
+    LiveWatchLog selectOneLogByLiveIdAndQwUserIdAndExternalId(@Param("liveId")Long liveId,@Param("qwUserId")String qwUserId,@Param("externalContactId")Long externalContactId);
+
+
+
+}

+ 59 - 0
fs-service/src/main/java/com/fs/live/param/LiveIsAddKfParam.java

@@ -0,0 +1,59 @@
+package com.fs.live.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * @author MixLiu
+ * @date 2025/12/12 下午1:32)
+ */
+
+@Data
+public class LiveIsAddKfParam implements Serializable {
+
+    /**
+     * 企微员工 id
+     */
+    @NotNull(message = "企微userId")
+    private String qwUserId;
+
+    /**
+     * 直播id
+     */
+    @NotNull(message = "直播id")
+    private Long liveId;
+
+    /**
+     * 登录的小程序id
+     */
+    private Long userId;
+
+    /**
+     * 企微主体id
+     */
+    private String corpId;
+
+    /**
+     *   companyUserId
+     */
+    @NotNull(message = "客服参数不能为空")
+    private Long companyUserId;
+
+    /**
+     * 公司id
+     */
+    @NotNull(message = "经销商参数参数不能为空")
+    private Long companyId;
+
+    /**
+     * 外部联系人id
+     */
+    private Long qwExternalId;
+
+    /**
+     * 群聊id
+     */
+    private String chatId;
+}

+ 7 - 0
fs-service/src/main/java/com/fs/live/service/ILiveService.java

@@ -2,6 +2,7 @@ package com.fs.live.service;
 
 
 import com.fs.common.core.page.PageRequest;
+import com.fs.company.vo.CompanyVO;
 import com.fs.live.param.LiveNotifyParam;
 import com.fs.live.vo.LiveVo;
 import com.fs.common.core.domain.R;
@@ -197,4 +198,10 @@ public interface ILiveService
     List<Live> listToLiveNoEnd(Live live);
 
     Live selectLiveDbByLiveId(Long liveId);
+
+    /**
+     * 获取公司下拉列表
+     * @return
+     */
+    List<CompanyVO> getCompanyDropList();
 }

+ 61 - 0
fs-service/src/main/java/com/fs/live/service/ILiveWatchLogService.java

@@ -0,0 +1,61 @@
+package com.fs.live.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.live.domain.LiveWatchLog;
+
+/**
+ * 直播看课记录Service接口
+ * 
+ * @author fs
+ * @date 2025-12-12
+ */
+public interface ILiveWatchLogService extends IService<LiveWatchLog>{
+    /**
+     * 查询直播看课记录
+     * 
+     * @param logId 直播看课记录主键
+     * @return 直播看课记录
+     */
+    LiveWatchLog selectLiveWatchLogByLogId(Long logId);
+
+    /**
+     * 查询直播看课记录列表
+     * 
+     * @param liveWatchLog 直播看课记录
+     * @return 直播看课记录集合
+     */
+    List<LiveWatchLog> selectLiveWatchLogList(LiveWatchLog liveWatchLog);
+
+    /**
+     * 新增直播看课记录
+     * 
+     * @param liveWatchLog 直播看课记录
+     * @return 结果
+     */
+    int insertLiveWatchLog(LiveWatchLog liveWatchLog);
+
+    /**
+     * 修改直播看课记录
+     * 
+     * @param liveWatchLog 直播看课记录
+     * @return 结果
+     */
+    int updateLiveWatchLog(LiveWatchLog liveWatchLog);
+
+    /**
+     * 批量删除直播看课记录
+     * 
+     * @param logIds 需要删除的直播看课记录主键集合
+     * @return 结果
+     */
+    int deleteLiveWatchLogByLogIds(Long[] logIds);
+
+    /**
+     * 删除直播看课记录信息
+     * 
+     * @param logId 直播看课记录主键
+     * @return 结果
+     */
+    int deleteLiveWatchLogByLogId(Long logId);
+}

+ 3 - 0
fs-service/src/main/java/com/fs/live/service/ILiveWatchUserService.java

@@ -4,6 +4,7 @@ package com.fs.live.service;
 import com.fs.common.core.domain.R;
 import com.fs.hisStore.domain.FsUserScrm;
 import com.fs.live.domain.LiveWatchUser;
+import com.fs.live.param.LiveIsAddKfParam;
 import com.fs.live.vo.LiveWatchUserVO;
 
 import java.util.Date;
@@ -126,4 +127,6 @@ public interface ILiveWatchUserService {
     void updateSingleVisible(long liveId, Integer status,long userId);
 
     LiveWatchUser selectLiveWatchUserByFlag(Long liveId, Long userId, Integer liveFlag, Integer replayFlag);
+
+    R liveIsAddKf(LiveIsAddKfParam param);
 }

+ 11 - 1
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.core.page.PageRequest;
 import com.fs.common.exception.base.BaseException;
 import com.fs.company.mapper.CompanyMapper;
+import com.fs.company.vo.CompanyVO;
 import com.fs.core.config.WxMaConfiguration;
 import com.fs.his.domain.FsStoreProduct;
 import com.fs.his.domain.FsUser;
@@ -150,6 +151,15 @@ public class LiveServiceImpl implements ILiveService
         return byId;
     }
 
+    /**
+     * 获取公司下拉列表
+     * @return
+     */
+    @Override
+    public  List<CompanyVO> getCompanyDropList(){
+       return  companyMapper.getCompanyDropList();
+    }
+
 
     /**
      * 查询直播
@@ -282,7 +292,7 @@ public class LiveServiceImpl implements ILiveService
     @Override
     public R subNotifyLive(LiveNotifyParam param) {
         LiveMiniprogramSubNotifyTask notifyTask = new LiveMiniprogramSubNotifyTask();
-        notifyTask.setPage("/pages_course/living?liveId=" + param.getLiveId());
+        notifyTask.setPage("/pages_course/living.html?liveId=" + param.getLiveId());
         notifyTask.setTaskName("直播间预约提醒");
         notifyTask.setTemplateId(param.getTemplateId());
         Long userId = param.getUserId();

+ 94 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveWatchLogServiceImpl.java

@@ -0,0 +1,94 @@
+package com.fs.live.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.live.mapper.LiveWatchLogMapper;
+import com.fs.live.domain.LiveWatchLog;
+import com.fs.live.service.ILiveWatchLogService;
+
+/**
+ * 直播看课记录Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-12-12
+ */
+@Service
+public class LiveWatchLogServiceImpl extends ServiceImpl<LiveWatchLogMapper, LiveWatchLog> implements ILiveWatchLogService {
+
+    /**
+     * 查询直播看课记录
+     * 
+     * @param logId 直播看课记录主键
+     * @return 直播看课记录
+     */
+    @Override
+    public LiveWatchLog selectLiveWatchLogByLogId(Long logId)
+    {
+        return baseMapper.selectLiveWatchLogByLogId(logId);
+    }
+
+    /**
+     * 查询直播看课记录列表
+     * 
+     * @param liveWatchLog 直播看课记录
+     * @return 直播看课记录
+     */
+    @Override
+    public List<LiveWatchLog> selectLiveWatchLogList(LiveWatchLog liveWatchLog)
+    {
+        return baseMapper.selectLiveWatchLogList(liveWatchLog);
+    }
+
+    /**
+     * 新增直播看课记录
+     * 
+     * @param liveWatchLog 直播看课记录
+     * @return 结果
+     */
+    @Override
+    public int insertLiveWatchLog(LiveWatchLog liveWatchLog)
+    {
+        liveWatchLog.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertLiveWatchLog(liveWatchLog);
+    }
+
+    /**
+     * 修改直播看课记录
+     * 
+     * @param liveWatchLog 直播看课记录
+     * @return 结果
+     */
+    @Override
+    public int updateLiveWatchLog(LiveWatchLog liveWatchLog)
+    {
+        liveWatchLog.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateLiveWatchLog(liveWatchLog);
+    }
+
+    /**
+     * 批量删除直播看课记录
+     * 
+     * @param logIds 需要删除的直播看课记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteLiveWatchLogByLogIds(Long[] logIds)
+    {
+        return baseMapper.deleteLiveWatchLogByLogIds(logIds);
+    }
+
+    /**
+     * 删除直播看课记录信息
+     * 
+     * @param logId 直播看课记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteLiveWatchLogByLogId(Long logId)
+    {
+        return baseMapper.deleteLiveWatchLogByLogId(logId);
+    }
+}

+ 184 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java

@@ -5,25 +5,41 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.thread.ThreadUtil;
 import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.constant.LiveKeysConstant;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
+import com.fs.course.service.impl.FsUserCourseVideoServiceImpl;
 import com.fs.his.domain.FsUser;
+import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.service.IFsUserService;
 import com.fs.hisStore.domain.FsUserScrm;
 import com.fs.hisStore.service.IFsUserScrmService;
 import com.fs.live.domain.Live;
 import com.fs.live.domain.LiveVideo;
+import com.fs.live.domain.LiveWatchLog;
 import com.fs.live.domain.LiveWatchUser;
+import com.fs.live.mapper.LiveWatchLogMapper;
 import com.fs.live.mapper.LiveWatchUserMapper;
 import com.fs.live.mapper.LiveMapper;
 import com.fs.live.mapper.LiveVideoMapper;
+import com.fs.live.param.LiveIsAddKfParam;
 import com.fs.live.service.ILiveWatchUserService;
 import com.fs.live.vo.LiveWatchUserStatistics;
 import com.fs.live.vo.LiveWatchUserVO;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwGroupChat;
+import com.fs.qw.domain.QwGroupChatUser;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.mapper.QwGroupChatMapper;
+import com.fs.qw.mapper.QwGroupChatUserMapper;
+import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.service.ISopUserLogsInfoService;
 import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -52,7 +68,20 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
     private LiveMapper liveMapper;
     @Autowired
     private LiveVideoMapper liveVideoMapper;
+    @Autowired
+    private FsUserMapper fsUserMapper;
+    @Autowired
+    private QwGroupChatMapper qwGroupChatMapper;
+    @Autowired
+    private QwGroupChatUserMapper qwGroupChatUserMapper;
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+    @Autowired
+    private LiveWatchLogMapper liveWatchLogMapper;
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
 
+    private static final Logger logger = LoggerFactory.getLogger(LiveWatchUserServiceImpl.class);
 
     /**
      * 查询直播间观看用户
@@ -507,4 +536,159 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
         return baseMapper.selectByUniqueIndex(liveId, userId, liveFlag, replayFlag);
     }
 
+    /**
+     * 直播链接打开判定是否添加客户
+     * @param param
+     * @return
+     */
+    @Override
+    public R liveIsAddKf(LiveIsAddKfParam param) {
+
+        logger.info("【直播判断添加客服】:{}", param);
+        //查询用户
+        FsUser fsUser = fsUserMapper.selectFsUserByUserId(param.getUserId());
+        //用户不存在唤起重新授权
+        if (fsUser == null) {
+            return R.error(401, "未授权");
+        }
+        if (fsUser.getStatus() == 0) {
+            return R.error("会员被停用,无权限,请联系客服!");
+        }
+        //未注册提示
+        String noRegisterMsg = "由于您还未完成注册,请联系伴学助手完成注册即可观看!";
+        //非独属链接提示
+        String noMemberMsg = "此链接已被绑定,请联系伴学助手领取您的专属链接,专属链接请勿分享哦!";
+
+        if (StringUtils.isNotBlank(param.getChatId())) {
+            return handleLiveChat(param,fsUser, noMemberMsg, noRegisterMsg);
+        } else if (null != param.getQwExternalId()) {
+            return handleLivePerson(param, noMemberMsg, noRegisterMsg);
+        } else {
+            return R.error("直播参数错误");
+        }
+
+    }
+
+    /**
+     * 处理发送群聊逻辑
+     * @param param
+     * @param user
+     * @param noMemberMsg
+     * @param noRegisterMsg
+     * @return
+     */
+    public R handleLiveChat(LiveIsAddKfParam param,FsUser user, String noMemberMsg,String noRegisterMsg){
+
+        QwGroupChat qwGroupChat = qwGroupChatMapper.selectQwGroupChatByChatId(param.getChatId());
+        if (qwGroupChat == null) {
+            return R.error("直播群参数异常");
+        }
+        SopUserLogsInfo sopUserLogsInfo = new SopUserLogsInfo();
+        sopUserLogsInfo.setChatId(param.getChatId());
+        List<QwGroupChatUser> qwGroupChatUsers = qwGroupChatUserMapper.selectByChatId(sopUserLogsInfo);
+        if (qwGroupChatUsers == null || qwGroupChatUsers.isEmpty()) {
+            return R.error("直播群参数异常");
+        }
+
+        QwExternalContact qwExternalContact = null;
+        if (null != param.getUserId() && null == qwExternalContact) {
+            try {
+                qwExternalContact = qwExternalContactMapper.selectOne(new QueryWrapper<QwExternalContact>()
+                        .eq("user_id", qwGroupChat.getOwner())
+                        .eq("fs_user_id", param.getUserId())
+                        .eq("corp_id", param.getCorpId())
+                        .eq("status", 0));
+            } catch (Exception e) {
+                log.error("直播群聊用户id匹配异常,参数user_id:{},fs_user_id:{},corp_id:{}", qwGroupChat.getOwner(), param.getUserId(), param.getCorpId(), e);
+            }
+        }
+        if (StringUtils.isNotBlank(param.getChatId()) && null == qwExternalContact) {
+            List<QwExternalContact> groupChatUserByChatIdAndUserName = qwExternalContactMapper.getGroupChatUserByChatIdAndUserName(qwGroupChat.getOwner(), user.getNickName(), param.getCorpId(), param.getChatId());
+            log.info("直播群聊用户查询结果,参数user_id:{},name:{},corp_id:{},chatId:{},groupChatUserByChatIdAndUserName:{}", qwGroupChat.getOwner(), user.getNickName(), param.getCorpId(), param.getChatId(), groupChatUserByChatIdAndUserName);
+            //没找到用户 || 找到的用户数量大于1 使用userid查询匹配
+            if (null == groupChatUserByChatIdAndUserName || groupChatUserByChatIdAndUserName.isEmpty() || groupChatUserByChatIdAndUserName.size() > 1) {
+                log.error("直播群聊用户昵称匹配异常,参数user_id:{},name:{},corp_id:{},chatId:{}", qwGroupChat.getOwner(), user.getNickName(), param.getCorpId(), param.getChatId());
+            } else {
+                qwExternalContact = groupChatUserByChatIdAndUserName.get(0);
+            }
+        }
+        if(qwExternalContact==null){
+            return R.error(noRegisterMsg);
+        }
+        QwExternalContact finalQwExternalContact = qwExternalContact;
+        if (qwGroupChatUsers.stream().noneMatch(e -> e.getUserId().equals(finalQwExternalContact.getExternalUserId()))) {
+            log.error("直播客户不在群:{},里面:{}", qwGroupChat.getChatId(), qwExternalContact.getExternalUserId());
+            return R.error(noRegisterMsg);
+        }
+        Long qwExternalId = qwExternalContact.getId();
+
+        LiveWatchLog liveWatchLog = liveWatchLogMapper.selectOneLogByLiveIdAndQwUserIdAndExternalId(param.getLiveId(), param.getQwUserId(),qwExternalId);
+        if (liveWatchLog==null ){
+            return R.error(noRegisterMsg);
+        }
+        //判断外部联系人有没有绑定userId
+        if (qwExternalContact.getFsUserId() != null) {
+            //有客户有小程序id  但 登录的小程序id和根据外部联系人id查出来的小程序id不一致
+            if (!qwExternalContact.getFsUserId().equals(param.getUserId())) {
+                return R.error(noRegisterMsg);
+            }
+            List<QwExternalContact> qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByMiniUserId(param.getUserId());
+            //匹配客户公司id
+            if (qwExternalContacts.stream().noneMatch(contact -> contact.getCorpId().equals(param.getCorpId()))){
+                return R.error(noRegisterMsg);
+            }
+
+            //看课记录中userId为0绑定userId
+            if (liveWatchLog.getUserId() == null || liveWatchLog.getUserId().equals(0L) || !liveWatchLog.getUserId().equals(param.getUserId())) {
+                liveWatchLog.setUserId(param.getUserId());
+            }
+
+            liveWatchLog.setUpdateTime(new Date());
+
+            liveWatchLogMapper.updateLiveWatchLog(liveWatchLog);
+            iSopUserLogsInfoService.updateSopUserInfoByExternalId(qwExternalId, param.getUserId());
+
+
+        } else {
+            //没绑定fsUser直接绑定fsUser
+            QwExternalContact contact = new QwExternalContact();
+            contact.setId(qwExternalId);
+            contact.setFsUserId(param.getUserId());
+            qwExternalContactMapper.updateQwExternalContact(contact);
+            iSopUserLogsInfoService.updateSopUserInfoByExternalId(qwExternalId, param.getUserId());
+
+            FsUser fsUser = new FsUser();
+            fsUser.setUserId(user.getUserId());
+            fsUser.setIsAddQw(1);
+            fsUserMapper.updateFsUser(fsUser);
+            //绑定上之后 更新观看记录
+            //看课记录中userId为0绑定userId
+            liveWatchLog.setUserId(param.getUserId());
+            liveWatchLog.setUpdateTime(new Date());
+            liveWatchLogMapper.updateLiveWatchLog(liveWatchLog);
+        }
+
+        return R.ok();
+    }
+
+    /**
+     * 处理发送个人逻辑
+     * @param param
+     * @param noMemberMsg
+     * @param noRegisterMsg
+     * @return
+     */
+    public R handleLivePerson(LiveIsAddKfParam param,String noMemberMsg,String noRegisterMsg){
+
+        Long qwExternalId = param.getQwExternalId();
+
+        LiveWatchLog liveWatchLog = liveWatchLogMapper.selectOneLogByLiveIdAndQwUserIdAndExternalId(param.getLiveId(), param.getQwUserId(),qwExternalId);
+
+        if (liveWatchLog==null ){
+            return R.error(noMemberMsg);
+        }
+        return R.ok();
+
+    }
+
 }

+ 102 - 16
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -30,6 +30,8 @@ import com.fs.course.service.IFsCourseLinkService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.fastGpt.domain.FastGptChatReplaceWords;
 import com.fs.fastGpt.mapper.FastGptChatReplaceWordsMapper;
+import com.fs.live.domain.LiveWatchLog;
+import com.fs.live.mapper.LiveWatchLogMapper;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
@@ -170,6 +172,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     @Autowired
     private IQwSopTempVoiceService sopTempVoiceService;
 
+    @Autowired
+    LiveWatchLogMapper liveWatchLogMapper;
+
 
 
     @Override
@@ -511,20 +516,25 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     e.setUserList(userMap.getOrDefault(e.getUserId(), Collections.emptyList()));
                 });
             }
-            try {
-                groupList.forEach(groupChat -> {
-                    QwUser qwUser = qwUserMapper.selectQwUserByIdByWeComeText2(groupChat.getOwner(), groupChat.getCorpId());
-                    groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
-                        Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
-                        GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
-                        if (vo != null && vo.getId() != null) {
-                            addWatchLogIfNeeded(param.getSopId(), param.getVideoId(), param.getCourseId(), vo.getFsUserId(), qwUser.getId().toString(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), vo.getId(), param.getStartTime(), createTime);
-                        }
+
+            //没有传值课程和课节 是直播的数据
+            if(null != param.getCourseId() && null !=param.getVideoId()){
+                try {
+                    groupList.forEach(groupChat -> {
+                        QwUser qwUser = qwUserMapper.selectQwUserByIdByWeComeText2(groupChat.getOwner(), groupChat.getCorpId());
+                        groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
+                            Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
+                            GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
+                            if (vo != null && vo.getId() != null) {
+                                addWatchLogIfNeeded(param.getSopId(), param.getVideoId(), param.getCourseId(), vo.getFsUserId(), qwUser.getId().toString(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), vo.getId(), param.getStartTime(), createTime);
+                            }
+                        });
                     });
-                });
-            } catch (Exception e) {
-                log.error("群聊创建看课记录失败!", e);
+                } catch (Exception e) {
+                    log.error("群聊创建看课记录失败!", e);
+                }
             }
+
             if (param.getSendType() != null && param.getSendType() == 2) {
                 sopLogsList = groupUserList.stream().map(groupUser -> {
                     QwGroupChat qwGroupChat = groupMap.get(groupUser.getChatId());
@@ -666,10 +676,19 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 break;
                             //直播小程序单独
                             case "12":
-                                String sortLiveLink = "/pages_course/living?companyId=" + qwUser.getCompanyUserId() + "&companyUserId=" + companyUserId + "&liveId=" + st.getLiveId();
+                                String sortLiveLink = "/pages_course/living.html?companyId=" + qwUser.getCompanyUserId() + "&companyUserId=" + companyUserId + "&liveId=" + st.getLiveId() + "&corpId=" + param.getCorpId()+"&qwUserId=" + qwUser.getId() +"&externalId=" + vo.getId().toString();
                                 st.setContentType("4");
                                 String js = configService.selectConfigByKey("his.config");
                                 FSSysConfig sysConfig= JSON.parseObject(js,FSSysConfig.class);
+                                //todo 发个人看课记录处理
+                                try {
+                                    if (vo != null && vo.getId() != null) {
+                                        createLiveWatchLogAndInsert(qwUser.getCompanyId().toString(), qwUser.getCompanyUserId().toString(),vo.getId().toString(),Long.valueOf(st.getLiveId()),sysConfig.getAppId(),2, qwUser.getId().toString(),param.getCorpId());
+                                    }
+                                } catch (Exception e) {
+                                    log.error("群聊创建直播看课记录失败!", e);
+                                }
+//                                createLiveWatchLogAndInsert();
                                 st.setMiniprogramAppid(sysConfig.getAppId());
                                 st.setMiniprogramPage(sortLiveLink);
                                 break;
@@ -809,10 +828,25 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 break;
                             //直播小程序单独
                             case "12":
-                                String sortLiveLink = "/pages_course/living?companyId=" + qwUser.getCompanyUserId() + "&companyUserId=" + qwUser.getCompanyUserId() + "&liveId=" + st.getLiveId();
+                                String sortLiveLink = "/pages_course/living.html?companyId=" + qwUser.getCompanyUserId() + "&companyUserId=" + qwUser.getCompanyUserId() + "&liveId=" + st.getLiveId() + "&corpId=" +param.getCorpId()+"&qwUserId=" + qwUser.getId() + "&chatId=" + groupChat.getChatId();
                                 st.setContentType("4");
                                 String js = configService.selectConfigByKey("his.config");
                                 FSSysConfig sysConfig= JSON.parseObject(js,FSSysConfig.class);
+                                //发群处理看课记录
+//                                createLiveWatchLogAndInsert();
+                                try {
+                                    groupList.forEach(gc -> {
+                                        gc.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
+                                            Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
+                                            GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
+                                            if (vo != null && vo.getId() != null) {
+                                                createLiveWatchLogAndInsert(qwUser.getCompanyId().toString(), qwUser.getCompanyUserId().toString(),vo.getId().toString(),Long.valueOf(st.getLiveId()),sysConfig.getAppId(),2, qwUser.getId().toString(),param.getCorpId());
+                                            }
+                                        });
+                                    });
+                                } catch (Exception e) {
+                                    log.error("群聊创建直播看课记录失败!", e);
+                                }
                                 st.setMiniprogramAppid(sysConfig.getAppId());
                                 st.setMiniprogramPage(sortLiveLink);
                                 break;
@@ -1013,10 +1047,17 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             break;
                         //直播小程序单独
                         case "12":
-                            String sortLiveLink = "/pages_course/living?companyId=" + qwUser.getCompanyUserId() + "&companyUserId=" + qwUser.getCompanyUserId() + "&liveId=" + st.getLiveId();
+                            String sortLiveLink = "/pages_course/living.html?companyId=" + qwUser.getCompanyUserId() + "&companyUserId=" + qwUser.getCompanyUserId() + "&liveId=" + st.getLiveId() + "&corpId=" + param.getCorpId()+"&qwUserId=" + qwUserId +"&externalId=" + item.getExternalId().toString();
                             st.setContentType("4");
                             String js = configService.selectConfigByKey("his.config");
                             FSSysConfig sysConfig= JSON.parseObject(js,FSSysConfig.class);
+                            //todo 发个人看课记录处理
+                            try {
+                                    createLiveWatchLogAndInsert(qwUser.getCompanyId().toString(), qwUser.getCompanyUserId().toString(),item.getExternalId().toString(),Long.valueOf(st.getLiveId()),sysConfig.getAppId(),2, qwUserId,param.getCorpId());
+
+                            } catch (Exception e) {
+                                log.error("群聊创建直播看课记录失败!", e);
+                            }
                             st.setMiniprogramAppid(sysConfig.getAppId());
                             st.setMiniprogramPage(sortLiveLink);
                             break;
@@ -1474,14 +1515,21 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 //直播小程序单独
                 case "12":
                     String sortLiveLink;
-                    sortLiveLink = "/pages_course/living?companyId=" + companyId + "&companyUserId=" + companyUserId + "&liveId=" + st.getLiveId();
+                    sortLiveLink = "/pages_course/living.html?companyId=" + companyId + "&companyUserId=" + companyUserId + "&liveId=" + st.getLiveId() + "&corpId=" + param.getCorpId()+"&qwUserId=" + qwUser.getId() +"&externalId=" + item.getExternalId().toString();
 
 
                     String miniprogramLiveTitle = st.getMiniprogramTitle();
                     int maxLiveLength = 17;
                     st.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
+
                     String json = configService.selectConfigByKey("his.config");
                     FSSysConfig sysConfig= JSON.parseObject(json,FSSysConfig.class);
+                    //todo 发个人看课记录处理
+                    try {
+                        createLiveWatchLogAndInsert(qwUser.getCompanyId().toString(), qwUser.getCompanyUserId().toString(),item.getExternalId().toString(),Long.valueOf(st.getLiveId()),sysConfig.getAppId(),2, String.valueOf(qwUser.getId()),param.getCorpId());
+                    } catch (Exception e) {
+                        log.error("群聊创建直播看课记录失败!", e);
+                    }
                     st.setMiniprogramAppid(sysConfig.getAppId());
                     st.setMiniprogramPage(sortLiveLink);
                     st.setContentType("4");
@@ -1741,5 +1789,43 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
     }
 
+    /**
+     * 直播看课记录处理
+     * @param companyId
+     * @param companyUserId
+     * @param externalId
+     * @param liveId
+     * @param appId
+     * @param logSource
+     * @param qwUserId
+     * @param corpId
+     */
+    public void createLiveWatchLogAndInsert(String companyId,String companyUserId,String externalId,Long liveId,String appId,Integer logSource,String qwUserId,String corpId){
+        try{
+            // 写入对应数据源的记录表
+            LiveWatchLog itemLiveWatchLog = new LiveWatchLog();
+            itemLiveWatchLog.setLiveId(liveId);
+            itemLiveWatchLog.setLogType(3);
+            itemLiveWatchLog.setSopCreateTime(new Date());
+            itemLiveWatchLog.setCompanyId(Long.valueOf(companyId));
+            itemLiveWatchLog.setCompanyUserId(Long.valueOf(companyUserId));
+            itemLiveWatchLog.setSendAppId(appId);
+            itemLiveWatchLog.setLogSource(logSource);
+            itemLiveWatchLog.setQwUserId(qwUserId);
+            itemLiveWatchLog.setExternalContactId(Long.valueOf(externalId));
+            itemLiveWatchLog.setCorpId(corpId);
+            if(liveWatchLogMapper.updateLiveWatchLogCondition(itemLiveWatchLog) > 0){
+
+            }else{
+                List<LiveWatchLog> handleList= new ArrayList<>();
+                handleList.add(itemLiveWatchLog);
+                liveWatchLogMapper.insertLiveWatchLogBatch(handleList);
+            }
+        }catch(Exception e){
+            log.error("创建直播看课记录失败:{}",e.getMessage());
+        }
+
+
+    }
 
 }

+ 4 - 4
fs-service/src/main/resources/application-druid-bjzm-test.yml

@@ -45,10 +45,10 @@ spring:
                 # 从库数据源
                 slave:
                     # 从数据源开关/默认关闭
-                    enabled: false
-                    url:
-                    username:
-                    password:
+                    enabled: true
+                    url: jdbc:mysql://gz-cdb-ofgnuz1n.sql.tencentcdb.com:26872/fs_his?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_1q2w3e4r5t6y
                 # 初始连接数
                 initialSize: 5
                 # 最小连接池数量

+ 111 - 0
fs-service/src/main/resources/mapper/live/LiveTagConfigMapper.xml

@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.live.mapper.LiveTagConfigMapper">
+    
+    <resultMap type="LiveTagConfig" id="LiveTagConfigResult">
+        <result property="id"    column="id"    />
+        <result property="liveId"    column="live_id"    />
+        <result property="corpId"    column="corp_id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="markType"    column="mark_type"    />
+        <result property="qwTagId"    column="qw_tag_id"    />
+        <result property="qwTagName"    column="qw_tag_name"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="createUserId"    column="create_user_id"    />
+        <result property="createUserName"    column="create_user_name"    />
+        <result property="updateUserId"    column="update_user_id"    />
+        <result property="updateUserName"    column="update_user_name"    />
+    </resultMap>
+
+    <sql id="selectLiveTagConfigVo">
+        select id, live_id, corp_id, company_id, mark_type, qw_tag_id, qw_tag_name,create_time, update_time, create_user_id, create_user_name, update_user_id, update_user_name from live_tag_config
+    </sql>
+
+    <select id="selectLiveTagConfigList" parameterType="LiveTagConfig" resultMap="LiveTagConfigResult">
+        <include refid="selectLiveTagConfigVo"/>
+        <where>  
+            <if test="liveId != null "> and live_id = #{liveId}</if>
+            <if test="corpId != null  and corpId != ''"> and corp_id = #{corpId}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="markType != null "> and mark_type = #{markType}</if>
+            <if test="qwTagId != null "> and qw_tag_id = #{qwTagId}</if>
+            <if test="qwTagName != null "> and qw_tag_name = #{qwTagName}</if>
+            <if test="createUserId != null "> and create_user_id = #{createUserId}</if>
+            <if test="createUserName != null  and createUserName != ''"> and create_user_name like concat('%', #{createUserName}, '%')</if>
+            <if test="updateUserId != null "> and update_user_id = #{updateUserId}</if>
+            <if test="updateUserName != null  and updateUserName != ''"> and update_user_name like concat('%', #{updateUserName}, '%')</if>
+        </where>
+    </select>
+    
+    <select id="selectLiveTagConfigById" parameterType="Long" resultMap="LiveTagConfigResult">
+        <include refid="selectLiveTagConfigVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertLiveTagConfig" parameterType="LiveTagConfig">
+        insert into live_tag_config
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="liveId != null">live_id,</if>
+            <if test="corpId != null">corp_id,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="markType != null">mark_type,</if>
+            <if test="qwTagId != null">qw_tag_id,</if>
+            <if test="qwTagName != null">qw_tag_name,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="createUserId != null">create_user_id,</if>
+            <if test="createUserName != null">create_user_name,</if>
+            <if test="updateUserId != null">update_user_id,</if>
+            <if test="updateUserName != null">update_user_name,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="liveId != null">#{liveId},</if>
+            <if test="corpId != null">#{corpId},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="markType != null">#{markType},</if>
+            <if test="qwTagId != null">#{qwTagId},</if>
+            <if test="qwTagName != null">#{qwTagName},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="createUserId != null">#{createUserId},</if>
+            <if test="createUserName != null">#{createUserName},</if>
+            <if test="updateUserId != null">#{updateUserId},</if>
+            <if test="updateUserName != null">#{updateUserName},</if>
+         </trim>
+    </insert>
+
+    <update id="updateLiveTagConfig" parameterType="LiveTagConfig">
+        update live_tag_config
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="liveId != null">live_id = #{liveId},</if>
+            <if test="corpId != null">corp_id = #{corpId},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="markType != null">mark_type = #{markType},</if>
+            <if test="qwTagId != null">qw_tag_id = #{qwTagId},</if>
+            <if test="qwTagName != null">qw_tag_name = #{qwTagName},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="createUserId != null">create_user_id = #{createUserId},</if>
+            <if test="createUserName != null">create_user_name = #{createUserName},</if>
+            <if test="updateUserId != null">update_user_id = #{updateUserId},</if>
+            <if test="updateUserName != null">update_user_name = #{updateUserName},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteLiveTagConfigById" parameterType="Long">
+        delete from live_tag_config where id = #{id}
+    </delete>
+
+    <delete id="deleteLiveTagConfigByIds" parameterType="String">
+        delete from live_tag_config where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 195 - 0
fs-service/src/main/resources/mapper/live/LiveWatchLogMapper.xml

@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.live.mapper.LiveWatchLogMapper">
+
+
+    <resultMap type="LiveWatchLog" id="LiveWatchLogResult">
+        <result property="logId"    column="log_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="liveId"    column="live_id"    />
+        <result property="logType"    column="log_type"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="externalContactId"    column="external_contact_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="finishTime"    column="finish_time"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="sopCreateTime"    column="sop_create_time"    />
+        <result property="sendAppId"    column="send_app_id"    />
+        <result property="logSource"    column="log_source"    />
+        <result property="qwUserId"    column="qw_user_id"    />
+        <result property="watchType"    column="watch_type"    />
+        <result property="corpId"    column="corp_id"    />
+    </resultMap>
+
+    <sql id="selectLiveWatchLogVo">
+        select log_id, user_id, live_id, log_type, create_time, update_time, external_contact_id, company_user_id, company_id, finish_time, create_by, sop_create_time, send_app_id, log_source, qw_user_id,watch_type,corp_id from live_watch_log
+    </sql>
+
+    <select id="selectLiveWatchLogList" parameterType="LiveWatchLog" resultMap="LiveWatchLogResult">
+        <include refid="selectLiveWatchLogVo"/>
+        <where>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="liveId != null "> and live_id = #{liveId}</if>
+            <if test="logType != null "> and log_type = #{logType}</if>
+            <if test="externalContactId != null "> and external_contact_id = #{externalContactId}</if>
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="finishTime != null "> and finish_time = #{finishTime}</if>
+            <if test="sopCreateTime != null "> and sop_create_time = #{sopCreateTime}</if>
+            <if test="sendAppId != null  and sendAppId != ''"> and send_app_id = #{sendAppId}</if>
+            <if test="logSource != null "> and log_source = #{logSource}</if>
+            <if test="qwUserId != null  and qwUserId != ''"> and qw_user_id = #{qwUserId}</if>
+            <if test="watchType != null">and watch_type = #{watchType} </if>
+            <if test="corpId != null">and corp_id = #{corpId} </if>
+        </where>
+    </select>
+
+    <select id="selectLiveWatchLogByLogId" parameterType="Long" resultMap="LiveWatchLogResult">
+        <include refid="selectLiveWatchLogVo"/>
+        where log_id = #{logId}
+    </select>
+
+    <insert id="insertLiveWatchLog" parameterType="LiveWatchLog" useGeneratedKeys="true" keyProperty="logId">
+        insert into live_watch_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="liveId != null">live_id,</if>
+            <if test="logType != null">log_type,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="externalContactId != null">external_contact_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="finishTime != null">finish_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="sopCreateTime != null">sop_create_time,</if>
+            <if test="sendAppId != null">send_app_id,</if>
+            <if test="logSource != null">log_source,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="watchType != null">watch_type,</if>
+            <if test="corpId != null">corp_id,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="liveId != null">#{liveId},</if>
+            <if test="logType != null">#{logType},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="externalContactId != null">#{externalContactId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="finishTime != null">#{finishTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="sopCreateTime != null">#{sopCreateTime},</if>
+            <if test="sendAppId != null">#{sendAppId},</if>
+            <if test="logSource != null">#{logSource},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="watchType != null">#{watchType},</if>
+            <if test="corpId != null">#{corpId},</if>
+        </trim>
+    </insert>
+
+    <update id="updateLiveWatchLog" parameterType="LiveWatchLog">
+        update live_watch_log
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="liveId != null">live_id = #{liveId},</if>
+            <if test="logType != null">log_type = #{logType},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="externalContactId != null">external_contact_id = #{externalContactId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="finishTime != null">finish_time = #{finishTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="sopCreateTime != null">sop_create_time = #{sopCreateTime},</if>
+            <if test="sendAppId != null">send_app_id = #{sendAppId},</if>
+            <if test="logSource != null">log_source = #{logSource},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
+            <if test="watchType != null">watch_type = #{watchType},</if>
+            <if test="corpId != null">corp_id = #{corpId},</if>
+        </trim>
+        where log_id = #{logId}
+    </update>
+
+    <delete id="deleteLiveWatchLogByLogId" parameterType="Long">
+        delete from live_watch_log where log_id = #{logId}
+    </delete>
+
+    <delete id="deleteLiveWatchLogByLogIds" parameterType="String">
+        delete from live_watch_log where log_id in
+        <foreach item="logId" collection="array" open="(" separator="," close=")">
+            #{logId}
+        </foreach>
+    </delete>
+
+    <insert id="insertLiveWatchLogBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="logId">
+        INSERT INTO live_watch_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="liveWatchLogs != null and liveWatchLogs.size() &gt; 0">
+                <foreach collection="liveWatchLogs" item="item" index="index" open="" close="" separator="">
+                    <if test="index == 0">
+                        <if test="item.userId != null">user_id,</if>
+                        <if test="item.liveId != null">live_id,</if>
+                        <if test="item.logType != null">log_type,</if>
+                        <if test="item.createTime != null">create_time,</if>
+                        <if test="item.updateTime != null">update_time,</if>
+                        <if test="item.externalContactId != null">external_contact_id,</if>
+                        <if test="item.companyUserId != null">company_user_id,</if>
+                        <if test="item.companyId != null">company_id,</if>
+                        <if test="item.finishTime != null">finish_time,</if>
+                        <if test="item.createBy != null">create_by,</if>
+                        <if test="item.sopCreateTime != null">sop_create_time,</if>
+                        <if test="item.sendAppId != null">send_app_id,</if>
+                        <if test="item.logSource != null">log_source,</if>
+                        <if test="item.qwUserId != null">qw_user_id,</if>
+                        <if test="item.watchType != null">watch_type,</if>
+                        <if test="item.corpId != null">corp_id,</if>
+                    </if>
+                </foreach>
+            </if>
+        </trim>
+        <trim prefix="VALUES">
+            <foreach collection="liveWatchLogs" item="item" separator=",">
+                (<trim suffixOverrides=",">
+                <if test="item.userId != null">#{item.userId},</if>
+                <if test="item.liveId != null">#{item.liveId},</if>
+                <if test="item.logType != null">#{item.logType},</if>
+                <if test="item.createTime != null">#{item.createTime},</if>
+                <if test="item.updateTime != null">#{item.updateTime},</if>
+                <if test="item.externalContactId != null">#{item.externalContactId},</if>
+                <if test="item.companyUserId != null">#{item.companyUserId},</if>
+                <if test="item.companyId != null">#{item.companyId},</if>
+                <if test="item.finishTime != null">#{item.finishTime},</if>
+                <if test="item.createBy != null">#{item.createBy},</if>
+                <if test="item.sopCreateTime != null">#{item.sopCreateTime},</if>
+                <if test="item.sendAppId != null">#{item.sendAppId},</if>
+                <if test="item.logSource != null">#{item.logSource},</if>
+                <if test="item.qwUserId != null">#{item.qwUserId},</if>
+                <if test="item.watchType != null">#{item.watchType},</if>
+                <if test="item.corpId != null">#{item.corpId},</if>
+            </trim>)
+            </foreach>
+        </trim>
+    </insert>
+
+    <update id="updateLiveWatchLogCondition" parameterType="com.fs.live.domain.LiveWatchLog" >
+        update live_watch_log
+        set update_time = NOW(),
+            sop_create_time = NOW(),
+            send_app_id = #{liveWatchLog.sendAppId},
+            log_source = #{liveWatchLog.logSource}
+        where external_contact_id = #{liveWatchLog.externalContactId}
+            and live_id = #{liveWatchLog.liveId}
+            and qw_user_id = #{liveWatchLog.qwUserId}
+    </update>
+
+    <select id="selectOneLogByLiveIdAndQwUserIdAndExternalId"  resultType="com.fs.live.domain.LiveWatchLog">
+        select * from live_watch_log where live_id = #{liveId} and qw_user_id = #{qwUserId} and external_contact_id = #{externalContactId}
+    </select>
+
+</mapper>

+ 12 - 1
fs-user-app/src/main/java/com/fs/app/controller/live/LiveWatchUserController.java

@@ -1,7 +1,10 @@
 package com.fs.app.controller.live;
 
 
+import com.fs.app.annotation.Login;
+import com.fs.app.controller.AppBaseController;
 import com.fs.app.facade.LiveFacadeService;
+import com.fs.live.param.LiveIsAddKfParam;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -21,7 +24,7 @@ import org.springframework.web.bind.annotation.*;
  */
 @RestController
 @RequestMapping("/app/live/liveWatchUser")
-public class LiveWatchUserController extends BaseController
+public class LiveWatchUserController extends AppBaseController
 {
     @Autowired
     private ILiveWatchUserService liveWatchUserService;
@@ -87,4 +90,12 @@ public class LiveWatchUserController extends BaseController
         return toAjax(liveWatchUserService.changeUserState(liveId, userId));
     }
 
+    @Login
+    @PostMapping("/liveIsAddKf")
+    public R liveIsAddKf(@RequestBody LiveIsAddKfParam param){
+        String userId = getUserId();
+        param.setUserId(Long.valueOf(userId));
+        return liveWatchUserService.liveIsAddKf(param);
+    }
+
 }