Parcourir la source

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

yh il y a 5 heures
Parent
commit
0293686508
25 fichiers modifiés avec 626 ajouts et 34 suppressions
  1. 4 0
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  2. 101 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  3. 1 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  4. 73 0
      fs-service/src/main/java/com/fs/course/dto/FsCourseLinkDTO.java
  5. 7 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseLinkMapper.java
  6. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  7. 12 0
      fs-service/src/main/java/com/fs/course/param/FsCourseEncryptLinkParam.java
  8. 20 0
      fs-service/src/main/java/com/fs/course/param/FsCourseH5ListParam.java
  9. 6 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseLinkService.java
  10. 5 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  11. 41 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java
  12. 25 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  13. 35 0
      fs-service/src/main/java/com/fs/course/vo/FsSopMyCourseH5LinkVO.java
  14. 17 0
      fs-service/src/main/java/com/fs/his/param/FsUserInfoParam.java
  15. 8 0
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java
  16. 2 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  17. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  18. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  19. 154 2
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  20. 9 0
      fs-service/src/main/resources/mapper/course/FsCourseLinkMapper.xml
  21. 9 0
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  22. 4 0
      fs-service/src/main/resources/mapper/qw/QwUserMapper.xml
  23. 44 0
      fs-user-app/src/main/java/com/fs/app/controller/CourseController.java
  24. 26 0
      fs-user-app/src/main/java/com/fs/app/controller/UserController.java
  25. 14 32
      fs-user-app/src/main/java/com/fs/app/controller/WxUserController.java

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

@@ -772,6 +772,10 @@ public class IpadSendServer {
                     // 福袋
                     sendMiniProgram(vo, content, miniMap);
                     break;
+                case "17":
+                    // 小程序H5看课
+                    sendMiniProgram(vo, content, miniMap);
+                    break;
                 case "99":
                     // 群发
                     sendTxtAtMsg(vo);

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

@@ -84,6 +84,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private static final String miniappRealLink = "/pages_course/video.html?course=";
     private static final String appRealLink = "/pages/courseAnswer/index?link=";
     private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
+    private static final String h5miniappLink = "/pages_course/shortLink.html?s=";
     private static final String appActivitlLink = "/pages_course/activity.html?link=";
     private static final String registeredRealLink = "/pages_course/register.html?link=";
 
@@ -1498,6 +1499,44 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     setting.setMiniprogramTitle("福袋发放");
                     setting.setMiniprogramPage(linkBy);
                     setting.setContentType("14");
+                    break;
+                case "17":
+                    try {
+                        String sroth5link;
+                        addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo,2);
+
+                        sroth5link = createH5LinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
+                                qwUserId, companyUserId, companyId, externalId, isOfficial, sopLogs.getFsUserId());
+
+                        if(sopLogs.getSendType()==1){
+                            setting.setMiniprogramAppid(miniAppId);
+                        }else {
+                            int miniType = getLevel(grade);
+                            //算主备小程序
+                            String finalAppId = getAppIdFromMiniMap(miniMap, companyId, sendMsgType, grade);
+
+                            if (StringUtil.strIsNullOrEmpty(finalAppId)) {
+                                finalAppId = miniAppId;
+                            }
+
+                            setting.setMiniType(miniType);
+                            if (!StringUtil.strIsNullOrEmpty(finalAppId)) {
+                                setting.setMiniprogramAppid(finalAppId);
+                            } else {
+                                log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
+                            }
+
+                        }
+
+                        setting.setMiniprogramTitle("邀请链接");
+                        setting.setMiniprogramPage(sroth5link);
+
+                        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;
@@ -2942,4 +2981,66 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             sopLogs.setRemark(remark);
         }
     }
