Jelajahi Sumber

sop新增飞书看课(还在完善中)

cgp 14 jam lalu
induk
melakukan
66a3309a4a

+ 2 - 9
fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -357,7 +357,7 @@ public class FsUserCourseVideoController extends AppBaseController {
     private ISysConfigService configService;
 
     @Login
-    @ApiOperation("生成飞书看课链接")
+    @ApiOperation("生成飞书初始链接")
     @GetMapping("/getFeiShuCourseLink")
     public R getFeiShuCourseLink(@RequestParam Long companyUserId,
                                  @RequestParam Long videoId,
@@ -379,14 +379,7 @@ public class FsUserCourseVideoController extends AppBaseController {
         }
 
         if (Boolean.TRUE.equals(enableFeishuNewLink)) {
-            FeishuLinkParam param = new FeishuLinkParam();
-            param.setCompanyId(companyId);
-            param.setCompanyUserId(companyUserId);
-            param.setCourseId(courseId);
-            param.setVideoId(videoId);
-            param.setProjectId(projectId);
-            param.setId(id);
-            String link = feiShuService.getFeishuRegisterLink(param);
+            String link = feiShuService.getFeishuRegisterLink(videoId, companyId, courseId,companyUserId);
             log.info("链接{}", link);
             return R.ok().put("data", link);
         }

+ 3 - 4
fs-company/src/main/java/com/fs/company/controller/course/FeiShuCourseController.java

@@ -23,12 +23,11 @@ public class FeiShuCourseController extends BaseController {
 
     @Autowired
     private FeiShuService feiShuService;
-    @ApiOperation("生成飞书看课链接")
+    @ApiOperation("生成飞书初始链接")
     @GetMapping("/getFeiShuCourseLink")
     public AjaxResult getFeiShuCourseLink(
             @RequestParam(value = "videoId", required = true) Long videoId,
-            @RequestParam(value = "courseId", required = false) Long courseId,
-            @RequestParam(value = "id", required = false) Long id) {
+            @RequestParam(value = "courseId", required = false) Long courseId) {
 
         // 1. 强制必填参数校验
         if (videoId == null) {
@@ -50,7 +49,7 @@ public class FeiShuCourseController extends BaseController {
 
         // 4. 调用业务层生成链接
         try {
-            String feishuLink = feiShuService.generateRegisterLink(videoId, companyId, courseId, companyUserId, id);
+            String feishuLink = feiShuService.getFeishuRegisterLink(videoId, companyId, courseId, companyUserId);
             return AjaxResult.success(feishuLink);
         } catch (CustomException e) {
             log.error("生成飞书看课链接业务异常:{}", e.getMessage());

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

@@ -26,6 +26,7 @@ import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.service.IFsCourseFinishTempService;
 import com.fs.course.service.IFsCourseLinkService;
 import com.fs.course.service.IFsUserCourseService;
+import com.fs.feishu.service.FeiShuService;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
@@ -81,6 +82,7 @@ import static com.fs.course.utils.LinkUtil.generateRandomStringWithLock;
 public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
     private static final String gjminiappLink = "/pages/course/learning?course=";
+    private static final String feishuMiniappLink = "/pages_course/video?course=";
     private static final String REAL_LINK_PREFIX = "/courseH5/pages/course/learning?course=";
     private static final String signArticleLink = "/pages_course/article.html?link=";
     private static final String shareMaterialLink = "/pages_course/customerStatistics.html?link=";
@@ -216,6 +218,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     @Autowired
     private ISysConfigService sysConfigService;
 
+    @Autowired
+    private FeiShuService feiShuService;
+
 
 
     @PostConstruct
@@ -1206,6 +1211,20 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     setting.setMiniprogramPage(phoneLinkBy);
                     setting.setContentType("17");
                     break;
+                case "18" :
+                    //飞书看课链接
+                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo);
+
+                    Map<String, String> feiShuLinkMap = createFeiShuLinkByMiniApp(setting, sopLogs.getCorpId(), sendTime, courseId, videoId,
+                            qwUserId, companyUserId, companyId, Long.parseLong(externalId), cachedCourseConfig);
+                    String feishuRealLink = feiShuLinkMap.get("url");
+                    String feishuLink = feiShuLinkMap.get("link");
+                    String feishuRedirectUrl = feiShuService.getFeishuRegisterLink(videoId,Long.valueOf(companyId),courseId,Long.valueOf(companyUserId));
+                    if (StringUtils.isNotEmpty(feishuRedirectUrl)) {
+                        setting.setFeiShuLinkUrl(feishuRedirectUrl);
+                        setting.setLinkUrl(feishuRedirectUrl);
+                    }
+                    break;
                 case "21"://短信看课
                     if (sopLogs.getFsUserId() != null && !Long.valueOf(0L).equals(sopLogs.getFsUserId())) {
                         addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo);
@@ -2395,6 +2414,38 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         result.put("link", link.getLink());
         return result;
     }
+
+    /**
+     * 批量生成飞书课程链接
+     * */
+    private Map<String, String> createFeiShuLinkByMiniApp(QwSopTempSetting.Content.Setting setting, String corpId, Date sendTime,
+                                                        Long courseId, Long videoId, String qwUserId,
+                                                        String companyUserId, String companyId, Long externalId, CourseConfig config) {
+
+        FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
+                companyUserId, companyId, externalId.toString(), 3);
+
+        FsCourseRealLink courseMap = new FsCourseRealLink();
+        BeanUtils.copyProperties(link, courseMap);
+
+        String courseJson = JSON.toJSONString(courseMap);
+
+        String realLinkFull = feishuMiniappLink + courseJson;
+        if (StringUtils.isNotEmpty(config.getRealLinkGjDomainName())) {
+            realLinkFull = config.getRealLinkGjDomainName() + realLinkFull;
+        }
+        link.setRealLink(realLinkFull);
+
+        Date updateTime = createUpdateTime(setting, sendTime, config);
+
+        link.setUpdateTime(updateTime);
+        //存短链-
+        fsCourseLinkMapper.insertFsCourseLink(link);
+        Map<String, String> result = new HashMap<>();
+        result.put("url", link.getRealLink());
+        result.put("link", link.getLink());
+        return result;
+    }
     public String createActivityLinkByMiniApp(QwSopTempSetting.Content.Setting st, QwSopLogs sopLogs, String corpId, Date sendTime, Long courseId, Long videoId, String qwUserId, String companyUserId, String companyId, CourseConfig config, String chatId) {
 
         FsCourseLink link = createActivityByLink(corpId, sendTime, courseId, videoId, qwUserId.toString(),

+ 37 - 85
fs-service/src/main/java/com/fs/feishu/service/FeiShuService.java

@@ -32,7 +32,7 @@ import org.springframework.stereotype.Component;
 @Slf4j
 public class FeiShuService {
 
-    private final String COURSE_PATH = "/feishu/pages_course/videovip?token=%s";
+    private final String COURSE_PATH = "/feishu/pages_course/video?token=%s";
 
     @Autowired
     private FeiShuConfig feishuConfig;
@@ -49,40 +49,56 @@ public class FeiShuService {
     }
 
     /**
-     * 复制飞书看课链接
+     * 001 生成飞书初始链接
      */
-    public String getFeishuCourseLink(Long companyUserId, Long videoId, Long periodId) {
-        if (companyUserId == null || videoId == null) {
-            throw new CustomException("用户ID和视频ID不能为空");
+    public String getFeishuRegisterLink(Long videoId, Long companyId, Long courseId,Long companyUserId) {
+        // 读取配置判断是否启用飞书新跳转
+        String json = configService.selectConfigByKey("course.config");
+        if (StringUtils.isBlank(json)) {
+            throw new CustomException("飞书看课配置为空");
+        }
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        Boolean enableFeishuNewLink = config.getEnableFeishuNewLink();
+        if (!Boolean.TRUE.equals(enableFeishuNewLink)) {
+            throw new CustomException("未开启飞书看课");
         }
 
-        FsUserCourseVideo userCourseVideo = videoMapper.selectFsUserCourseVideoByVideoId(videoId);
+        // 构建参数
+        FeishuLinkParam param = new FeishuLinkParam();
+        param.setCompanyId(companyId);
+        param.setCompanyUserId(companyUserId);
+        param.setCourseId(courseId);
+        param.setVideoId(videoId);
+
+        FsUserCourseVideo userCourseVideo = videoMapper.selectFsUserCourseVideoByVideoId(param.getVideoId());
         if (userCourseVideo == null) {
-            throw new CustomException("视频不存在: " + videoId);
+            throw new CustomException("视频不存在: " + param.getVideoId());
         }
 
         try {
             // 创建云文档
-            String documentId = createDocument(userCourseVideo.getTitle());
-
-            // 拼接看课url
-            String url = buildCourseLink(companyUserId, videoId, periodId);
+            String documentId = createDocument(userCourseVideo.getTitle() + "-注册");
 
-            // 创建iframe块
-            createIframeBlock(documentId, url);
+            // 拼接授权url
+            String authUrl = buildAuthLink(param.getCompanyId(), param.getCompanyUserId(),
+                    param.getCourseId(), param.getVideoId());
 
+            // 创建关注公众号 text + link block
+            createTextLinkBlock(documentId, authUrl);
             // 更新文档权限
             changeDocumentPermissions(documentId);
 
             // 返回飞书看课链接
             return "https://www.feishu.cn/docx/" + documentId;
         } catch (Exception e) {
-            throw new CustomException("创建飞书课程链接失败: " + e.getMessage(), e);
+            throw new CustomException("创建飞书注册链接失败: " + e.getMessage(), e);
         }
     }
 
+
+
     /**
-     * 复制飞书看课新链接
+     * 002生成飞书课程链接
      */
     public String getFeishuCourseNewLink(FeishuLinkParam param) {
         if (param == null || param.getCompanyUserId() == null || param.getCompanyId() == null) {
@@ -98,7 +114,7 @@ public class FeiShuService {
             String documentId = createDocument(userCourseVideo.getTitle());
 
             // 拼接新看课url
-            String url = buildCourseNewLink(param.getCompanyId(), param.getCompanyUserId(), param.getCourseId(), param.getVideoId(),param.getUserId(),param.getId());
+            String url = buildCourseNewLink(param.getCompanyId(), param.getCompanyUserId(), param.getCourseId(), param.getVideoId(),param.getUserId());
 
             // 创建iframe块
             createIframeBlock(documentId, url);
@@ -113,57 +129,6 @@ public class FeiShuService {
         }
     }
 
-    public String generateRegisterLink(Long videoId, Long companyId, Long courseId,Long companyUserId, Long id) {
-        // 读取配置判断是否启用飞书新跳转
-        String json = configService.selectConfigByKey("course.config");
-        if (StringUtils.isBlank(json)) {
-            throw new CustomException("飞书看课配置为空");
-        }
-        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-        Boolean enableFeishuNewLink = config.getEnableFeishuNewLink();
-        if (!Boolean.TRUE.equals(enableFeishuNewLink)) {
-            throw new CustomException("未开启飞书看课");
-        }
-
-        // 构建参数
-        FeishuLinkParam param = new FeishuLinkParam();
-        param.setCompanyId(companyId);
-        param.setCompanyUserId(companyUserId);
-        param.setCourseId(courseId);
-        param.setVideoId(videoId);
-        param.setId(id);
-        // 调用原有方法
-        return getFeishuRegisterLink(param);
-    }
-    /**
-     * 复制飞书注册链接
-     */
-    public String getFeishuRegisterLink(FeishuLinkParam param) {
-        FsUserCourseVideo userCourseVideo = videoMapper.selectFsUserCourseVideoByVideoId(param.getVideoId());
-        if (userCourseVideo == null) {
-            throw new CustomException("视频不存在: " + param.getVideoId());
-        }
-
-        try {
-            // 创建云文档
-            String documentId = createDocument(userCourseVideo.getTitle() + "-注册");
-
-            // 拼接授权url
-            String authUrl = buildAuthLink(param.getCompanyId(), param.getCompanyUserId(),
-                    param.getCourseId(), param.getVideoId(),param.getId());
-
-            // 创建关注公众号 text + link block
-            createTextLinkBlock(documentId, authUrl);
-            // 更新文档权限
-            changeDocumentPermissions(documentId);
-
-            // 返回飞书看课链接
-            return "https://www.feishu.cn/docx/" + documentId;
-        } catch (Exception e) {
-            throw new CustomException("创建飞书注册链接失败: " + e.getMessage(), e);
-        }
-    }
-
 
     /**
      * 创建云文档
@@ -178,24 +143,11 @@ public class FeiShuService {
         return data.getDocument().getDocumentId();
     }
 
-    /**
-     * 拼接看课url
-     */
-    private String buildCourseLink(Long companyUserId, Long videoId, Long periodId) {
-        String json = configService.selectConfigByKey("course.config");
-        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-
-        // 生成安全令牌
-        Long timestamp = System.currentTimeMillis() / 1000;
-        String token = SecureTokenUtil.generateToken(companyUserId, videoId, periodId, timestamp);
-
-        return config.getFeishuLinkDomainName() + String.format(COURSE_PATH, token);
-    }
 
     /**
      * 拼接看课url(课程参数JSON方式,适配飞书iframe,双重URL编码)
      */
-    private String buildCourseNewLink(Long companyId, Long companyUserId, Long courseId, Long videoId,Long userId,Long id) {
+    private String buildCourseNewLink(Long companyId, Long companyUserId, Long courseId, Long videoId,Long userId) {
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
@@ -205,14 +157,15 @@ public class FeiShuService {
         courseParam.put("videoId", videoId);
         courseParam.put("companyId", companyId);
         courseParam.put("courseId", courseId);
-        courseParam.put("id", id);
+        //TODO: 查询这个用户对应的sop课程短链
+        courseParam.put("link", courseId);
         String courseJson = JSONUtil.toJsonStr(courseParam);
         try {
             // 第一次编码:对JSON进行URL编码
             String encodedJson = URLEncoder.encode(courseJson, "UTF-8");
             // 拼接完整URL(userId与course参数同级)
             String baseUrl = config.getFeishuNewLinkDomainName();
-            String fullUrl = baseUrl + "/feishuCourse/pages_course/videovip?course=" + encodedJson + "&userId=" +userId;
+            String fullUrl = baseUrl + "/feishuCourse/pages_course/video?course=" + encodedJson + "&userId=" +userId;
             // 第二次编码:对整个URL进行URL编码(适配飞书iframe创建要求)
             return URLEncoder.encode(fullUrl, "UTF-8");
         } catch (UnsupportedEncodingException e) {
@@ -223,7 +176,7 @@ public class FeiShuService {
     /**
      * 拼接授权url(课程参数JSON方式,适配飞书iframe,双重URL编码)
      */
-    private String buildAuthLink(Long companyId, Long companyUserId, Long courseId, Long videoId,Long id) {
+    private String buildAuthLink(Long companyId, Long companyUserId, Long courseId, Long videoId) {
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
@@ -232,7 +185,6 @@ public class FeiShuService {
         authParam.put("videoId", videoId);
         authParam.put("companyId", companyId);
         authParam.put("courseId", courseId);
-        authParam.put("id", id);
         String authJson = JSONUtil.toJsonStr(authParam);
 
         try {

+ 2 - 6
fs-service/src/main/java/com/fs/feishu/util/SecureTokenUtil.java

@@ -18,9 +18,9 @@ public class SecureTokenUtil {
     /**
      * 生成安全令牌
      */
-    public static String generateToken(Long companyUserId, Long videoId, Long periodId, Long timestamp) {
+    public static String generateToken(Long companyUserId, Long videoId,  Long timestamp) {
         try {
-            String dataBuilder = companyUserId + ":" + videoId + ":" + periodId + ":" + timestamp;
+            String dataBuilder = companyUserId + ":" + videoId + ":" + timestamp;
             return encryptData(dataBuilder);
         } catch (Exception e) {
             throw new RuntimeException("飞书令牌生成失败", e);
@@ -96,8 +96,4 @@ public class SecureTokenUtil {
         long currentTime = System.currentTimeMillis() / 1000;
         return (currentTime - timestamp) <= 60 * 60 * 24 * 2;
     }
-
-    public static void main(String[] args) {
-        System.out.println(generateToken(10378L, 136L,38L, System.currentTimeMillis() / 1000));
-    }
 }

+ 3 - 0
fs-service/src/main/java/com/fs/qw/vo/QwSopTempSetting.java

@@ -102,6 +102,9 @@ public class QwSopTempSetting implements Serializable{
             private String linkUrl;
             //销售易追踪链接
             private String xsyLinkUrl;
+
+            //飞书追踪链接
+            private String feiShuLinkUrl;
             //发送app消息的链接
             private String appLinkUrl;
 

+ 24 - 0
fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempServiceImpl.java

@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
+import com.fs.common.exception.CustomException;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
 import com.fs.course.config.CourseConfig;
@@ -212,6 +213,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
     public Map<String, Object> addOrUpdateSetting(QwSopTempDay day) {
         List<QwSopTempRules> list = day.getList();
         List<QwSopTempContent> collect = list.stream().flatMap(e -> e.getSettingList().stream()).collect(Collectors.toList());
+        checkFeishuSopRule(list);
         List<QwSopTempContent> voiceList = collect.stream().filter(e -> e.getContentType() == 7).collect(Collectors.toList());
         if(!voiceList.isEmpty()){
 //            day.setVoice(1);
@@ -285,6 +287,28 @@ public class QwSopTempServiceImpl implements IQwSopTempService
         return map;
     }
 
+    /**
+     * 校验飞书看课规则:当内容类别为18时,其所属规则的消息类别必须为课程(type==2)
+     */
+    public void checkFeishuSopRule(List<QwSopTempRules> rulesList) {
+        if (rulesList == null || rulesList.isEmpty()) {
+            return;
+        }
+        for (QwSopTempRules rule : rulesList) {
+            List<QwSopTempContent> settingList = rule.getSettingList();
+            if (settingList == null || settingList.isEmpty()) {
+                continue;
+            }
+            boolean hasFeishu = settingList.stream()
+                    .anyMatch(content -> content.getContentType() != null && content.getContentType() == 18);
+            if (hasFeishu) {
+                if (rule.getType() == null || rule.getType() != 2) {
+                    throw new CustomException("当内容类别为[飞书看课]时,必须选课程");
+                }
+            }
+        }
+    }
+
     private void processAndReplaceContent(List<QwSopTempRules> tempSettings){
         List<FastGptChatReplaceWords> words = fastGptChatReplaceWordsMapper.selectAllFastGptChatReplaceWords();
         //循环天

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

@@ -31,6 +31,7 @@ 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.feishu.service.FeiShuService;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
@@ -106,6 +107,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
     private static final String gjminiappLink = "/pages/course/learning?course=";
 
+    private static final String feishuMiniappLink = "/pages_course/video?course=";
+
     @Lazy
     @Autowired
     private XiaoShouYiTrackLinkCacheService xiaoShouYiTrackLinkCacheService;
@@ -195,6 +198,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     @Autowired
     private ISysConfigService sysConfigService;
 
+    @Autowired
+    private FeiShuService feiShuService;
+
 
     @Override
     public void save(SopUserLogsInfo sopUserLogsInfo) {
@@ -1128,7 +1134,46 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             st.setMiniprogramTitle("领取开箱密码");
                             st.setMiniprogramPage(linkByMiniApp);
                             break;
+                        //飞书看课
+                        case "18":
+                            // 飞书看课(第一阶段:发送注册授权链接)
+                            addWatchLogIfNeeded(param.getSopId(), param.getVideoId(), param.getCourseId(),
+                                    item.getFsUserId(), qwUserId, companyUserId, companyId,
+                                    item.getExternalId(), param.getStartTime(), createTime);
+
+                            // 参数转换(注意判空,避免NPE)
+                            Long videoId = param.getVideoId() != null ? param.getVideoId().longValue() : null;
+                            Long courseId = param.getCourseId() != null ? param.getCourseId().longValue() : null;
+                            Long companyIdLong = Long.parseLong(companyId);
+                            Long companyUserIdLong = Long.parseLong(companyUserId);
+
+                            if (videoId == null) {
+                                log.error("飞书链接生成失败:视频ID为空,sopId={}", param.getSopId());
+                                break;
+                            }
 
+                            // 调用 001 方法:生成注册授权链接
+                            String feishuLink = feiShuService.getFeishuRegisterLink(videoId, companyIdLong, courseId, companyUserIdLong);
+
+                            if (StringUtils.isNotEmpty(feishuLink)) {
+                                // 替换占位符(销售称呼、客户称呼等)
+                                String txt = StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText();
+                                String customerTitle = StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus()) ? "同学" : contact.getStageStatus();
+                                if (StringUtils.isNotEmpty(st.getLinkTitle())) {
+                                    st.setLinkTitle(st.getLinkTitle()
+                                            .replaceAll("#销售称呼#", txt)
+                                            .replaceAll("#客户称呼#", customerTitle));
+                                }
+                                if (StringUtils.isNotEmpty(st.getLinkDescribe())) {
+                                    st.setLinkDescribe(st.getLinkDescribe()
+                                            .replaceAll("#销售称呼#", txt)
+                                            .replaceAll("#客户称呼#", customerTitle));
+                                }
+                                st.setLinkUrl(feishuLink);
+                            } else {
+                                log.error("生成飞书注册链接失败,sopId={}", param.getSopId());
+                            }
+                            break;
                         case "21":
                             if (sopLogs.getFsUserId() != null && !Long.valueOf(0L).equals(sopLogs.getFsUserId())) {
                                 addWatchLogIfNeeded(item.getSopId(), param.getVideoId(), param.getCourseId(), item.getFsUserId(), qwUserId, companyUserId, companyId,
@@ -1774,6 +1819,46 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     st.setMiniprogramTitle("领取开箱密码");
                     st.setMiniprogramPage(linkByMiniApp);
                     break;
+                //飞书看课
+                case "18":
+                    // 飞书看课(第一阶段:发送注册授权链接)
+                    addWatchLogIfNeeded(item.getSopId(), param.getVideoId(), param.getCourseId(),
+                            item.getFsUserId(), String.valueOf(qwUser.getId()), companyUserId, companyId,
+                            item.getExternalId(), item.getStartTime(), dataTime);
+
+                    // 参数转换
+                    Long videoId = param.getVideoId() != null ? param.getVideoId().longValue() : null;
+                    Long courseId = param.getCourseId() != null ? param.getCourseId().longValue() : null;
+                    Long companyIdLong = Long.parseLong(companyId);
+                    Long companyUserIdLong = Long.parseLong(companyUserId);
+
+                    if (videoId == null) {
+                        log.error("飞书链接生成失败:视频ID为空,sopId={}", item.getSopId());
+                        break;
+                    }
+
+                    // 调用 001 方法:生成注册授权链接
+                    String feishuLink = feiShuService.getFeishuRegisterLink(videoId, companyIdLong, courseId, companyUserIdLong);
+
+                    if (StringUtils.isNotEmpty(feishuLink)) {
+                        // 替换占位符
+                        String txt = StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText();
+                        String customerTitle = StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus()) ? "同学" : contact.getStageStatus();
+                        if (StringUtils.isNotEmpty(st.getLinkTitle())) {
+                            st.setLinkTitle(st.getLinkTitle()
+                                    .replaceAll("#销售称呼#", txt)
+                                    .replaceAll("#客户称呼#", customerTitle));
+                        }
+                        if (StringUtils.isNotEmpty(st.getLinkDescribe())) {
+                            st.setLinkDescribe(st.getLinkDescribe()
+                                    .replaceAll("#销售称呼#", txt)
+                                    .replaceAll("#客户称呼#", customerTitle));
+                        }
+                        st.setLinkUrl(feishuLink);
+                    } else {
+                        log.error("生成飞书注册链接失败,sopId={}", item.getSopId());
+                    }
+                    break;
 
                 case "21":
                     if (sopLogs.getFsUserId() != null && !Long.valueOf(0L).equals(sopLogs.getFsUserId())) {
@@ -1824,6 +1909,26 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return  list;
     }
 
+    private Map<String, String> createFeishuH5Link(QwSopCourseFinishTempSetting.Setting setting, String corpId, Date sendTime,
+                                                   Integer courseId, Integer videoId, String qwUserId,
+                                                   String companyUserId, String companyId, Long externalId, CourseConfig config) {
+        FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, Long.valueOf(qwUserId),
+                companyUserId, companyId, externalId, 3, null);
+        FsCourseRealLink courseMap = new FsCourseRealLink();
+        BeanUtils.copyProperties(link, courseMap);
+        String courseJson = JSON.toJSONString(courseMap);
+        String realLinkFull = feishuMiniappLink + courseJson;
+        if (StringUtils.isNotEmpty(config.getRealLinkGjDomainName())) {
+            realLinkFull = config.getRealLinkGjDomainName() + realLinkFull;
+        }
+        link.setRealLink(realLinkFull);
+        link.setUpdateTime(createUpdateTime(setting, sendTime, config));
+        fsCourseLinkMapper.insertFsCourseLink(link);
+        Map<String, String> result = new HashMap<>();
+        result.put("url", link.getRealLink());
+        result.put("link", link.getLink());
+        return result;
+    }
 
     private String createSmsShortLink(QwSopCourseFinishTempSetting.Setting setting, String corpId, Date sendTime,
                                       Integer courseId, Integer videoId, String qwUserId,

+ 3 - 2
fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

@@ -176,10 +176,11 @@ public class CourseFsUserController extends AppBaseController {
         return courseLinkService.getLinkInfo(param.getLogId());
     }
 
-    @ApiOperation("获取飞书看课新链接")
+    @ApiOperation("获取飞书课程链接")
     @PostMapping("/getFeiShuCourseNewLink")
     public R getFeiShuCourseNewLink(@RequestBody FeishuLinkParam param) {
-        return R.ok().put("data", feiShuService.getFeishuCourseNewLink(param));
+        String feishuCourseNewLink = feiShuService.getFeishuCourseNewLink(param);
+        return R.ok().put("data", feishuCourseNewLink);
     }
 
 

+ 23 - 3
fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

@@ -6,6 +6,7 @@ import com.fs.app.annotation.Login;
 import com.fs.app.controller.AppBaseController;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
 import com.fs.common.utils.StringUtils;
 import com.fs.core.utils.OrderCodeUtils;
 import com.fs.course.config.CourseConfig;
@@ -35,6 +36,7 @@ import org.springframework.web.bind.annotation.*;
 import javax.servlet.http.HttpServletRequest;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 @Api("企微看课接口")
@@ -121,7 +123,14 @@ public class CourseQwController extends AppBaseController {
     @GetMapping("/getH5CourseVideoDetails")
     public R getCourseVideoDetails(FsUserCourseVideoFinishUParam param)
     {
-        param.setUserId(Long.parseLong(getUserId()));
+        logger.info("h5课程详情加问答入参:param:{}",param);
+        try {
+            param.setUserId(Long.parseLong(getUserId()));
+        }catch (NumberFormatException e){
+            if (param.getUserId()==null){
+                return R.error("用户未登录");
+            }
+        }
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
@@ -367,8 +376,19 @@ public class CourseQwController extends AppBaseController {
 
     @Login
     @PostMapping("/getErrMsg")
-    public void getErrMsg(@RequestParam("msg") String msg) {
-        Long userId = Long.parseLong(getUserId());
+    public void getErrMsg(@RequestParam("msg") String msg,
+                          @RequestHeader Map<String, String> headers) {
+        Long userId;
+        try {
+            userId =Long.parseLong(getUserId());
+        }catch (NumberFormatException e){
+            //如果登录状态异常就从请求头获取userId
+            String skipAuth = headers.get("x-skip-auth");
+            if (skipAuth == null){
+                throw new CustomException("用户未登录");
+            }
+            userId = Long.parseLong(skipAuth);
+        }
         logger.error("zyp \n【看课中途报错】:{}","userId:"+userId+msg);
     }