Browse Source

Merge remote-tracking branch 'origin/中康智慧' into 中康智慧

yys 2 tuần trước cách đây
mục cha
commit
55e207f513

+ 60 - 0
fs-admin/src/main/java/com/fs/app/controller/statistic/CourseFinishAppReportController.java

@@ -0,0 +1,60 @@
+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.CourseFinishAppReportParam;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.his.vo.CourseFinishAppReportVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+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: AI
+ * @createDate: 2026/7/16
+ * @version: 1.0
+ */
+@RestController
+@RequestMapping("/app/statistics")
+public class CourseFinishAppReportController extends BaseController {
+
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+
+    /**
+     * APP完课统计报表(分页由Service层处理)
+     */
+    @PreAuthorize("@ss.hasPermi('app:statistics:courseFinishAppReport:list')")
+    @GetMapping("/courseFinishAppReport")
+    public TableDataInfo courseFinishAppReport(CourseFinishAppReportParam param) {
+        return fsCourseWatchLogService.selectCourseFinishAppReport(param);
+    }
+
+    /**
+     * 导出APP完课统计报表
+     */
+    @PreAuthorize("@ss.hasPermi('app:statistics:courseFinishAppReport:export')")
+    @GetMapping("/exportCourseFinishAppReport")
+    public AjaxResult exportCourseFinishAppReport(CourseFinishAppReportParam param) {
+        // 导出不分页,传null让service返回全量
+        Integer origPageNum = param.getPageNum();
+        Integer origPageSize = param.getPageSize();
+        param.setPageNum(null);
+        param.setPageSize(null);
+        TableDataInfo dataInfo = fsCourseWatchLogService.selectCourseFinishAppReport(param);
+        param.setPageNum(origPageNum);
+        param.setPageSize(origPageSize);
+
+        @SuppressWarnings("unchecked")
+        List<CourseFinishAppReportVO> list = (List<CourseFinishAppReportVO>) dataInfo.getRows();
+        ExcelUtil<CourseFinishAppReportVO> util = new ExcelUtil<CourseFinishAppReportVO>(CourseFinishAppReportVO.class);
+        return util.exportExcel(list, "APP完课统计报表");
+    }
+}

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

@@ -830,4 +830,9 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
      */
     @Select("select * from fs_course_watch_log where user_id = #{userId} and project = #{projectId} and create_time >= #{startTime}")
     FsCourseWatchLog selectByUserIdAndProjectId(@Param("userId") Long userId, @Param("projectId") Long projectId, @Param("startTime") Date startTime);
+
+    /**
+     * APP完课统计
+     */
+    List<CourseFinishAppReportVO> selectCourseFinishAppReport(CourseFinishAppReportParam param);
 }

+ 34 - 0
fs-service/src/main/java/com/fs/course/param/CourseFinishAppReportParam.java

@@ -0,0 +1,34 @@
+package com.fs.course.param;
+
+import lombok.Data;
+
+/**
+ * @description: APP完课统计查询参数
+ * @author: AI
+ * @createDate: 2026/7/16
+ * @version: 1.0
+ */
+@Data
+public class CourseFinishAppReportParam {
+
+    /** 开始日期 yyyy-MM-dd */
+    private String startDate;
+
+    /** 结束日期 yyyy-MM-dd */
+    private String endDate;
+
+    /** 维度类型: null=默认总计, company=公司维度, project=项目维度 */
+    private String dimensionType;
+
+    /** 公司ID(可选筛选) */
+    private Long companyId;
+
+    /** 项目ID(可选筛选) */
+    private Long projectId;
+
+    /** 页码 */
+    private Integer pageNum;
+
+    /** 每页条数 */
+    private Integer pageSize;
+}

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

@@ -2,6 +2,7 @@ package com.fs.course.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
@@ -9,6 +10,7 @@ import com.fs.his.vo.AppCourseReportVO;
 import com.fs.his.vo.AppSalesCourseStatisticsVO;
 import com.fs.his.vo.AppSalesWatchLogReportVO;
 import com.fs.his.vo.AppWatchLogReportVO;
+import com.fs.his.vo.CourseFinishAppReportVO;
 import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.qw.vo.QwWatchLogStatisticsListVO;
 
@@ -206,4 +208,11 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
      * 检查飞书看课状态
      */
     void checkFeiShuWatchStatus();
+
+    /**
+     * APP完课统计报表
+     * @param param
+     * @return
+     */
+    TableDataInfo selectCourseFinishAppReport(CourseFinishAppReportParam param);
 }

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

@@ -10,6 +10,7 @@ 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.page.TableDataInfo;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.base.BusinessException;
 import com.fs.common.utils.DateUtils;
@@ -90,6 +91,7 @@ import java.util.*;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -2993,4 +2995,87 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         }
     }
 