+    private String createH5LinkByMiniApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
+                                         Long courseId, Long videoId, String qwUserId,
+                                         String companyUserId, String companyId, String externalId, String isOfficial, Long fsUserId) {
+        // 获取缓存的配置
+        CourseConfig config;
+        synchronized (configLock) {
+            config = cachedCourseConfig;
+        }
+
+        if (config == null) {
+            log.error("CourseConfig is not loaded.");
+            return "";
+        }
+//        if (StringUtils.isEmpty(config.getMiniprogramPage())){
+//            log.error("miniprogramPage is not loaded.");
+//            return "";
+//        }
+
+        // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
+        FsCourseLink link = new FsCourseLink();
+        link.setCompanyId(Long.parseLong(companyId));
+        link.setQwUserId(Long.valueOf(qwUserId));
+        link.setCompanyUserId(Long.parseLong(companyUserId));
+        link.setVideoId(videoId.longValue());
+        link.setCorpId(logVo.getCorpId());
+        link.setCourseId(courseId.longValue());
+        link.setQwExternalId(Long.parseLong(externalId));
+        link.setUNo(UUID.randomUUID().toString());
+
+
+        String randomString = generateRandomStringWithLock();
+        if (StringUtil.strIsNullOrEmpty(randomString)) {
+            link.setLink(UUID.randomUUID().toString().replace("-", ""));
+        } else {
+            link.setLink(randomString);
+        }
+
+        link.setCreateTime(sendTime);
+
+        FsCourseRealLink courseMap = new FsCourseRealLink();
+        BeanUtils.copyProperties(link, courseMap);
+
+        String courseJson = JSON.toJSONString(courseMap);
+        String realLinkFull = h5miniappLink + courseJson;
+        link.setRealLink(realLinkFull);
+
+
+        Integer expireDays = (setting.getExpiresDays() == null || setting.getExpiresDays() == 0)
+                ? config.getVideoLinkExpireDate()
+                : setting.getExpiresDays();
+
+        // 使用 Java 8 时间 API 计算过期时间
+        LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays - 1);
+        expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
+        Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
+        link.setUpdateTime(updateTime);
+
+        //存短链-
+        enqueueCourseLink(link);
+        return link.getRealLink();
+    }
 }

+ 1 - 0
fs-service/src/main/java/com/fs/course/config/CourseConfig.java

@@ -19,6 +19,7 @@ public class CourseConfig implements Serializable {
     private Integer appAnswerIntegral; //app答题积分
     private Integer defaultLine;//默认看课线路
     private String realLinkDomainName;//真链域名
+    private String realLinkH5DomainName;//H5通用看课域名
     private String authDomainName;//网页授权域名
     private String mpAppId;//看课公众号APPID
     private String registerDomainName;//注册域名

+ 73 - 0
fs-service/src/main/java/com/fs/course/dto/FsCourseLinkDTO.java

@@ -0,0 +1,73 @@
+package com.fs.course.dto;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+/**
+ * 短链对象 fs_course_link
+ *
+ * @author fs
+ * @date 2024-10-24
+ */
+@Data
+public class FsCourseLinkDTO extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键Id */
+    private Long linkId;
+
+    /** 链接后缀 */
+    @Excel(name = "链接后缀")
+    private String link;
+
+    /** 真实链接 */
+    @Excel(name = "真实链接")
+    private String realLink;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 客服id */
+    @Excel(name = "客服id")
+    private Long companyUserId;
+
+    /** 企微userId主键 */
+    @Excel(name = "企微userId主键")
+    private String qwUserId;
+
+    /** 课节id */
+    @Excel(name = "课节id")
+    private Long videoId;
+
+    /** 企微主体id */
+    @Excel(name = "企微主体id")
+    private String corpId;
+
+    private Long courseId;
+    /**
+    * 企微外部联系表主键
+    */
+    private Long qwExternalId;
+
+    private Integer linkType; //链接类型 0:正常链接  1:应急链接  3:小程序链接 4:app 5:官方
+
+    private Integer isRoom;//是否发群
+
+    private String chatId;
+//    private String link_uuid;
+    // 识别编号
+    private String uNo;
+
+    private String courseName;
+
+    /** 课程封面 */
+    private String courseUrl;
+
+    private String title;
+
+
+
+}

+ 7 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseLinkMapper.java

@@ -1,6 +1,8 @@
 package com.fs.course.mapper;
 
 import com.fs.course.domain.FsCourseLink;
+import com.fs.course.dto.FsCourseLinkDTO;
+import com.fs.course.param.FsCourseH5ListParam;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
@@ -78,4 +80,9 @@ public interface FsCourseLinkMapper
 
     @Select("select link_id,real_link from fs_course_link where create_time > '2025-03-07 12:00:00' and create_time < '2025-03-07 17:00:00'")
     List<FsCourseLink> selectFsCourseLinkListUpdate();
