Forráskód Böngészése

重粉判断和现实

吴树波 1 napja
szülő
commit
4da0173af9

+ 11 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -947,8 +947,19 @@ public class QwExternalContactController extends BaseController
         startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setUserId(loginUser.getUser().getUserId());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
         List<UserWatchLogListVo> list= fsUserCompanyBindService.getWatchLogList(param);
         return getDataTable(list);
     }
 
+    /**
+     * 重粉看课历史 - 综合信息查询(关联销售 + 课程进度)
+     */
+    @GetMapping("/getRepeatCourseHistory")
+    public AjaxResult getRepeatCourseHistory(@RequestParam Long fsUserId){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        return AjaxResult.success(fsUserCompanyBindService.getRepeatCourseHistory(fsUserId, companyId));
+    }
+
 }

+ 14 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCompanyBindMapper.java

@@ -2,6 +2,8 @@ package com.fs.course.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.course.domain.FsUserCompanyBind;
+import com.fs.course.vo.CourseProgressResultVO;
+import com.fs.course.vo.RelatedSalesResultVO;
 import com.fs.course.vo.UserWatchLogListVo;
 import com.fs.qw.param.UserWatchLogParam;
 import org.apache.ibatis.annotations.Param;
@@ -67,4 +69,16 @@ public interface FsUserCompanyBindMapper extends BaseMapper<FsUserCompanyBind>{
     
     int deleteFsUserCompanyBindByCompanyUserIdAndFsUserId(@Param("companyUserId") Long companyUserId,
                                                           @Param("fsUserId") Long fsUserId);
+
+    /**
+     * 查询重粉看课历史 - 关联销售信息
+     * 通过fsUserId查找所有同名fsUserId的QwExternalContact记录,关联QwUser和QwCompany
+     */
+    List<RelatedSalesResultVO> getRelatedSalesByFsUserId(@Param("fsUserId") Long fsUserId);
+
+    /**
+     * 查询重粉看课历史 - 课程学习进度
+     * 通过fsUserId查找FsCourseWatchLog记录,计算每门课程的进度
+     */
+    List<CourseProgressResultVO> getCourseProgressByFsUserId(@Param("fsUserId") Long fsUserId);
 }

+ 8 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCompanyBindService.java

@@ -3,6 +3,7 @@ package com.fs.course.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.domain.FsUserCompanyBind;
+import com.fs.course.vo.RepeatCourseHistoryVO;
 import com.fs.course.vo.UserWatchLogListVo;
 import com.fs.qw.param.UserWatchLogParam;
 
@@ -81,4 +82,11 @@ public interface IFsUserCompanyBindService extends IService<FsUserCompanyBind>{
     void finish(Long fsUserId, Long qwUserId, Long companyUserId, FsCourseWatchLog watchLog);
 
     List<UserWatchLogListVo> getWatchLogList(UserWatchLogParam param);
+
+    /**
+     * 查询重粉看课历史综合信息(关联销售 + 课程进度)
+     * @param fsUserId 小程序用户ID
+     * @return 综合信息VO
+     */
+    RepeatCourseHistoryVO getRepeatCourseHistory(Long fsUserId, Long companyId);
 }

+ 110 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsUserCompanyBindServiceImpl.java

@@ -15,18 +15,24 @@ import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.mapper.FsUserCompanyBindMapper;
 import com.fs.course.mapper.FsUserCourseMapper;
 import com.fs.course.service.IFsUserCompanyBindService;
+import com.fs.course.vo.CourseProgressResultVO;
+import com.fs.course.vo.RelatedSalesResultVO;
+import com.fs.course.vo.RepeatCourseHistoryVO;
 import com.fs.course.vo.UserWatchLogListVo;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.mapper.QwCompanyMapper;
 import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.param.UserWatchLogParam;
+import com.fs.qw.vo.QwOptionsVO;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
@@ -48,6 +54,7 @@ public class FsUserCompanyBindServiceImpl extends ServiceImpl<FsUserCompanyBindM
     private QwExternalContactMapper qwExternalContactMapper;
     private FsUserCourseMapper fsUserCourseMapper;
     private QwCompanyMapper qwCompanyMapper;
