Pārlūkot izejas kodu

绑定微信假删除,app统计,红包记录添加watch_type

xgb 1 mēnesi atpakaļ
vecāks
revīzija
8292f1bc04
19 mainītis faili ar 485 papildinājumiem un 10 dzēšanām
  1. 56 0
      fs-admin/src/main/java/com/fs/app/controller/statistic/courseStatisticController.java
  2. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  3. 3 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseAnswerLogs.java
  4. 9 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  5. 2 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogStatisticsListParam.java
  6. 10 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  7. 1 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java
  8. 162 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  9. 4 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  10. 23 0
      fs-service/src/main/java/com/fs/his/dto/AppUserCompanyDTO.java
  11. 9 2
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  12. 2 1
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  13. 87 0
      fs-service/src/main/java/com/fs/his/vo/AppCourseReportVO.java
  14. 9 0
      fs-service/src/main/resources/mapper/company/CompanyMapper.xml
  15. 2 0
      fs-service/src/main/resources/mapper/course/FsCourseAnswerLogsMapper.xml
  16. 66 0
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  17. 19 0
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  18. 17 7
      fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java
  19. 1 0
      fs-user-app/src/main/java/com/fs/app/controller/IntegralController.java

+ 56 - 0
fs-admin/src/main/java/com/fs/app/controller/statistic/courseStatisticController.java

@@ -0,0 +1,56 @@
+package com.fs.app.controller.statistic;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.his.vo.AppCourseReportVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @description: app 看课统计
+ * @author: Xgb
+ * @createDate: 2026/3/16
+ * @version: 1.0
+ */
+@RestController
+@RequestMapping("/app/statistics")
+public class courseStatisticController extends BaseController {
+
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+
+
+    /**
+     * app看课统计报表
+     * @param param
+     * @return
+     */
+    @GetMapping("/appCourseReport")
+    public TableDataInfo selectFsAppCourseReportVO(FsCourseWatchLogStatisticsListParam param) {
+        startPage();
+        List<AppCourseReportVO> list = fsCourseWatchLogService.selectAppCourseReportVO(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出app看课统计报表
+     * @param param
+     * @return
+     */
+    @GetMapping("/exportAppCourseReport")
+    public AjaxResult exportAppCourseReport(FsCourseWatchLogStatisticsListParam param) {
+        List<AppCourseReportVO> list = fsCourseWatchLogService.selectAppCourseReportVO(param);
+        ExcelUtil<AppCourseReportVO> util = new ExcelUtil<AppCourseReportVO>(AppCourseReportVO.class);
+        return util.exportExcel(list, "APP看课统计报表");
+    }
+
+
+}

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

@@ -11,6 +11,7 @@ import com.fs.company.param.CompanyParam;
 import com.fs.company.vo.CompanyCrmVO;
 import com.fs.company.vo.CompanyNameVO;
 import com.fs.company.vo.CompanyVO;
+import com.fs.his.vo.AppCourseReportVO;
 import com.fs.his.vo.OptionsVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -253,4 +254,6 @@ public interface CompanyMapper
 
     @Select("select company_id,company_name from company where FIND_IN_SET( company_id , (select company_ids from qw_company where corp_id = #{corpId})) ")
     List<Company> getCompanyList(@Param("corpId") String corpId);
+
+    List<AppCourseReportVO> selectAllCompanies(@Param("companyId") Long companyId);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseAnswerLogs.java

@@ -69,4 +69,7 @@ public class FsCourseAnswerLogs extends BaseEntity {
 
     /** 营期id */
     private Long periodId;
+
+    // 看课方式:1 app  2 小程序
+    private Integer watchType;
 }

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

@@ -5,6 +5,7 @@ import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.dto.WatchLogDTO;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
+import com.fs.his.vo.AppCourseReportVO;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.sop.vo.QwRatingVO;
@@ -741,4 +742,12 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
     List<FsUserCourseAppListVO> selectCourseByUserIdForStatusNotFinish(Long userId);
 
     FsUserCourseAppListVO getAppCourseLearningOne(long userId);
+
+    int selectActiveUserIds(ArrayList<Long> longs);
+
+    List<AppCourseReportVO> selectAppWatchStatistics(FsCourseWatchLogStatisticsListParam param);
+
+    List<AppCourseReportVO> selectAppAnswerStatistics(FsCourseWatchLogStatisticsListParam param);
+
+    List<AppCourseReportVO> selectAppRedPacketStatistics(FsCourseWatchLogStatisticsListParam param);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogStatisticsListParam.java

@@ -81,4 +81,6 @@ public class FsCourseWatchLogStatisticsListParam {
         });
         return longs;
     }