+
+    List<FsCourseLinkDTO> selectFsCourseLinkListByQwUserId(FsCourseH5ListParam param);
+
+
+    List<FsCourseLink> selectLinkByCourseIdAndExIdAndQwUserId(@Param("courseId") Long courseId,@Param("videoId") Long videoId,@Param("qwExternalContactId") Long qwExternalContactId,@Param("qwUserId") String qwUserId);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -728,4 +728,6 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             "</script>"
     })
     List<Long> getExContactIdsIdsByWatchLogIds(@Param("watchLogIds")List<Long> watchLogIds);
+
+    List<FsSopMyCourseH5LinkVO> getSopCourseH5StudyList(@Param("userId") Long userId);
 }

+ 12 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseEncryptLinkParam.java

@@ -0,0 +1,12 @@
+package com.fs.course.param;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "加解密")
+public class FsCourseEncryptLinkParam {
+
+   private String url;
+
+}

+ 20 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseH5ListParam.java

@@ -0,0 +1,20 @@
+package com.fs.course.param;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class FsCourseH5ListParam {
+
+    @ApiModelProperty(value = "页码,默认为1")
+    private Integer pageNum =1;
+    @ApiModelProperty(value = "页大小,默认为100")
+    private Integer pageSize = 100;
+
+    private String qwUserId;
+
+    private Long qwExternalId;
+
+    private Long userId;
+
+    private Long logId;
+}

+ 6 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseLinkService.java

@@ -2,6 +2,8 @@ package com.fs.course.service;
 
 import com.fs.common.core.domain.R;
 import com.fs.course.domain.FsCourseLink;
+import com.fs.course.dto.FsCourseLinkDTO;
+import com.fs.course.param.FsCourseH5ListParam;
 import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.param.FsCourseLinkRoomParam;
 import com.fs.qw.domain.QwUser;
@@ -96,4 +98,8 @@ public interface IFsCourseLinkService
     R getWxaCodeGenerateScheme(String linkStr,String appId);
 
     R getProjectCode();
+
+    List<FsCourseLinkDTO> selectFsCourseLinkListByQwUserId(FsCourseH5ListParam param);
+
+    R getLinkInfo(Long logId);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java

@@ -1,6 +1,7 @@
 package com.fs.course.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.common.core.domain.R;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
@@ -161,4 +162,8 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
      * @return
      */
     List<Long> getExContactIdsIdsByWatchLogIds(List<Long> watchLogIds);
+
+    R encryptLink(String url);
+
+    R decryptLink(String url);
 }

+ 41 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java

@@ -18,10 +18,12 @@ import com.fs.core.config.WxMaConfiguration;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.config.CourseMaConfig;
 import com.fs.course.domain.*;
+import com.fs.course.dto.FsCourseLinkDTO;
 import com.fs.course.mapper.FsCourseDomainNameMapper;
 import com.fs.course.mapper.FsCourseLinkMapper;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.mapper.FsUserCourseVideoMapper;
+import com.fs.course.param.FsCourseH5ListParam;
 import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.param.FsCourseLinkRoomParam;
 import com.fs.course.service.IFsCourseLinkService;
@@ -71,6 +73,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.*;
+import java.util.stream.Collectors;
 
 import static com.fs.course.utils.LinkUtil.generateRandomStringWithLock;
 import static com.fs.sop.service.impl.SopUserLogsInfoServiceImpl.convertStringToDate;
@@ -921,4 +924,42 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
         }
         return R.ok().put("code",code);
     }
+
+    @Override
+    public List<FsCourseLinkDTO> selectFsCourseLinkListByQwUserId(FsCourseH5ListParam param) {
+
+        List<FsCourseLinkDTO> fsCourseLink = fsCourseLinkMapper.selectFsCourseLinkListByQwUserId(param);
+        // fsCourseLink 根据course_id 去重 并且 保留最新的一条create_time
+        fsCourseLink = fsCourseLink.stream()
+                .collect(Collectors.groupingBy(
+                        FsCourseLinkDTO::getCourseId,
+                        Collectors.collectingAndThen(
+                                Collectors.maxBy(Comparator.comparing(FsCourseLinkDTO::getCreateTime)),
+                                Optional::get
+                        )
+                ))
+                .values()
+                .stream()
+                .collect(Collectors.toList());
+
+
+        return fsCourseLink;
+    }
+
+    @Override
+    public R getLinkInfo(Long logId) {
+
+        FsCourseWatchLog log = fsCourseWatchLogMapper.selectById(logId);
+        // 根据
+        List<FsCourseLink>  list = fsCourseLinkMapper.selectLinkByCourseIdAndExIdAndQwUserId(log.getCourseId(),log.getVideoId(),log.getQwExternalContactId(),log.getQwUserId().toString());
+        if(list.isEmpty()){
+            // 群发的目前不支持
+            return R.error("未找到该课节");
+        }
+        // 筛选取 create_time 最大的一条
+        FsCourseLink link = list.stream().max(Comparator.comparing(FsCourseLink::getCreateTime)).get();
+        Map<String,Object> map = new HashMap<>();
+        map.put("link",link.getRealLink());
+        return R.ok().put("data",map);
+    }
 }