+    private QwUserMapper qwUserMapper;
     private CompanyMapper companyMapper;
 
     /**
@@ -242,6 +249,108 @@ public class FsUserCompanyBindServiceImpl extends ServiceImpl<FsUserCompanyBindM
         if(param.getExternalUserId() == null &&  param.getFsUserId() == null){
             return Collections.emptyList();
         }
-        return baseMapper.getWatchLogList(param);
+        List<UserWatchLogListVo> list = baseMapper.getWatchLogList(param);
+        // 权限判断:如果传了companyId,对无权限的主体脱敏
+        if (param.getCompanyId() != null && list != null) {
+            List<String> permittedCorpIds = getPermittedCorpIds(param.getCompanyId());
+            for (UserWatchLogListVo vo : list) {
+                boolean hasPermission = vo.getCorpId() == null || permittedCorpIds.contains(vo.getCorpId());
+                vo.setHasPermission(hasPermission);
+                if (!hasPermission) {
+                    vo.setQwUserName("***");
+                    vo.setCorpName("***");
+                }
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public RepeatCourseHistoryVO getRepeatCourseHistory(Long fsUserId, Long companyId) {
+        RepeatCourseHistoryVO vo = new RepeatCourseHistoryVO();
+
+        // 查询当前用户公司有权限的企微主体corpId列表
+        List<String> permittedCorpIds = getPermittedCorpIds(companyId);
+
+        // 1. 查询客户基本信息
+        QwExternalContact contact = qwExternalContactMapper.selectOne(
+                new QueryWrapper<QwExternalContact>()
+                        .eq("fs_user_id", fsUserId)
+                        .eq("status", 0)
+                        .last("LIMIT 1")
+        );
+        if (contact != null) {
+            vo.setName(contact.getName());
+            vo.setAvatar(contact.getAvatar());
+            vo.setRemark(contact.getRemark());
+            vo.setUserRepeat(contact.getUserRepeat());
+        }
+
+        // 2. 查询关联销售(单条SQL + GROUP BY 优化)
+        List<RelatedSalesResultVO> salesResults = baseMapper.getRelatedSalesByFsUserId(fsUserId);
+        List<RepeatCourseHistoryVO.RelatedSalesVO> salesList = new ArrayList<>();
+        for (RelatedSalesResultVO r : salesResults) {
+            RepeatCourseHistoryVO.RelatedSalesVO s = new RepeatCourseHistoryVO.RelatedSalesVO();
+            boolean hasPermission = r.getCorpId() == null || permittedCorpIds.contains(r.getCorpId());
+            s.setHasPermission(hasPermission);
+            if (hasPermission) {
+                s.setQwUserName(r.getQwUserName());
+                s.setCorpName(r.getCorpName());
+            } else {
+                s.setQwUserName("***");
+                s.setCorpName("***");
+            }
+            s.setAddTime(r.getAddTime());
+            s.setStatus(r.getStatus());
+            salesList.add(s);
+        }
+        vo.setSalesList(salesList);
+
+        // 3. 查询课程学习进度(单条SQL + 子查询优化)
+        List<CourseProgressResultVO> courseResults = baseMapper.getCourseProgressByFsUserId(fsUserId);
+        List<RepeatCourseHistoryVO.CourseProgressVO> courseList = new ArrayList<>();
+        for (CourseProgressResultVO c : courseResults) {
+            RepeatCourseHistoryVO.CourseProgressVO cp = new RepeatCourseHistoryVO.CourseProgressVO();
+            cp.setCourseId(c.getCourseId());
+            cp.setCourseName(c.getCourseName());
+            cp.setTotalCount(c.getTotalCount() != null ? c.getTotalCount() : 0);
+            cp.setWatchedCount(c.getWatchedCount() != null ? c.getWatchedCount() : 0);
+            // 计算百分比
+            int total = cp.getTotalCount();
+            int watched = cp.getWatchedCount();
+            cp.setPercentage(total > 0 ? (watched * 100 / total) : 0);
+            cp.setLatestSection(c.getLatestSection());
+            cp.setLatestTime(c.getLatestTime());
+            cp.setFinished(total > 0 && watched >= total);
+            boolean hasPermission = c.getCorpId() == null || permittedCorpIds.contains(c.getCorpId());
+            cp.setHasPermission(hasPermission);
+            if (hasPermission) {
+                cp.setQwUserName(c.getQwUserName());
+                cp.setCorpName(c.getCorpName());
+            } else {
+                cp.setQwUserName("***");
+                cp.setCorpName("***");
+            }
+            courseList.add(cp);
+        }
+        vo.setCourseList(courseList);
+
+        return vo;
+    }
+
+    /**
+     * 根据运营公司ID查询有权限的企微主体corpId列表
+     */
+    private List<String> getPermittedCorpIds(Long companyId) {
+        List<QwOptionsVO> companyList = qwUserMapper.selectQwCompanyListOptionsVOByCompanyId(companyId);
+        List<String> corpIds = new ArrayList<>();
+        if (companyList != null) {
+            for (QwOptionsVO vo : companyList) {
+                if (vo.getDictValue() != null) {
+                    corpIds.add(vo.getDictValue());
+                }
+            }
+        }
+        return corpIds;
     }
 }