+
+    private String appId;
 }

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

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
+import com.fs.his.vo.AppCourseReportVO;
 import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.qw.vo.QwWatchLogStatisticsListVO;
 
@@ -167,4 +168,13 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
      * @return
      */
     List<FsCourseReportVO> selectFsCourseReportVO(FsCourseWatchLogStatisticsListParam param);
+
+
+    /**
+     * app端看课统计报表
+     * @param param
+     * @return
+     */
+    List<AppCourseReportVO> selectAppCourseReportVO(FsCourseWatchLogStatisticsListParam param);
+
 }

+ 1 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java

@@ -229,6 +229,7 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         logs.setQuestionJson(JSONObject.toJSONString(questions));
         logs.setCreateTime(new Date());
         logs.setPeriodId(param.getPeriodId());
+        logs.setWatchType(log.getWatchType());
 
         if (thisRightCount == questions.size()) {
             logs.setIsRight(1);

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

@@ -29,10 +29,14 @@ import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
 import com.fs.course.vo.*;
+import com.fs.his.config.AppConfig;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.FsUser;
+import com.fs.his.dto.AppUserCompanyDTO;
+import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
+import com.fs.his.vo.AppCourseReportVO;
 import com.fs.qw.Bean.MsgBean;
 import com.fs.qw.cache.IQwExternalContactCacheService;
 import com.fs.qw.cache.IQwUserCacheService;
@@ -51,9 +55,11 @@ import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.mapper.SopUserLogsMapper;
 import com.fs.store.service.cache.IFsUserCacheService;
 import com.fs.store.service.cache.IFsUserCourseCacheService;
+import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
 import com.fs.tag.service.FsTagUpdateService;
 import com.github.pagehelper.PageHelper;
+import com.google.gson.Gson;
 import com.hc.openapi.tool.util.StringUtils;
 import org.apache.commons.collections4.CollectionUtils;
 import org.slf4j.Logger;
@@ -155,6 +161,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private FinishCourseStatisticsSyncMapper finishCourseStatisticsSyncMapper;
 
+    @Autowired
+    private FsUserMapper userMapper;
+
     /**
      * 查询短链课程看课记录
      *
@@ -1692,6 +1701,159 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseReportVOS;
     }
 
+    /**
+     * @Description: app 看课统计
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2026/3/16 16:39
+     */
+    @Override
+    public List<AppCourseReportVO> selectAppCourseReportVO(FsCourseWatchLogStatisticsListParam param) {
+        // 时间转字符串
+        if (param.getSTime() != null && param.getETime() != null) {
+            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
+            param.setStartDate(simpleDateFormat.format(param.getSTime()));
+            param.setEndDate(simpleDateFormat.format(param.getETime()));
+        }
+
+        // 1. 查询所有公司列表
+        List<AppCourseReportVO> allCompanies = companyMapper.selectAllCompanies(param.getCompanyId());
+        if (CollectionUtils.isEmpty(allCompanies)) {
+            return Collections.emptyList();
+        }
+
+        // 2. 查询指定公司和时间范围内的 APP 会员
+//        List<AppUserCompanyDTO> appUserList = userMapper.selectAppUserListForActiveCount(param);
+
+        // 3. 如果有APP会员数据,则统计活跃用户数
+//        Map<Long, int[]> companyStatsMap = null;
+//        if (!CollectionUtils.isEmpty(appUserList)) {
+//            // 提取所有唯一的 userId 并查询活跃用户
+//            Set<Long> allUserIds = appUserList.stream()
+//                    .map(AppUserCompanyDTO::getUserId)
+//                    .collect(Collectors.toSet());
+//
+//            Set<Long> activeUserSet = new HashSet<>(
+//                    fsCourseWatchLogMapper.selectActiveUserIds(new ArrayList<>(allUserIds))
+//            );
+//
+//            // 按公司分组统计 APP 会员数据 [总数, 活跃数]
+//            companyStatsMap = new HashMap<>();
+//            for (AppUserCompanyDTO dto : appUserList) {
+//                Long cid = dto.getCompanyId();
+//                int[] stats = companyStatsMap.computeIfAbsent(cid, k -> new int[]{0, 0});
+//                stats[0]++;
+//                if (activeUserSet.contains(dto.getUserId())) {
+//                    stats[1]++;
+//                }
+//            }
+//        }
+
+        // 查询用户注册数
+        List<Map<String, Object>> registerCountMap = userMapper.selectRegisterCount(param.getCompanyId(), param.getStartDate(), param.getEndDate());
+        Map<Long, Integer> countMap = registerCountMap.stream()
+                .collect(Collectors.toMap(
+                        map ->  map.get("companyId")==null?0L:Long.parseLong(map.get("companyId").toString()),
+                        map -> Integer.parseInt(map.get("registerCount").toString())
+                ));
+
+        // 4. 批量查询看课、答题、红包统计数据
+        List<Long> companyIds = allCompanies.stream()
+                .map(AppCourseReportVO::getCompanyId)
+                .collect(Collectors.toList());
+        param.setCompanyIds(companyIds);
+
+        // 并行查询三个统计数据源
+        List<AppCourseReportVO> watchStatsList = fsCourseWatchLogMapper.selectAppWatchStatistics(param);
+        List<AppCourseReportVO> answerList = fsCourseWatchLogMapper.selectAppAnswerStatistics(param);
+        SysConfig sysConfig = configService.selectConfigByConfigKey("app.config");
+        AppConfig config = new Gson().fromJson(sysConfig.getConfigValue(), AppConfig.class);
+        param.setAppId(config.getAppId());
+        List<AppCourseReportVO> redpackList = fsCourseWatchLogMapper.selectAppRedPacketStatistics(param);
+
+        // 5. 转换为 Map 便于查找
+        Map<Long, AppCourseReportVO> watchStatsMap = watchStatsList.stream()
+                .collect(Collectors.toMap(AppCourseReportVO::getCompanyId, Function.identity()));
+        Map<Long, AppCourseReportVO> answerStatsMap = answerList.stream()
+                .collect(Collectors.toMap(AppCourseReportVO::getCompanyId, Function.identity(), (e, r) -> e));
+        Map<Long, AppCourseReportVO> redPacketStatsMap = redpackList.stream()
+                .collect(Collectors.toMap(AppCourseReportVO::getCompanyId, Function.identity(), (e, r) -> e));
+
+        // 6. 一次性组装所有数据,减少遍历次数
+        for (AppCourseReportVO vo : allCompanies) {
+            Long companyId = vo.getCompanyId();
+
+            // APP会员统计
+//            if (companyStatsMap != null) {
+//                int[] stats = companyStatsMap.getOrDefault(companyId, new int[]{0, 0});
+//                vo.setAppUserCount(stats[0]);
+//                vo.setActiveAppUserCount(stats[1]);
+//            } else {
+//                vo.setAppUserCount(0);
+//                vo.setActiveAppUserCount(0);
+//            }
+            vo.setAppUserCount(countMap.getOrDefault(companyId, 0));
+
+            // 看课统计 - 如果查不到数据就用0填充
+            AppCourseReportVO watchStats = watchStatsMap.get(companyId);
+            if (watchStats != null) {
+                vo.setPendingCount(watchStats.getPendingCount() != null ? watchStats.getPendingCount() : 0);
+                vo.setWatchingCount(watchStats.getWatchingCount() != null ? watchStats.getWatchingCount() : 0);
+                vo.setFinishedCount(watchStats.getFinishedCount() != null ? watchStats.getFinishedCount() : 0);
+                vo.setAccessCount(watchStats.getAccessCount() != null ? watchStats.getAccessCount() : 0);
+                vo.setStopCount(watchStats.getStopCount() != null ? watchStats.getStopCount() : 0);
+                vo.setWatchRate(calculateWatchingRate(
+                        vo.getWatchingCount(),
+                        vo.getFinishedCount(),
+                        vo.getAccessCount()));
+            } else {
+                // 查不到看课数据时设置默认值0
+                vo.setPendingCount(0);
+                vo.setWatchingCount(0);
+                vo.setFinishedCount(0);
+                vo.setAccessCount(0);
+                vo.setStopCount(0);
+                vo.setWatchRate(BigDecimal.ZERO);
+            }
+
+            // 答题统计 - 如果查不到数据就用0填充
+            AppCourseReportVO answerStats = answerStatsMap.getOrDefault(companyId, new AppCourseReportVO());
+            vo.setAnswerUserCount(answerStats.getAnswerUserCount() != null ? answerStats.getAnswerUserCount() : 0);
+
+            // 红包统计 - 如果查不到数据就用0填充
+            AppCourseReportVO redPacketStats = redPacketStatsMap.getOrDefault(companyId, new AppCourseReportVO());
+            vo.setPacketUserCount(redPacketStats.getPacketUserCount() != null ? redPacketStats.getPacketUserCount() : 0);
+            vo.setPacketAmount(redPacketStats.getPacketAmount() != null ? redPacketStats.getPacketAmount() : BigDecimal.ZERO);
+        }
+
+        return allCompanies;
+    }
+    /**
+     * app 看课率((看课中人次+完课人次)/ 私域课总人次)
+     * @param watchingCount 私域看课中人次
+     * @param finishedCount 私域完课人次
+     * @param totalCount 私域课总人次
+     * @return 看课率(百分比,保留 2 位小数)
+     */
+    private BigDecimal calculateWatchingRate(Integer watchingCount, Integer finishedCount, Integer totalCount) {
+        // 防止除以 0
+        if (totalCount == null || totalCount == 0) {
+            return BigDecimal.ZERO;
+        }
+
+        // 防止空指针
+        int watching = watchingCount != null ? watchingCount : 0;
+        int finished = finishedCount != null ? finishedCount : 0;
+
+        // 看课率 = (看课中人次 + 完课人次) / 总人次 * 100%
+        return BigDecimal.valueOf(watching + finished)
+                .divide(BigDecimal.valueOf(totalCount), 4, RoundingMode.HALF_UP)
+                .multiply(BigDecimal.valueOf(100))
+                .setScale(2, RoundingMode.HALF_UP);
+    }
+
+
     /**
      * 批量设置课程和视频名称
      *

+ 4 - 0
fs-service/src/main/java/com/fs/his/domain/FsUser.java

@@ -246,6 +246,10 @@ public class FsUser extends BaseEntity
     @TableField(exist = false)
     private String nicknameExact;
 
+    // app注册时间
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date appCreateTime;
+
 //    /**
 //     * 搜索关键词-电话号码/会员id/会员昵称
 //     * **/

+ 23 - 0
fs-service/src/main/java/com/fs/his/dto/AppUserCompanyDTO.java

@@ -0,0 +1,23 @@
+package com.fs.his.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class AppUserCompanyDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 公司 ID */
+    private Long companyId;
+
+    /** 公司名称 */
+    private String companyName;
+
+    /** 用户 ID */
+    private Long userId;
+
+    /** 销售 ID */
+    private Long companyUserId;
+}