+ 25 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -9,6 +9,7 @@ import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+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.DictUtils;
@@ -31,6 +32,7 @@ import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
+import com.fs.his.utils.PhoneUtil;
 import com.fs.qw.Bean.MsgBean;
 import com.fs.qw.cache.IQwExternalContactCacheService;
 import com.fs.qw.cache.IQwUserCacheService;
@@ -1326,4 +1328,27 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseWatchLogMapper.selectWatchLogIMVOListByMap(params);
     }
 
+    private final String courseUrl = "/pages/index/index?s=";
+
+    @Override
+    public R encryptLink(String url) {
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSON.parseObject(json, CourseConfig.class);
+        String encryptionLink = PhoneUtil.encryptPhone(url);
+        Map<String, Object> data = new HashMap<>();
+        data.put("encryptionLink", config.getRealLinkH5DomainName()+courseUrl+encryptionLink);
+        return R.ok().put("data",data);
+    }
+
+    @Override
+    public R decryptLink(String url) {
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSON.parseObject(json, CourseConfig.class);
+
+        String decryptLink = PhoneUtil.decryptPhone(url);
+        Map<String, Object> data = new HashMap<>();
+        data.put("decryptLink", config.getRealLinkH5DomainName()+courseUrl+decryptLink);
+        return R.ok().put("data", data);
+    }
+
 }

+ 35 - 0
fs-service/src/main/java/com/fs/course/vo/FsSopMyCourseH5LinkVO.java

@@ -0,0 +1,35 @@
+package com.fs.course.vo;
+
+import lombok.Data;
+
+
+@Data
+public class FsSopMyCourseH5LinkVO {
+
+    private static final long serialVersionUID = 1L;
+
+
+//    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+//    private String updateTime;
+
+
+
+//    /** 课程标题 */
+//    @Excel(name = "课程标题")
+    private String courseName;
+
+    /** 课程封面 */
+    private String courseUrl;
+
+    private String title;
+
+//    private Long qwExternalId;
+
+    private Long logId;
+
+    private String qwUserName;
+
+
+
+
+}

+ 17 - 0
fs-service/src/main/java/com/fs/his/param/FsUserInfoParam.java

@@ -0,0 +1,17 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class FsUserInfoParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String qwUserId;
+
+    private String corpId;
+
+    private Long userId;
+}

+ 8 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java

@@ -204,6 +204,14 @@ public class FsIntegralGoodsServiceImpl implements IFsIntegralGoodsService
             param.setMinPoints(stageMaxPoints-5000);
             param.setMaxPoints(stageMaxPoints);
             stageGoods = selectRandomStageGoods(param);
+            if (stageGoods.isEmpty()){
+                FsIntegralGoods queryParam = new FsIntegralGoods();
+                queryParam.setStatus(1L);
+                stageGoods = selectFsIntegralGoodsList(queryParam);
+                if (stageGoods.size()>3){
+                    stageGoods.subList(0,stageGoods.size()-3).clear();
+                }
+            }
         }
 
         // 5. 转换为奖励商品列表

+ 2 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java

@@ -531,4 +531,6 @@ public interface QwUserMapper extends BaseMapper<QwUser>
     List<QwUser> getQwUser(List<String> list);
 
     Long getqwUserByphone(String phone);
+
+    QwUserVO getQwUserCompanyInfo(@Param("qwUserId") Long qwUserId);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/qw/service/IQwUserService.java

@@ -226,4 +226,6 @@ public interface IQwUserService
     List<QwUserGroup> grouplist(QwUserListParam qwUser);
 
     R getQwUser(String qwUserKey);
+
+    QwUserVO getQwUserCompanyInfo(Long qwUserId);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -1738,4 +1738,9 @@ public class QwUserServiceImpl implements IQwUserService
         }
         return ""; // 如果没有扩展名,返回空字符串
     }