+    /**
+     * APP完课统计报表(含Redis缓存 + 手动分页)
+     * 缓存策略:
+     *   - 查询今天的:缓存10分钟
+     *   - 查询非今天的:缓存到当天24点失效
+     * 缓存的是全量数据(无分页),每次请求从缓存中取全量再分页返回
+     */
+    @Override
+    public TableDataInfo selectCourseFinishAppReport(CourseFinishAppReportParam param) {
+        String cacheKey = buildCourseFinishAppReportCacheKey(param);
+
+        // 从缓存获取全量数据
+        List<CourseFinishAppReportVO> allList = redisCache.getCacheObject(cacheKey);
+        if (allList == null) {
+            // 缓存未命中,查询数据库
+            allList = fsCourseWatchLogMapper.selectCourseFinishAppReport(param);
+
+            // 写入缓存
+            String today = DateUtils.dateTimeNow("yyyy-MM-dd");
+            boolean isToday = today.equals(param.getStartDate()) && today.equals(param.getEndDate());
+            if (isToday) {
+                redisCache.setCacheObject(cacheKey, allList, 10, TimeUnit.MINUTES);
+            } else {
+                long secondsUntilMidnight = getSecondsUntilMidnight();
+                redisCache.setCacheObject(cacheKey, allList, (int) secondsUntilMidnight, TimeUnit.SECONDS);
+            }
+        }
+
+        // 手动分页(pageNum 和 pageSize 均为 null 时返回全量,用于导出)
+        boolean needPage = param.getPageNum() != null && param.getPageSize() != null;
+        int total = allList.size();
+        List<CourseFinishAppReportVO> pageList;
+        if (needPage) {
+            int pageNum = param.getPageNum();
+            int pageSize = param.getPageSize();
+            int fromIndex = (pageNum - 1) * pageSize;
+            int toIndex = Math.min(fromIndex + pageSize, total);
+            if (fromIndex >= total) {
+                pageList = Collections.emptyList();
+            } else {
+                pageList = allList.subList(fromIndex, toIndex);
+            }
+        } else {
+            pageList = allList;
+        }
+
+        TableDataInfo tableDataInfo = new TableDataInfo();
+        tableDataInfo.setRows(pageList);
+        tableDataInfo.setTotal(total);
+        return tableDataInfo;
+    }
+
+    /**
+     * 构建缓存Key
+     */
+    private String buildCourseFinishAppReportCacheKey(CourseFinishAppReportParam param) {
+        StringBuilder sb = new StringBuilder("course:finish:app:report:");
+        String dimType = (param.getDimensionType() != null && !param.getDimensionType().isEmpty())
+                ? param.getDimensionType() : "default";
+        sb.append(dimType);
+        sb.append(":").append(param.getStartDate());
+        sb.append(":").append(param.getEndDate());
+        if (param.getCompanyId() != null) {
+            sb.append(":company_").append(param.getCompanyId());
+        }
+        if (param.getProjectId() != null) {
+            sb.append(":project_").append(param.getProjectId());
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 计算距离当天24点剩余的秒数
+     */
+    private long getSecondsUntilMidnight() {
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.DAY_OF_MONTH, 1);
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        return (cal.getTimeInMillis() - System.currentTimeMillis()) / 1000;
+    }
 }

+ 40 - 0
fs-service/src/main/java/com/fs/his/vo/CourseFinishAppReportVO.java

@@ -0,0 +1,40 @@
+package com.fs.his.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+/**
+ * @description: APP完课统计 VO
+ * @author: AI
+ * @createDate: 2026/7/16
+ * @version: 1.0
+ */
+@Data
+public class CourseFinishAppReportVO {
+
+    /** 总完播数 */
+    @Excel(name = "总完播")
+    private Integer totalFinishCount;
+
+    /** APP完播数 */
+    @Excel(name = "APP完播")
+    private Integer appFinishCount;
+
+    /** 统计日期 */
+    @Excel(name = "时间")
+    private String statDate;
+
+    /** 公司ID(公司维度时使用) */
+    private Long companyId;
+
+    /** 公司名称 */
+    @Excel(name = "公司名称")
+    private String companyName;
+
+    /** 项目ID(项目维度时使用) */
+    private Long projectId;
+
+    /** 项目名称 */
+    @Excel(name = "项目名称")
+    private String projectName;
+}

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

@@ -1862,4 +1862,52 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                      #{remark}
                  )
     </insert>
+
+    <!-- APP完课统计:日期仅作为筛选条件,不作为分组维度 -->
+    <select id="selectCourseFinishAppReport" resultType="com.fs.his.vo.CourseFinishAppReportVO">
+        SELECT
+        <choose>
+            <when test="dimensionType == 'company'">
+                fwl.company_id AS companyId,
+                c.company_name AS companyName,
+            </when>
+            <when test="dimensionType == 'project'">
+                fwl.project AS projectId,
+                d.dict_label AS projectName,
+            </when>
+        </choose>
+        COUNT(*) AS totalFinishCount,
+        SUM(CASE WHEN fwl.watch_type = 1 THEN 1 ELSE 0 END) AS appFinishCount,
+        CONCAT(#{startDate}, ' ~ ', #{endDate}) AS statDate
+        FROM fs_course_watch_log fwl
+        <if test="dimensionType == 'company'">
+            LEFT JOIN company c ON c.company_id = fwl.company_id
+        </if>
+        <if test="dimensionType == 'project'">
+            LEFT JOIN sys_dict_data d ON d.dict_type = 'sys_course_project' AND d.dict_value = CAST(fwl.project AS CHAR)
+        </if>
+        WHERE fwl.log_type = 2
+        <if test="startDate != null and startDate != ''">
+            AND fwl.create_time &gt;= CONCAT(#{startDate}, ' 00:00:00')
+        </if>
+        <if test="endDate != null and endDate != ''">
+            AND fwl.create_time &lt;= CONCAT(#{endDate}, ' 23:59:59')
+        </if>
+        <if test="companyId != null">
+            AND fwl.company_id = #{companyId}
+        </if>
+        <if test="projectId != null">
+            AND fwl.project = #{projectId}
+        </if>
+        <choose>
+            <when test="dimensionType == 'company'">
+                GROUP BY fwl.company_id
+                ORDER BY fwl.company_id
+            </when>
+            <when test="dimensionType == 'project'">
+                GROUP BY fwl.project
+                ORDER BY fwl.project
+            </when>
+        </choose>
+    </select>
 </mapper>