+ 9 - 2
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -7,8 +7,10 @@ import java.util.Map;
 import com.fs.course.domain.FsUserWatchCourseStatistics;
 import com.fs.course.domain.FsUserWatchStatistics;
 import com.fs.course.param.CourseAnalysisParam;
+import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
 import com.fs.course.vo.newfs.FsCourseAnalysisCountVO;
 import com.fs.his.domain.FsUser;
+import com.fs.his.dto.AppUserCompanyDTO;
 import com.fs.his.dto.FindUsersByDTO;
 import com.fs.his.param.FindUserByParam;
 import com.fs.his.param.FsUserParam;
@@ -137,6 +139,9 @@ public interface FsUserMapper
     @Update("update fs_user set is_del=1 where user_id=#{userId}")
     int updateFsUserByUserId(Long userId);
 
+    @Update("update fs_user set is_del=1,remark=#{username} where user_id=#{userId}")
+    int updateFsUserByUserId(@Param("userId") Long userId,@Param("username") String username);
+
     @Select("select f1.*,f2.nick_name tui_name,f2.phone tui_phone FROM fs_user f1 LEFT JOIN fs_user f2 ON f1.tui_user_id =f2.user_id where f1.user_id=#{userId} ")
     FsUserVO selectFsUserVoByUserId(Long userId);
 