+
+    @Override
+    public QwUserVO getQwUserCompanyInfo(Long qwUserId) {
+        return qwUserMapper.getQwUserCompanyInfo( qwUserId);
+    }
 }

+ 154 - 2
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -92,6 +92,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     private static final String REAL_LINK_PREFIX = "/courseH5/pages/course/learning?course=";
     private static final String SHORT_LINK_PREFIX = "/courseH5/pages/course/learning?s=";
     private static final String miniappRealLink = "/pages_course/video.html?course=";
+    private static final String h5miniappLink = "/pages_course/shortLink.html?s=";
     private static final String appRealLink = "/pages/courseAnswer/index?link=";
     private static final String appActivitlLink = "/pages_course/activity.html?link=";
     private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
@@ -571,7 +572,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     if (vo == null) return null;
                     QwSopLogs sopLogs = new QwSopLogs();
 
-                    sopLogs.setQwUserid(qwUser.getQwUserId());
+                    String qwUserId = qwUser.getQwUserId();
+                    sopLogs.setQwUserid(qwUserId);
                     sopLogs.setExternalUserId(vo.getExternalUserId());
                     sopLogs.setLogType(2);
                     sopLogs.setContentJson(param.getSetting());
@@ -590,7 +592,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setQwUserKey(qwUser.getId());
 
                     // 设置实际发送人
-                    updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId(),groupUser.getChatId());
+                    updateQwUserKey(sopLogs, qwUserId,param.getSopId(),groupUser.getChatId());
                     //域名
                     String companyUserId = qwUser.getCompanyUserId().toString();
                     String companyId = String.valueOf(qwUser.getCompanyId()).trim();
@@ -752,6 +754,50 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 break;
                             case "16":
                                 createVoiceUrl(st, companyUserId, qwSop);
+                                break;
+                            case "17": //h5看课
+                                try {
+                                    addWatchLogIfNeeded(param.getSopId(), param.getVideoId(), param.getCourseId(), Long.valueOf(groupUser.getFsUserId()), qwUserId, companyUserId, companyId, groupUser.getId(), param.getStartTime(), createTime,2);
+
+                                    String sroth5link = createH5LinkByMiniApp(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
+                                            qwUserId, companyUserId, companyId, groupUser.getId(), config);
+
+
+                                    miniAppId = null;
+                                    if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                        Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                                        if (integerListMap != null) {
+                                            int listIndexTemp = 1;
+                                            List<CompanyMiniapp> miniapps = integerListMap.get(listIndexTemp);
+                                            if (miniapps != null && !miniapps.isEmpty()) {
+                                                CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                                if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                                    miniAppId = companyMiniapp.getAppId();
+                                                }
+                                            }
+                                        }
+                                    }
+
+                                    if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                                        miniAppId = qwCompany.getMiniAppId();
+                                    }
+
+                                    if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                                        st.setMiniprogramAppid(miniAppId);
+                                    } else {
+                                        log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
+                                    }
+
+                                    st.setMiniprogramTitle("邀请链接");
+                                    st.setMiniprogramPage(sroth5link);
+
+                                    st.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(st.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : st.getMiniprogramPicUrl());
+
+
+                                } catch (Exception e) {
+                                    log.error("浏览器看课模板解析失败:" + e);
+                                }
+
                                 break;
                             default:
                                 break;
@@ -1302,6 +1348,50 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             } catch (Exception e) {
                                 throw new RuntimeException(e);
                             }
+                            break;
+                        case "17":
+                            try {
+                                addWatchLogIfNeeded(item.getSopId(), param.getVideoId(), param.getCourseId(),item.getFsUserId(), qwUserId, companyUserId, companyId, item.getExternalId(),item.getStartTime(),createTime,2 );
+
+                                String sroth5link = createH5LinkByMiniApp(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
+                                        qwUserId, companyUserId, companyId,  item.getExternalId(), config);
+
+
+                                miniAppId = null;
+                                if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                    Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                                    if (integerListMap != null) {
+                                        int listIndexTemp = 1;
+                                        List<CompanyMiniapp> miniapps = integerListMap.get(listIndexTemp);
+                                        if (miniapps != null && !miniapps.isEmpty()) {
+                                            CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                            if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                                miniAppId = companyMiniapp.getAppId();
+                                            }
+                                        }
+                                    }
+                                }
+
+                                if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                                    miniAppId = qwCompany.getMiniAppId();
+                                }
+
+                                if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                                    st.setMiniprogramAppid(miniAppId);
+                                } else {
+                                    log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
+                                }
+
+                                st.setMiniprogramTitle("邀请链接");
+                                st.setMiniprogramPage(sroth5link);
+
+                                st.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(st.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : st.getMiniprogramPicUrl());
+
+
+                            } catch (Exception e) {
+                                log.error("浏览器看课模板解析失败:" + e);
+                            }
+
                             break;
                         default:
                             break;