+ 32 - 0
fs-service/src/main/java/com/fs/course/vo/CourseProgressResultVO.java

@@ -0,0 +1,32 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 重粉看课历史 - 课程进度查询结果VO(Mapper用)
+ */
+@Data
+public class CourseProgressResultVO {
+    /** 课程ID */
+    private Long courseId;
+    /** 课程名称 */
+    private String courseName;
+    /** 课程总节数 */
+    private Integer totalCount;
+    /** 已完课节数 */
+    private Integer watchedCount;
+    /** 最新学习小节名称 */
+    private String latestSection;
+    /** 最新学习时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date latestTime;
+    /** 发送课程的销售企微名 */
+    private String qwUserName;
+    /** 发送课程的企微主体名 */
+    private String corpName;
+    /** 企微主体corpId(用于权限判断) */
+    private String corpId;
+}

+ 23 - 0
fs-service/src/main/java/com/fs/course/vo/RelatedSalesResultVO.java

@@ -0,0 +1,23 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 重粉看课历史 - 关联销售查询结果VO(Mapper用)
+ */
+@Data
+public class RelatedSalesResultVO {
+    /** 企微用户名(销售名) */
+    private String qwUserName;
+    /** 所属企微主体名称 */
+    private String corpName;
+    /** 企微主体corpId(用于权限判断) */
+    private String corpId;
+    private Integer status;
+    /** 添加时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date addTime;
+}

+ 77 - 0
fs-service/src/main/java/com/fs/course/vo/RepeatCourseHistoryVO.java

@@ -0,0 +1,77 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 重粉看课历史 - 综合信息VO
+ */
+@Data
+public class RepeatCourseHistoryVO {
+
+    /** 客户名称 */
+    private String name;
+    /** 客户头像 */
+    private String avatar;
+    /** 备注 */
+    private String remark;
+    /** 重粉状态 0正常 1重粉 */
+    private Integer userRepeat;
+
+    /** 关联销售列表 */
+    private List<RelatedSalesVO> salesList;
+
+    /** 课程学习进度列表 */
+    private List<CourseProgressVO> courseList;
+
+    /**
+     * 关联销售信息
+     */
+    @Data
+    public static class RelatedSalesVO {
+        /** 企微用户名(销售名) */
+        private String qwUserName;
+        /** 所属企微主体名称 */
+        private String corpName;
+        /** 添加时间 */
+        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+        private Date addTime;
+        /** 状态 0正常 1员工离职待接替 2正在接替 3流失 4删除 */
+        private Integer status;
+        /** 当前用户是否有权查看该主体信息 */
+        private Boolean hasPermission;
+    }
+
+    /**
+     * 课程学习进度
+     */
+    @Data
+    public static class CourseProgressVO {
+        /** 课程ID */
+        private Long courseId;
+        /** 课程名称 */
+        private String courseName;
+        /** 课程总节数 */
+        private Integer totalCount;
+        /** 已学习节数(有看课记录的小节数) */
+        private Integer watchedCount;
+        /** 完课百分比 */
+        private Integer percentage;
+        /** 最新学习小节名称 */
+        private String latestSection;
+        /** 最新学习时间 */
+        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+        private Date latestTime;
+        /** 是否已完课 */
+        private Boolean finished;
+        /** 发送课程的销售企微名 */
+        private String qwUserName;
+        /** 发送课程的企微主体名 */
+        private String corpName;
+        /** 当前用户是否有权查看该主体信息 */
+        private Boolean hasPermission;
+    }
+}

+ 4 - 0
fs-service/src/main/java/com/fs/course/vo/UserWatchLogListVo.java

@@ -31,5 +31,9 @@ public class UserWatchLogListVo {
     private String qwUserName;
     //所属企微主体
     private String corpName;
+    //企微主体corpId(权限判断)
+    private String corpId;
+    //是否有权查看
+    private Boolean hasPermission;
 
 }

+ 1 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -246,6 +246,7 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
             "left join qw_contact_way_group wg on wg.id=cw.group_id" +
             "<where>  \n" +
             "            <if test=\"id != null  and id != ''\"> and ec.id   like concat( #{id}, '%') </if>\n" +
+            "            <if test=\"isRepeat != null\"> and ec.is_repeat   = #{isRepeat} </if>\n" +
             "            <if test=\"userId != null  and userId != ''\"> and ec.user_id   like concat( #{userId}, '%') </if>\n" +
             "            <if test=\"qwUserName != null  and qwUserName != ''\"> and qu.qw_user_name   like concat( #{qwUserName}, '%') </if>\n" +
             "            <if test=\"externalUserId != null  and externalUserId != ''\"> and ec.external_user_id = #{externalUserId}</if>\n" +

+ 1 - 0
fs-service/src/main/java/com/fs/qw/param/QwExternalContactParam.java

@@ -49,6 +49,7 @@ public class QwExternalContactParam {
      * 是否重粉
      */
     private Integer userRepeat;
+    private Integer isRepeat;
 
 
     private Long customerId;

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

@@ -10,5 +10,7 @@ public class UserWatchLogParam {
     private Long videoId;
     private Long userId;
     private Long fsUserId;
+    /** 当前登录用户的运营公司ID(权限判断用) */
+    private Long companyId;
 
 }

+ 3 - 3
fs-service/src/main/resources/application-druid-bjczwh-test.yml

@@ -46,9 +46,9 @@ spring:
                 slave:
                     # 从数据源开关/默认关闭
                     enabled: false
-                    url:
-                    username:
-                    password:
+                    url: jdbc:mysql://1.95.54.246:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: czwh
+                    password: Ylrz_1q2w3e4r5t6y
                 # 初始连接数
                 initialSize: 5
                 # 最小连接池数量

+ 80 - 1
fs-service/src/main/resources/mapper/course/FsUserCompanyBindMapper.xml

@@ -137,7 +137,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         e.course_name,
         g.qw_user_name,
         f.title videoName,
-        qc.corp_name
+        qc.corp_name,
+        qc.corp_id AS corpId
         from
         fs_user_company_bind a
         inner join (
@@ -183,4 +184,82 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      <delete id="deleteFsUserCompanyBindByCompanyUserIdAndFsUserId" parameterType="Long">
         delete  from fs_user_company_bind where fs_user_id=#{fsUserId} and company_user_id=#{companyUserId}
     </delete>
+
+    <!-- 重粉看课历史 - 关联销售信息:通过fsUserId找出该客户添加的所有销售 -->
+    <!-- 优化:走 qw_external_contact(fs_user_id) 索引 + GROUP BY 去重 + 内连接避免无效数据 -->
+    <select id="getRelatedSalesByFsUserId" resultType="com.fs.course.vo.RelatedSalesResultVO">
+        SELECT
+            CASE
+                WHEN qu.qw_user_name IS NOT NULL AND qu.qw_user_name LIKE '{%' THEN JSON_UNQUOTE(JSON_EXTRACT(qu.qw_user_name, '$.name'))
+                ELSE qu.qw_user_name
+            END AS qwUserName,
+            qc.corp_name AS corpName,
+            qu.corp_id AS corpId,
+            MIN(ec.`status`) AS `status`,
+            MIN(ec.create_time) AS addTime
+        FROM qw_external_contact ec
+        INNER JOIN qw_user qu ON qu.id = ec.qw_user_id
+        LEFT JOIN qw_company qc ON qc.corp_id = qu.corp_id
+        WHERE ec.fs_user_id = #{fsUserId}
+        GROUP BY ec.qw_user_id
+        ORDER BY addTime DESC
+    </select>
+
+    <!-- 重粉看课历史 - 课程学习进度:通过fsUserId查出看课记录并计算进度 -->
+    <!-- 优化:使用子查询先缩小课程范围,避免全表扫描;分别统计总节数和完课节数 -->
+    <select id="getCourseProgressByFsUserId" resultType="com.fs.course.vo.CourseProgressResultVO">
+        SELECT
+            uc.course_id AS courseId,
+            uc.course_name AS courseName,
+            COALESCE(vt.total_count, 0) AS totalCount,
+            COALESCE(ws.watched_count, 0) AS watchedCount,
+            latest.video_name AS latestSection,
+            latest.max_time AS latestTime,
+            latest.qw_user_name AS qwUserName,
+            latest.corp_name AS corpName,
+            latest.corp_id AS corpId
+        FROM (
+            SELECT DISTINCT course_id
+            FROM fs_course_watch_log
+            WHERE user_id = #{fsUserId}
+        ) watched
+        INNER JOIN fs_user_course uc ON uc.course_id = watched.course_id
+        LEFT JOIN (
+            SELECT course_id, COUNT(*) AS total_count
+            FROM fs_user_course_video
+            WHERE is_del = 0
+            GROUP BY course_id
+        ) vt ON vt.course_id = watched.course_id
+        LEFT JOIN (
+            SELECT course_id, COUNT(DISTINCT video_id) AS watched_count
+            FROM fs_course_watch_log
+            WHERE user_id = #{fsUserId}
+            GROUP BY course_id
+        ) ws ON ws.course_id = watched.course_id
+        LEFT JOIN (
+            SELECT
+                wl.course_id,
+                v.title AS video_name,
+                wl.create_time AS max_time,
+                CASE
+                    WHEN qu.qw_user_name IS NOT NULL AND qu.qw_user_name LIKE '{%' THEN JSON_UNQUOTE(JSON_EXTRACT(qu.qw_user_name, '$.name'))
+                    ELSE qu.qw_user_name
+                END AS qw_user_name,
+                qc.corp_name,
+                qu.corp_id
+            FROM fs_course_watch_log wl
+            INNER JOIN fs_user_course_video v ON v.video_id = wl.video_id
+            INNER JOIN (
+                SELECT course_id, MAX(create_time) AS max_time
+                FROM fs_course_watch_log
+                WHERE user_id = #{fsUserId}
+                GROUP BY course_id
+            ) lt ON lt.course_id = wl.course_id AND lt.max_time = wl.create_time
+            LEFT JOIN qw_user qu ON qu.id = wl.qw_user_id
+            LEFT JOIN qw_company qc ON qc.corp_id = qu.corp_id
+            WHERE wl.user_id = #{fsUserId}
+            GROUP BY wl.course_id
+        ) latest ON latest.course_id = watched.course_id
+        ORDER BY uc.course_id DESC
+    </select>
 </mapper>