@@ -150,7 +155,7 @@ public interface FsUserMapper
     @Select("select * from fs_user where is_del = 0 and phone=#{phone}")
     FsUser selectFsUserByPhone(String phone);
 
-    @Select("select * from fs_user where phone=#{phone} limit 1")
+    @Select("select * from fs_user where is_del=0 and  phone=#{phone} limit 1")
     FsUser selectFsUserByPhoneLimitOne(String phone);
     @Select({"<script> " +
             "select distinct f1.*,f2.nick_name tui_name,f2.phone tui_phone FROM company_user_user cuu LEFT JOIN fs_user f1 ON cuu.user_id =f1.user_id left JOIN fs_user f2 ON f1.tui_user_id =f2.user_id"+
@@ -201,7 +206,7 @@ public interface FsUserMapper
     List<FsUserVO> selectFsUserListVOByComponent(FsUserParam fsUser);
 
 
-    @Select("select * from fs_user where union_id=#{unionid} limit 1")
+    @Select("select * from fs_user where is_del=0  and union_id=#{unionid} limit 1")
     FsUser selectFsUserByUnionid(String unionid);
 
     @Select("<script>" +
@@ -479,4 +484,6 @@ public interface FsUserMapper
 
     @Select("select * from fs_user where apple_key = #{appleKey}")
     FsUser findUserByAppleKey(String appleKey);
+
+    List<Map<String, Object>>  selectRegisterCount(@Param("companyId") Long companyId,@Param("startDate") String startDate,@Param("endDate") String endDate);
 }

+ 2 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java

@@ -295,7 +295,8 @@ public class FsUserServiceImpl implements IFsUserService {
      */
     @Override
     public int deleteFsUserByUserId(Long userId) {
-        return fsUserMapper.updateFsUserByUserId(userId);
+        String username = "绑定手机号同步了用户";
+        return fsUserMapper.updateFsUserByUserId(userId,username);
     }
 
     @Override

+ 87 - 0
fs-service/src/main/java/com/fs/his/vo/AppCourseReportVO.java

@@ -0,0 +1,87 @@
+package com.fs.his.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class AppCourseReportVO {
+    /** 公司id */
+    private  Long companyId;
+
+    /** 公司名称 */
+    @Excel(name = "销售公司")
+    private String companyName;
+
+    /**
+     * app新增注册人数
+     */
+    @Excel(name = "新增注册人数")
+    private  Integer appUserCount;
+
+    /** 活跃 APP 会员数 */
+    @Excel(name = "APP活跃人数")
+    private Integer activeAppUserCount;
+
+
+    /**
+     * 待看课人数
+     */
+    @Excel(name = "私域课待看课人次")
+    private  Integer pendingCount;
+
+    /**
+     * 看课中人数
+     */
+    @Excel(name = "私域课看课中人次")
+    private  Integer watchingCount;
+
+    /**
+     * 看课中人数
+     */
+    @Excel(name = "私域课看课中断人次")
+    private  Integer stopCount;
+
+
+    /**
+     * 完课人数
+     */
+    @Excel(name = "私域课完课人次")
+    private  Integer finishedCount;
+
+
+    /**
+     * 看课率
+     */
+    @Excel(name = "看课率")
+    private BigDecimal watchRate;
+
+    /**
+     * 答题人数
+     */
+    @Excel(name = "答题人次")
+    private  Integer answerUserCount;
+
+    /**
+     * 红包领取数
+     */
+    @Excel(name = "红包领取人次")
+    private  Integer packetUserCount;
+
+    /**
+     * 红包金额
+     */
+    @Excel(name = "红包金额")
+    private  BigDecimal packetAmount;
+
+    /**
+     * 总人数
+     */
+    private  Integer  accessCount;
+
+    /**
+     * 日志id
+     */
+    private  Long logId;
+}

+ 9 - 0
fs-service/src/main/resources/mapper/company/CompanyMapper.xml

@@ -271,6 +271,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         select company_id,company_name,money,red_package_money from company where is_del= 0
     </select>
 
+    <select id="selectAllCompanies" resultType="com.fs.his.vo.AppCourseReportVO">
+        SELECT company_id as companyId, company_name as companyName FROM company WHERE status = 1
+        <if test="companyId != null">
+            and company_id = #{companyId}
+        </if>
+        GROUP BY company_id
+        ORDER BY company_id DESC
+    </select>
+
     <update id="batchUpdateCompany" parameterType="java.util.List">
         update company set money =
         <foreach collection="list" item="company"  index="index" separator=" " open="case company_id" close="end">

+ 2 - 0
fs-service/src/main/resources/mapper/course/FsCourseAnswerLogsMapper.xml

@@ -57,6 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="questionJson != null">question_json,</if>
             <if test="watchLogId != null">watch_log_id,</if>
             <if test="periodId != null">period_id,</if>
+            <if test="watchType != null">watch_type,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="userId != null">#{userId},</if>
@@ -70,6 +71,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="questionJson != null">#{questionJson},</if>
             <if test="watchLogId != null">#{watchLogId},</if>
             <if test="periodId != null">#{periodId},</if>
+            <if test="watchType != null">#{watchType},</if>
         </trim>
     </insert>
 

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

@@ -1315,4 +1315,70 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </choose>
         ORDER BY accessCount desc
     </select>
+
+<!--    <select id="selectActiveUserIds" resultType="java.lang.Long">-->
+<!--        SELECT DISTINCT user_id-->
+<!--        FROM fs_course_watch_log-->
+<!--        WHERE user_id IN-->
+<!--        <foreach item="userId" collection="userIds" open="(" separator="," close=")">-->
+<!--            #{userId}-->
+<!--        </foreach>-->
+<!--        and send_type = 1-->
+<!--        and watch_type = 1-->
+<!--    </select>-->
+
+    <select id="selectAppWatchStatistics" resultType="com.fs.his.vo.AppCourseReportVO">
+        SELECT
+        company_id AS companyId,
+        COUNT(CASE WHEN log_type = 4 THEN log_id END) AS stopCount,
+        COUNT(CASE WHEN log_type = 3 THEN log_id END) AS pendingCount,
+        COUNT(CASE WHEN log_type = 1 THEN log_id END) AS watchingCount,
+        COUNT(CASE WHEN log_type = 2 THEN log_id END) AS finishedCount,
+        count(log_id) as accessCount
+        FROM fs_course_watch_log
+        <where>
+            watch_type =1 and send_type = 1
+            <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+                AND create_time &gt;= #{startDate} AND create_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
+            </if>
+            <if test="companyIds != null and companyIds.size() > 0">
+                AND company_id IN
+                <foreach collection="companyIds" item="companyId" open="(" separator="," close=")">
+                    #{companyId}
+                </foreach>
+            </if>
+        </where>
+        GROUP BY company_id
+    </select>
+    <select id="selectAppAnswerStatistics" resultType="com.fs.his.vo.AppCourseReportVO">
+        SELECT
+        l.company_id AS companyId,
+        COUNT( l.log_id) AS answerUserCount
+        FROM fs_course_answer_logs l
+        WHERE l.company_id IN
+        <foreach collection="companyIds" item="companyId" open="(" separator="," close=")">
+            #{companyId}
+        </foreach>
+        and l.watch_type=1
+        <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+            AND l.create_time &gt;= #{startDate} AND l.create_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
+        </if>
+        GROUP BY l.company_id
+    </select>
+    <select id="selectAppRedPacketStatistics" resultType="com.fs.his.vo.AppCourseReportVO">
+        SELECT
+        rpl.company_id AS companyId,
+        COUNT( rpl.log_id) AS packetUserCount,
+        COALESCE(SUM(rpl.amount), 0) AS packetAmount
+        FROM fs_course_red_packet_log rpl
+        WHERE rpl.company_id IN
+        <foreach collection="companyIds" item="companyId" open="(" separator="," close=")">
+            #{companyId}
+        </foreach>
+        and rpl.app_id=#{appId}
+        <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+            AND rpl.create_time &gt;= #{startDate} AND rpl.create_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
+        </if>
+        GROUP BY rpl.company_id
+    </select>
 </mapper>

+ 19 - 0
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -606,6 +606,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="appId != null">app_id,</if>
             <if test="appOpenId != null">app_open_id,</if>
             <if test="appleKey != null">apple_key,</if>
+            <if test="appCreateTime != null">app_create_time,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="nickName != null">#{nickName},</if>
@@ -658,6 +659,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="appId != null">#{appId},</if>
             <if test="appOpenId != null">#{appOpenId},</if>
             <if test="appleKey != null">#{appleKey},</if>
+            <if test="appCreateTime != null">#{appCreateTime},</if>
          </trim>
     </insert>
 
@@ -713,6 +715,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="invitedBySalesId != null">invited_by_sales_id = #{invitedBySalesId},</if>
             <if test="appOpenId != null">app_open_id = #{appOpenId},</if>
             <if test="appleKey != null">apple_key = #{appleKey},</if>
+            <if test="appCreateTime != null">app_create_time = #{appCreateTime},</if>
         </trim>
         where user_id = #{userId}
     </update>
@@ -2458,5 +2461,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         order by user_id desc
     </select>
 
+    <select id="selectRegisterCount" resultType="java.util.Map">
+        SELECT
+        ucu.company_id AS companyId,
+        count(distinct ucu.user_id) AS registerCount
+        FROM fs_user u
+        left join fs_user_company_user ucu on u.user_id = ucu.user_id
+        WHERE u.status = 1
+        <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+            AND u.app_create_time &gt;=  #{startDate} AND  u.app_create_time &lt;= #{endDate}
+        </if>
+        <if test="companyId != null">
+            AND ucu.company_id = #{companyId}
+        </if>
+        group by ucu.company_id
+    </select>
+
 
 </mapper>

+ 17 - 7
fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java

@@ -310,6 +310,7 @@ public class AppLoginController extends AppBaseController{
                 // 新用户 - 添加 appId
                 user.setAppId(appId);
                 user.setCreateTime(new Date());
+                user.setAppCreateTime(new Date());
                 user.setStatus(1);
                 if (StringUtils.isNotEmpty(param.getJpushId())) {
                     user.setJpushId(param.getJpushId());
@@ -325,17 +326,23 @@ public class AppLoginController extends AppBaseController{
             } else {
                 // 老用户 - 检查并添加appId(不重复添加)
                 String updatedAppId = addAppIdIfNotExists(user.getAppId(), appId);
+
+                FsUser userMap = new FsUser();
+                userMap.setUserId(user.getUserId());
                 if (!updatedAppId.equals(user.getAppId())) {
-                    FsUser userMap = new FsUser();
-                    userMap.setUserId(user.getUserId());
                     userMap.setAppId(updatedAppId);
-                    userService.updateFsUser(userMap);
                 }
-
                 if (StringUtils.isNotEmpty(param.getJpushId())) {
-                    user.setAppOpenId(openid);
-                    updateExistingUserJpushId(user, param.getJpushId());
+                    userMap.setJpushId(param.getJpushId());
                 }
+                if (StringUtils.isNotEmpty(openid)) {
+                    userMap.setAppOpenId(openid);
+                }
+                if(user.getAppCreateTime()== null){
+                    userMap.setAppCreateTime(new Date());
+                }
+                userService.updateFsUser(userMap);
+
                 if (StringUtils.isEmpty(user.getPhone())) {
                     String token = jwtUtils.generateToken(user.getUserId());
                     redisCache.setCacheObject("userToken:" + user.getUserId(), token, 604800, TimeUnit.SECONDS);
@@ -621,10 +628,12 @@ public class AppLoginController extends AppBaseController{
                     keepUser.setSource(param.getSource() != null ? param.getSource() : null );
                     keepUser.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);
                     keepUser.setNickName(nickname);
+                    keepUser.setIntegral(keepUser.getIntegral()+deleteUser.getIntegral());
                     keepUser.setAvatar(avatar);
                     keepUser.setSex(sex);
                     if (userService.updateFsUser(keepUser)>0){
-                        userService.realDeleteFsUserByUserId(deleteUser.getUserId());
+//                        userService.realDeleteFsUserByUserId(deleteUser.getUserId());
+                        userService.deleteFsUserByUserId(deleteUser.getUserId());
                         return generateTokenAndReturn(keepUser);
                     }
                     else {
@@ -900,6 +909,7 @@ public class AppLoginController extends AppBaseController{
         newUser.setPhone(param.getPhone());
         newUser.setCreateTime(new Date());
         newUser.setStatus(1);
+        newUser.setAppCreateTime(new Date());
         newUser.setAvatar("https://cos.his.cdwjyyh.com/fs/20240926/420728ee06e54575ba82665dedb4756b.png");
         if (StringUtils.isNotEmpty(param.getJpushId())) {
             newUser.setJpushId(param.getJpushId());

+ 1 - 0
fs-user-app/src/main/java/com/fs/app/controller/IntegralController.java

@@ -219,6 +219,7 @@ public class IntegralController extends  AppBaseController {
         return R.ok("签到获得" + integral + "积分");
     }
 
+    // app 短视频获取积分
     @Login
     @ApiOperation("获取积分")
     @PostMapping("/addIntegral")