@@ -1916,7 +2006,47 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
                     st.setMiniprogramPage(linkByMiniApp);
                     break;
+                case "17": //h5看课
+                    try {
+                        addWatchLogIfNeeded(item.getSopId(), param.getVideoId(), param.getCourseId(), item.getFsUserId(), String.valueOf(qwUser.getId()), companyUserId, companyId,
+                                item.getExternalId(), item.getStartTime(), dataTime,2);
+                        String sroth5link = createH5LinkByMiniApp(st, param.getCorpId(), dataTime, param.getCourseId(), param.getVideoId(),
+                                String.valueOf(qwUser.getId()), companyUserId, companyId, item.getExternalId(), config);
+
+                        miniAppId = null;
+                        if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                            Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                            if (integerListMap != null) {
+                                int listIndexTemp = 1;
+                                List<CompanyMiniapp> miniapps = integerListMap.get(listIndexTemp);
+                                if (miniapps != null && !miniapps.isEmpty()) {
+                                    CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                    if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                        miniAppId = companyMiniapp.getAppId();
+                                    }
+                                }
+                            }
+                        }
+
+                        if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                            miniAppId = qwCompany.getMiniAppId();
+                        }
+
+                        if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                            st.setMiniprogramAppid(miniAppId);
+                        } else {
+                            log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
+                        }
+                        st.setMiniprogramTitle("邀请链接");
+                        st.setMiniprogramPage(sroth5link);
+
+                        st.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(st.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : st.getMiniprogramPicUrl());
 
+
+                    } catch (Exception e) {
+                        log.error("浏览器看课模板解析失败:" + e);
+                    }
+                    break;
                 default:
                     break;
 
@@ -2097,6 +2227,28 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return link.getRealLink().replaceAll("^[\\s\\u2005]+", "");
     }
 
+    String createH5LinkByMiniApp(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 = h5miniappLink + courseJson;
+        link.setRealLink(realLinkFull);
+
+        Date updateTime = createUpdateTime(setting, sendTime, config);
+
+        link.setUpdateTime(updateTime);
+        //存短链-
+        fsCourseLinkMapper.insertFsCourseLink(link);
+        return link.getRealLink();
+    }
+
     private QwCreateLinkByAppVO createLinkByApp(QwSopCourseFinishTempSetting.Setting setting, String corpId,
                                                 Date sendTime, Integer courseId, Integer videoId, Long qwUserId,
                                                 String companyUserId, String companyId, Long externalId,

+ 9 - 0
fs-service/src/main/resources/mapper/course/FsCourseLinkMapper.xml

@@ -46,6 +46,15 @@
         <include refid="selectFsCourseLinkVo"/>
         where link_id = #{linkId}
     </select>
+    <select id="selectFsCourseLinkListByQwUserId" resultType="com.fs.course.dto.FsCourseLinkDTO">
+        select l.link_id,l.real_link,l.create_time,l.update_time,l.company_id,l.company_user_id,l.qw_user_id,l.video_id,l.corp_id,l.course_id,l.qw_external_id,l.link_type,l.chat_id,l.is_room,l.u_no,
+               c.img_url courseUrl,c.course_name courseName,c.title
+        from fs_course_link l left join fs_user_course c on  c.course_id=l.course_id where create_time &gt;= CURDATE() AND create_time &lt; CURDATE() + INTERVAL 1 DAY
+                                                                                       and l.qw_user_id = #{qwUserId} and l.qw_external_id = #{qwExternalId}
+    </select>
+    <select id="selectLinkByCourseIdAndExIdAndQwUserId" resultType="FsCourseLink">
+        select l.link_id,l.real_link,l.create_time,l.update_time,l.company_id,l.company_user_id,l.qw_user_id,l.video_id,l.corp_id,l.course_id,l.qw_external_id,l.link_type,l.chat_id,l.is_room,l.u_no from  fs_course_link l where l.course_id=#{courseId} and l.qw_external_id=#{qwExternalContactId} and l.qw_user_id=#{qwUserId} and l.video_id = #{videoId}
+    </select>
 
     <insert id="insertFsCourseLink" parameterType="FsCourseLink" useGeneratedKeys="true" keyProperty="linkId">
         insert into fs_course_link

+ 9 - 0
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -1193,4 +1193,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         )
 
     </select>
+    <select id="getSopCourseH5StudyList" resultType="com.fs.course.vo.FsSopMyCourseH5LinkVO">
+        select  c.img_url courseUrl,v.title courseName,c.title,l.log_id logId,u.qw_user_name qwUserName from fs_course_watch_log l
+             left join fs_user_course c on c.course_id = l.course_id
+             left join qw_user u on l.qw_user_id = u.id
+             left join fs_user_course_video v on l.video_id =v.video_id
+        where l.user_id = #{userId}  AND l.create_time &gt;= CURDATE()
+          AND l.create_time &lt; CURDATE() + INTERVAL 1 DAY
+        order by l.create_time desc
+    </select>
 </mapper>

+ 4 - 0
fs-service/src/main/resources/mapper/qw/QwUserMapper.xml

@@ -374,6 +374,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="getqwUserByphone" resultType="java.lang.Long">
         SELECT user_id FROM `fs_user` WHERE phone = #{phone}
     </select>
+    <select id="getQwUserCompanyInfo" resultType="com.fs.qw.vo.QwUserVO">
+        select qu.qw_user_id,qu.qw_user_name,c.company_name from qw_user qu left join company c on qu.company_id = c.company_id
+        where qu.id = #{qwUserId}
+    </select>
 
 
 </mapper>

+ 44 - 0
fs-user-app/src/main/java/com/fs/app/controller/CourseController.java

@@ -8,6 +8,7 @@ import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.course.domain.*;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.param.*;
 import com.fs.course.service.*;
 import com.fs.course.vo.*;
@@ -50,6 +51,14 @@ public class CourseController extends  AppBaseController{
     private IFsCourseSopAppLinkService courseSopAppLinkService;
     @Autowired
     ITencentCloudCosService tencentCloudCosService;
+    @Autowired
+    private IFsCourseWatchLogService courseWatchLogService;
+
+    @Autowired
+    private IFsCourseLinkService linkService;
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
 
 //    @Cacheable(value="getCourseCate" )
     @ApiOperation("获取分类")
@@ -374,4 +383,39 @@ public class CourseController extends  AppBaseController{
         param.setUserId(userId);
         return courseVideoService.registerQwFsUser(param);
     }
+
+    @ApiOperation("加密链接参数")
+    @PostMapping("/encryptLink")
+    @Login
+    public R encryptLink(@RequestBody FsCourseEncryptLinkParam param) {
+        return courseWatchLogService.encryptLink(param.getUrl());
+    }
+
+    @ApiOperation("解密链接参数")
+    @PostMapping("/decryptLink")
+    public R decryptLink(@RequestBody FsCourseEncryptLinkParam param) {
+        return courseWatchLogService.decryptLink(param.getUrl());
+    }
+
+
+    @ApiOperation("获取我的sop课程")
+    @PostMapping("/getSopCourseH5StudyList")
+    public R getSopCourseH5StudyList(@RequestBody FsCourseH5ListParam  param ){
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        long userId = Long.parseLong(getUserId());
+//        long userId = param.getUserId();
+        List<FsSopMyCourseH5LinkVO> list = fsCourseWatchLogMapper.getSopCourseH5StudyList(userId);
+
+//        FsCourseLinkDTO linkVO = linkService.selectFsCourseLinkListByQwUserId(param);
+
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
+    @ApiOperation("获取我的sop课程详情")
+    @PostMapping("/getSopCourseH5StudyInfo")
+    public R getSopCourseH5StudyInfo(@RequestBody FsCourseH5ListParam  param ){
+        // 查询看课记录
+        return linkService.getLinkInfo(param.getLogId());
+    }
 }

+ 26 - 0
fs-user-app/src/main/java/com/fs/app/controller/UserController.java

@@ -18,6 +18,7 @@ import com.fs.his.dto.FindUsersByDTO;
 import com.fs.his.param.FindUserByParam;
 import com.fs.his.param.FsUserCouponUParam;
 import com.fs.his.param.FsUserEditPushParam;
+import com.fs.his.param.FsUserInfoParam;
 import com.fs.his.service.IFsDoctorService;
 import com.fs.his.service.IFsPackageService;
 import com.fs.his.service.IFsUserCouponService;
@@ -27,6 +28,8 @@ import com.fs.his.vo.FsUserCouponCountUVO;
 import com.fs.his.vo.FsUserCouponListUVO;
 import com.fs.his.vo.UserVo;
 import com.fs.qw.service.IQwAppContactWayService;
+import com.fs.qw.service.IQwUserService;
+import com.fs.qw.vo.QwUserVO;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
 import com.github.pagehelper.PageHelper;
@@ -78,6 +81,9 @@ public class UserController extends  AppBaseController {
     @Autowired
     private IFsUserCourseVideoService courseVideoService;
 
+    @Autowired
+    private IQwUserService qwUserService;
+
 
     @Autowired
     private IFsUserService fsUserService;
@@ -365,4 +371,24 @@ public class UserController extends  AppBaseController {
     public R removeUser(){
         return userService.removeUser(Long.parseLong(getUserId()));
     }
+
+    /**
+     * @Description: 获取用户信息,包含经销商
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2026/1/9 13:37
+     */
+    @Login
+    @ApiOperation("获取用户信息")
+    @GetMapping("/getUserInfoByQwUser")
+    public R getUserInfoByQwUser(FsUserInfoParam param , HttpServletRequest request){
+        FsUser user=userService.selectFsUserByUserId(Long.parseLong(getUserId()));
+//        FsUser user=userService.selectFsUserByUserId(param.getUserId());
+        QwUserVO qwUserVO =qwUserService.getQwUserCompanyInfo(Long.parseLong(param.getQwUserId()));
+        Map<String,Object> map=new HashMap<>();
+        map.put("user",user);
+        map.put("qwUser",qwUserVO);
+        return R.ok().put("data",map);
+    }
 }

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

@@ -466,39 +466,21 @@ public class WxUserController extends AppBaseController{
     @PostMapping("/handleFsUserWx")
     public R handleFsUserWx(@RequestBody FsUserLoginByMpParam param) {
 
-        SysConfig sysConfig3 = sysConfigMapper.selectConfigByConfigKey("courseMa.config");
-        List<CourseMaConfig> courseMaConfigs = JSON.parseArray(sysConfig3.getConfigValue(), CourseMaConfig.class);
-        if (courseMaConfigs.isEmpty()){
-            return R.error("小程序配置为空");
-        }
-        // 根据appId匹配配置
-        CourseMaConfig matchedConfig = courseMaConfigs.stream()
-                .filter(config -> param.getAppId().equals(config.getAppid()))
-                .findFirst()
-                .orElse(null);
-
-        if (matchedConfig == null) {
-            return R.error("未找到匹配的小程序配置");
+        try {
+            final WxMaService wxService = WxMaConfiguration.getMaService(param.getAppId());
+            //获取微信用户信息
+            WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(param.getCode());
+
+            FsUser user = userService.selectFsUserByUserId(param.getUserId());
+            handleFsUserWx(user, param, session);
+            return R.ok();
+        } catch (WxErrorException e) {
+            if (e.getError().getErrorCode() == 40163) {
+                return R.error(40163, e.getError().getErrorMsg());
+            } else {
+                return R.error("获取用户信息失败," + e.getMessage());
+            }
         }
-        return handleCourseLogin1(param,
-                () -> WxMaConfiguration.getMaService(matchedConfig.getAppid()),
-                matchedConfig.getName());
-
-//    final WxMaService wxService = WxMaConfiguration.getMaService(param.getAppId());
-//    try {
-//      //获取微信用户信息
-//      WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(param.getCode());
-//
-//      FsUser user = userService.selectFsUserByUserId(param.getUserId());
-//      handleFsUserWx(user, param, session);
-//      return R.ok();
-//    } catch (WxErrorException e) {
-//      if (e.getError().getErrorCode() == 40163) {
-//        return R.error(40163, e.getError().getErrorMsg());
-//      } else {
-//        return R.error("获取用户信息失败," + e.getMessage());
-//      }
-//    }
     }
 
     private R handleCourseLogin1(FsUserLoginByMpParam param, Supplier<WxMaService> wxServiceSupplier, String logName) {