Quellcode durchsuchen

1、新增会员每日看课统计统计
2、会员看课统计列表展示数据

yfh vor 1 Woche
Ursprung
Commit
7dd98d460a

+ 172 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCompanyStatisticsController.java

@@ -0,0 +1,172 @@
+package com.fs.course.controller;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import com.fs.common.exception.ServiceException;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.course.domain.FsUserCourseCompanyStatistics;
+import com.fs.course.service.IFsUserCourseCompanyStatisticsService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 会员每日看课统计Controller
+ *
+ * @author fs
+ * @date 2025-10-27
+ */
+@RestController
+@RequestMapping("/course/statistics")
+public class FsUserCourseCompanyStatisticsController extends BaseController
+{
+    @Autowired
+    private IFsUserCourseCompanyStatisticsService fsUserCourseCompanyStatisticsService;
+
+    /**
+     * 查询会员每日看课统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:statistics:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics)
+    {
+        if (fsUserCourseCompanyStatistics.getBeginTime() == null || fsUserCourseCompanyStatistics.getEndTime() == null) {
+            throw new ServiceException("请选择开始时间和结束时间!");
+        }
+
+        startPage();
+        List<FsUserCourseCompanyStatistics> list =
+                fsUserCourseCompanyStatisticsService.selectFsUserCourseCompanyStatisticsTotal(fsUserCourseCompanyStatistics);
+        Optional.ofNullable(list).orElse(Collections.emptyList())
+                .forEach(item -> {
+                    // 完播率
+                    Long watchCount = item.getWatchCount() != null ? item.getWatchCount() : 0L;
+                    Long completeWatchCount = item.getCompleteWatchCount() != null ? item.getCompleteWatchCount() : 0L;
+                    if (watchCount > 0) {
+                        BigDecimal rate = BigDecimal.valueOf(completeWatchCount)
+                                .multiply(BigDecimal.valueOf(100))
+                                .divide(BigDecimal.valueOf(watchCount), 2, RoundingMode.HALF_UP);
+                        item.setCompleteRate(rate.longValue());
+                    } else {
+                        item.setCompleteRate(0L);
+                    }
+
+                    // 正确率
+                    Long answerCount = item.getAnswerCount() != null ? item.getAnswerCount() : 0L;
+                    Long correctCount = item.getCorrectCount() != null ? item.getCorrectCount() : 0L;
+                    if (answerCount > 0) {
+                        BigDecimal rate = BigDecimal.valueOf(correctCount)
+                                .multiply(BigDecimal.valueOf(100))
+                                .divide(BigDecimal.valueOf(answerCount), 2, RoundingMode.HALF_UP);
+                        item.setCorrectRate(rate.longValue());
+                    } else {
+                        item.setCorrectRate(0L);
+                    }
+                });
+
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出会员每日看课统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:statistics:export')")
+    @Log(title = "会员每日看课统计", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics)
+    {
+        List<FsUserCourseCompanyStatistics> list =
+                fsUserCourseCompanyStatisticsService.selectFsUserCourseCompanyStatisticsTotal(fsUserCourseCompanyStatistics);
+
+        Optional.ofNullable(list).orElse(Collections.emptyList())
+                .forEach(item -> {
+                    // 计算完播率 (完播次数 / 观看次数 * 100)
+                    item.setCompleteRate(
+                            Optional.ofNullable(item.getWatchCount())
+                                    .filter(watchCount -> watchCount > 0)
+                                    .map(watchCount -> BigDecimal.valueOf(
+                                                    Optional.ofNullable(item.getCompleteWatchCount()).orElse(0L))
+                                            .multiply(BigDecimal.valueOf(100))
+                                            .divide(BigDecimal.valueOf(watchCount), 2, RoundingMode.HALF_UP)
+                                            .longValue()
+                                    )
+                                    .orElse(0L)
+                    );
+
+                    // 计算正确率 (正确人次 / 答题人次 * 100)
+                    item.setCorrectRate(
+                            Optional.ofNullable(item.getAnswerCount())
+                                    .filter(answerCount -> answerCount > 0)
+                                    .map(answerCount -> BigDecimal.valueOf(
+                                                    Optional.ofNullable(item.getCorrectCount()).orElse(0L))
+                                            .multiply(BigDecimal.valueOf(100))
+                                            .divide(BigDecimal.valueOf(answerCount), 2, RoundingMode.HALF_UP)
+                                            .longValue()
+                                    )
+                                    .orElse(0L)
+                    );
+                });
+
+        ExcelUtil<FsUserCourseCompanyStatistics> util = new ExcelUtil<FsUserCourseCompanyStatistics>(FsUserCourseCompanyStatistics.class);
+        return util.exportExcel(list, "会员每日看课统计数据");
+    }
+
+    /**
+     * 获取会员每日看课统计详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:statistics:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsUserCourseCompanyStatisticsService.selectFsUserCourseCompanyStatisticsById(id));
+    }
+
+    /**
+     * 新增会员每日看课统计
+     */
+    @PreAuthorize("@ss.hasPermi('course:statistics:add')")
+    @Log(title = "会员每日看课统计", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics)
+    {
+        return toAjax(fsUserCourseCompanyStatisticsService.insertFsUserCourseCompanyStatistics(fsUserCourseCompanyStatistics));
+    }
+
+    /**
+     * 修改会员每日看课统计
+     */
+    @PreAuthorize("@ss.hasPermi('course:statistics:edit')")
+    @Log(title = "会员每日看课统计", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics)
+    {
+        return toAjax(fsUserCourseCompanyStatisticsService.updateFsUserCourseCompanyStatistics(fsUserCourseCompanyStatistics));
+    }
+
+    /**
+     * 删除会员每日看课统计
+     */
+    @PreAuthorize("@ss.hasPermi('course:statistics:remove')")
+    @Log(title = "会员每日看课统计", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsUserCourseCompanyStatisticsService.deleteFsUserCourseCompanyStatisticsByIds(ids));
+    }
+}

+ 17 - 0
fs-admin/src/main/java/com/fs/course/task/CourseStatisticsTask.java

@@ -0,0 +1,17 @@
+package com.fs.course.task;
+
+import com.fs.course.service.IFsUserCourseCompanyStatisticsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component("courseStatisticsTask")
+public class CourseStatisticsTask {
+    @Autowired
+    private IFsUserCourseCompanyStatisticsService fsUserCourseCompanyStatisticsService;
+
+    public void saveCourseStatisticsTask(Integer status,Integer day) {
+        fsUserCourseCompanyStatisticsService.courseDailyStatisticsTask(status,day);
+
+    }
+
+}

+ 83 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCourseCompanyStatistics.java

@@ -0,0 +1,83 @@
+package com.fs.course.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 会员每日看课统计对象 fs_user_course_company_statistics
+ *
+ * @author fs
+ * @date 2025-10-27
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsUserCourseCompanyStatistics extends BaseEntity{
+
+    /** 主键ID */
+    private Long id;
+
+    /** 项目ID */
+//    @Excel(name = "项目ID")
+    private Long projectId;
+
+    /** 完播次数(人次) */
+    @Excel(name = "完播次数", readConverterExp = "人=次")
+    private Long completeWatchCount;
+
+    /** 观看次数(人次) */
+    @Excel(name = "观看次数", readConverterExp = "人=次")
+    private Long watchCount;
+
+    /** 完播率(完播次数/观看次数) */
+    @Excel(name = "完播率", readConverterExp = "完=播次数/观看次数")
+    private Long completeRate;
+
+    /** 答题人次 */
+    @Excel(name = "答题人次")
+    private Long answerCount;
+
+    /** 正确人次 */
+    @Excel(name = "正确人次")
+    private Long correctCount;
+
+    /** 正确率(正确人次/答题人次) */
+    @Excel(name = "正确率", readConverterExp = "正=确人次/答题人次")
+    private Long correctRate;
+
+    /** 领取次数 */
+    @Excel(name = "领取次数")
+    private Long receiveCount;
+
+    /** 领取金额(元) */
+    @Excel(name = "领取金额", readConverterExp = "元=")
+    private BigDecimal receiveAmount;
+
+    /** 会员数量 */
+    @Excel(name = "会员数量")
+    private Long userCount;
+
+    /** 会员黑名单数量 */
+    @Excel(name = "会员黑名单数量")
+    private Long userBlacklistCount;
+
+    /** 公司ID */
+//    @Excel(name = "公司ID")
+    private Long companyId;
+
+    /** 公司名称 */
+    @Excel(name = "公司名称")
+    private String companyName;
+
+    /** 统计日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "统计日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date createDate;
+
+
+}

+ 69 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseCompanyStatisticsMapper.java

@@ -0,0 +1,69 @@
+package com.fs.course.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsUserCourseCompanyStatistics;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 会员每日看课统计Mapper接口
+ *
+ * @author fs
+ * @date 2025-10-27
+ */
+public interface FsUserCourseCompanyStatisticsMapper extends BaseMapper<FsUserCourseCompanyStatistics>{
+    /**
+     * 查询会员每日看课统计
+     *
+     * @param id 会员每日看课统计主键
+     * @return 会员每日看课统计
+     */
+    FsUserCourseCompanyStatistics selectFsUserCourseCompanyStatisticsById(Long id);
+
+    /**
+     * 查询会员每日看课统计列表
+     *
+     * @param fsUserCourseCompanyStatistics 会员每日看课统计
+     * @return 会员每日看课统计集合
+     */
+    List<FsUserCourseCompanyStatistics> selectFsUserCourseCompanyStatisticsList(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics);
+    List<FsUserCourseCompanyStatistics> selectFsUserCourseCompanyStatisticsTotal(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics);
+
+    /**
+     * 新增会员每日看课统计
+     *
+     * @param fsUserCourseCompanyStatistics 会员每日看课统计
+     * @return 结果
+     */
+    int insertFsUserCourseCompanyStatistics(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics);
+
+    /**
+     * 修改会员每日看课统计
+     *
+     * @param fsUserCourseCompanyStatistics 会员每日看课统计
+     * @return 结果
+     */
+    int updateFsUserCourseCompanyStatistics(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics);
+
+    /**
+     * 删除会员每日看课统计
+     *
+     * @param id 会员每日看课统计主键
+     * @return 结果
+     */
+    int deleteFsUserCourseCompanyStatisticsById(Long id);
+
+    /**
+     * 批量删除会员每日看课统计
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsUserCourseCompanyStatisticsByIds(Long[] ids);
+
+    List<FsUserCourseCompanyStatistics> selectStatisticsByDate(
+            @Param("companyId") Long companyId,
+                @Param("startTime") String startTime,
+            @Param("endTime") String endTime
+    );
+}

+ 69 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCourseCompanyStatisticsService.java

@@ -0,0 +1,69 @@
+package com.fs.course.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.course.domain.FsUserCourseCompanyStatistics;
+
+/**
+ * 会员每日看课统计Service接口
+ *
+ * @author fs
+ * @date 2025-10-27
+ */
+public interface IFsUserCourseCompanyStatisticsService extends IService<FsUserCourseCompanyStatistics>{
+    /**
+     * 查询会员每日看课统计
+     *
+     * @param id 会员每日看课统计主键
+     * @return 会员每日看课统计
+     */
+    FsUserCourseCompanyStatistics selectFsUserCourseCompanyStatisticsById(Long id);
+
+    /**
+     * 查询会员每日看课统计列表
+     *
+     * @param fsUserCourseCompanyStatistics 会员每日看课统计
+     * @return 会员每日看课统计集合
+     */
+    List<FsUserCourseCompanyStatistics> selectFsUserCourseCompanyStatisticsList(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics);
+
+    /**
+     * 新增会员每日看课统计
+     *
+     * @param fsUserCourseCompanyStatistics 会员每日看课统计
+     * @return 结果
+     */
+    int insertFsUserCourseCompanyStatistics(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics);
+
+    /**
+     * 修改会员每日看课统计
+     *
+     * @param fsUserCourseCompanyStatistics 会员每日看课统计
+     * @return 结果
+     */
+    int updateFsUserCourseCompanyStatistics(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics);
+
+    /**
+     * 批量删除会员每日看课统计
+     *
+     * @param ids 需要删除的会员每日看课统计主键集合
+     * @return 结果
+     */
+    int deleteFsUserCourseCompanyStatisticsByIds(Long[] ids);
+
+    /**
+     * 删除会员每日看课统计信息
+     *
+     * @param id 会员每日看课统计主键
+     * @return 结果
+     */
+    int deleteFsUserCourseCompanyStatisticsById(Long id);
+
+    /**
+     * 会员每日统计定时任务
+     * @param status
+     */
+    void courseDailyStatisticsTask(Integer status,Integer day);
+
+    List<FsUserCourseCompanyStatistics> selectFsUserCourseCompanyStatisticsTotal(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics);
+}

+ 237 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseCompanyStatisticsServiceImpl.java

@@ -0,0 +1,237 @@
+package com.fs.course.service.impl;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+
+import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.company.domain.Company;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.course.mapper.*;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.course.domain.FsUserCourseCompanyStatistics;
+import com.fs.course.service.IFsUserCourseCompanyStatisticsService;
+
+/**
+ * 会员每日看课统计Service业务层处理
+ *
+ * @author fs
+ * @date 2025-10-27
+ */
+@Service
+@Slf4j
+public class FsUserCourseCompanyStatisticsServiceImpl extends ServiceImpl<FsUserCourseCompanyStatisticsMapper, FsUserCourseCompanyStatistics> implements IFsUserCourseCompanyStatisticsService {
+    @Autowired
+    private CompanyMapper companyMapper;
+    /**
+     * 查询会员每日看课统计
+     *
+     * @param id 会员每日看课统计主键
+     * @return 会员每日看课统计
+     */
+    @Override
+    public FsUserCourseCompanyStatistics selectFsUserCourseCompanyStatisticsById(Long id)
+    {
+        return baseMapper.selectFsUserCourseCompanyStatisticsById(id);
+    }
+
+    /**
+     * 查询会员每日看课统计列表
+     *
+     * @param fsUserCourseCompanyStatistics 会员每日看课统计
+     * @return 会员每日看课统计
+     */
+    @Override
+    public List<FsUserCourseCompanyStatistics> selectFsUserCourseCompanyStatisticsList(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics)
+    {
+        return baseMapper.selectFsUserCourseCompanyStatisticsList(fsUserCourseCompanyStatistics);
+    }
+
+    /**
+     * 新增会员每日看课统计
+     *
+     * @param fsUserCourseCompanyStatistics 会员每日看课统计
+     * @return 结果
+     */
+    @Override
+    public int insertFsUserCourseCompanyStatistics(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics)
+    {
+        fsUserCourseCompanyStatistics.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsUserCourseCompanyStatistics(fsUserCourseCompanyStatistics);
+    }
+
+    /**
+     * 修改会员每日看课统计
+     *
+     * @param fsUserCourseCompanyStatistics 会员每日看课统计
+     * @return 结果
+     */
+    @Override
+    public int updateFsUserCourseCompanyStatistics(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics)
+    {
+        fsUserCourseCompanyStatistics.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsUserCourseCompanyStatistics(fsUserCourseCompanyStatistics);
+    }
+
+    /**
+     * 批量删除会员每日看课统计
+     *
+     * @param ids 需要删除的会员每日看课统计主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserCourseCompanyStatisticsByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsUserCourseCompanyStatisticsByIds(ids);
+    }
+
+    /**
+     * 删除会员每日看课统计信息
+     *
+     * @param id 会员每日看课统计主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserCourseCompanyStatisticsById(Long id)
+    {
+        return baseMapper.deleteFsUserCourseCompanyStatisticsById(id);
+    }
+    /**
+     * 会员每日统计定时任务
+     * @param status
+     */
+    @Override
+    public void courseDailyStatisticsTask(Integer status, Integer day) {
+        /**
+         * 课程数据统计任务
+         *
+         * 统计内容:
+         * 1. 看课次数、完播次数、完播率(fs_course_watch_log)
+         * 2. 答题数量、正确数量、正确率(fs_course_answer_logs)
+         * 3. 红包领取次数、金额(fs_course_red_packet_log)
+         * 4. 会员数量、黑名单数量(fs_user_company_user)
+         *
+         * 参数说明:
+         * status=1 → 查询前一天的数据(00:00:00 ~ 23:59:59)
+         * status=2 → 查询前一个整点小时的数据(例如 17:15 → 16:00:00~16:59:59)
+         * status=3 → 查询最近 day 天到昨天23:59:59的数据
+         */
+        log.info("【课程统计任务开始】status={}, day={}", status, day);
+
+        // 参数校验
+        if (status == null || (!status.equals(1) && !status.equals(2) && !status.equals(3))) {
+            log.warn("课程统计任务状态参数错误:{}", status);
+            return;
+        }
+        if (status.equals(3) && (day == null || day <= 0)) {
+            log.warn("课程统计任务参数错误:status=3 时 day 不能为空且 > 0");
+            return;
+        }
+
+        try {
+            // 计算时间范围
+            LocalDateTime now = LocalDateTime.now();
+            LocalDateTime startTime;
+            LocalDateTime endTime;
+
+            //统计时间(创建时间)
+            Date date = new Date();
+            switch (status) {
+                case 1:
+                    // 前一天 00:00:00 ~ 23:59:59
+                    LocalDate yesterday = LocalDate.now().minusDays(1);
+                    startTime = yesterday.atStartOfDay();
+                    endTime = yesterday.atTime(23, 59, 59);
+                    date=DateUtils.addDays(new Date(),-1);
+                    break;
+
+                case 2:
+                    // 前一个整点小时:例如现在17:15 → 16:00:00 - 16:59:59
+                    LocalDateTime lastHour = now.truncatedTo(ChronoUnit.HOURS).minusHours(1);
+                    startTime = lastHour;
+                    endTime = lastHour.withMinute(59).withSecond(59);
+                    break;
+
+                case 3:
+                    // 最近 day 天到昨天晚上23:59:59
+                    // 结束时间:昨天23:59:59
+                    LocalDate yesterdayEnd = LocalDate.now().minusDays(1);
+                    endTime = yesterdayEnd.atTime(23, 59, 59);
+                    date=DateUtils.addDays(new Date(),-1);
+                    // 开始时间:day天前的00:00:00
+                    LocalDate startDate = yesterdayEnd.minusDays(day - 1);
+                    startTime = startDate.atStartOfDay();
+                    break;
+
+                default:
+                    log.warn("未知状态值:{}", status);
+                    return;
+            }
+
+            String start = startTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+            String end = endTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+            log.info("【课程统计时间范围】{} - {}", start, end);
+
+            // 查询公司列表
+            List<Company> companyList = companyMapper.selectCompanyAllList();
+            if (companyList == null || companyList.isEmpty()) {
+                log.warn("未查询到任何公司信息,任务结束");
+                return;
+            }
+
+            int total = 0;
+            for (Company company : companyList) {
+                try {
+                    List<FsUserCourseCompanyStatistics> statisticsList =
+                            baseMapper.selectStatisticsByDate(company.getCompanyId(), start, end);
+
+                    if (statisticsList == null || statisticsList.isEmpty()) {
+                        log.info("公司[{}]({}) 在时间段 {} - {} 无统计数据", company.getCompanyName(), company.getCompanyId(), start, end);
+                        continue;
+                    }
+
+                    for (FsUserCourseCompanyStatistics stat : statisticsList) {
+                        stat.setCompanyId(company.getCompanyId());
+                        stat.setCompanyName(company.getCompanyName());
+                        stat.setCreateDate(date);
+                        baseMapper.insertFsUserCourseCompanyStatistics(stat);
+                        total++;
+                    }
+
+                    log.info("公司[{}]({}) 数据统计完成,共 {} 条", company.getCompanyName(), company.getCompanyId(), statisticsList.size());
+
+                } catch (Exception ex) {
+                    log.error("公司[{}]({}) 统计异常:{}", company.getCompanyName(), company.getCompanyId(), ex.getMessage(), ex);
+                }
+            }
+
+            log.info("【课程统计任务完成】共处理公司数={},插入统计数据={} 条", companyList.size(), total);
+
+        } catch (Exception e) {
+            log.error("课程统计任务执行异常:{}", e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public List<FsUserCourseCompanyStatistics> selectFsUserCourseCompanyStatisticsTotal(FsUserCourseCompanyStatistics fsUserCourseCompanyStatistics) {
+        // 判断对象是否为空,或 companyId 是否为空
+        Long companyId = Optional.ofNullable(fsUserCourseCompanyStatistics)
+                .map(FsUserCourseCompanyStatistics::getCompanyId)
+                .orElseThrow(() -> new ServiceException("请选择公司后再进行统计查询!"));
+
+        // companyId 不为空,再执行查询
+        return baseMapper.selectFsUserCourseCompanyStatisticsTotal(fsUserCourseCompanyStatistics);
+    }
+
+
+
+}

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

@@ -1050,7 +1050,7 @@ public class FsUserServiceImpl implements IFsUserService {
         //查询用户
         FsUser fsUser = fsUserMapper.selectFsUserById(param.getUserId());
         if (Objects.isNull(fsUser)) {
-            return ResponseResult.fail(404, "当前用户信息不存在");
+            return ResponseResult.fail(401, "当前用户信息不存在");
         }
 
         //判断该销售是否存在

+ 3 - 3
fs-service/src/main/resources/application-dev.yml

@@ -43,9 +43,9 @@ spring:
             druid:
                 # 主库数据源
                 master:
-                    url: jdbc:mysql://127.0.0.1:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: root
-                    password: 1101165230
+                    url: jdbc:mysql://139.186.77.83:3306/ylrz_his_scrm?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: Rtroot
+                    password: Rtroot
                 # 初始连接数
                 initialSize: 5
                 # 最小连接池数量

+ 248 - 0
fs-service/src/main/resources/mapper/course/FsUserCourseCompanyStatisticsMapper.xml

@@ -0,0 +1,248 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.course.mapper.FsUserCourseCompanyStatisticsMapper">
+
+    <resultMap type="FsUserCourseCompanyStatistics" id="FsUserCourseCompanyStatisticsResult">
+        <result property="id"    column="id"    />
+        <result property="projectId"    column="project_id"    />
+        <result property="watchCount"    column="watch_count"    />
+        <result property="answerCount"    column="answer_count"    />
+        <result property="correctCount"    column="correct_count"    />
+        <result property="receiveCount"    column="receive_count"    />
+        <result property="receiveAmount"    column="receive_amount"    />
+        <result property="userCount"    column="user_count"    />
+        <result property="userBlacklistCount"    column="user_blacklist_count"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="companyName"    column="company_name"    />
+        <result property="createDate"    column="create_date"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="updateBy"    column="update_by"    />
+    </resultMap>
+
+    <sql id="selectFsUserCourseCompanyStatisticsVo">
+        select id, project_id,  complete_watch_count, watch_count, answer_count, correct_count,receive_count, receive_amount, user_count, user_blacklist_count, company_id, company_name, create_date, create_time, update_time, create_by, update_by from fs_user_course_company_statistics
+    </sql>
+
+    <select id="selectFsUserCourseCompanyStatisticsList" parameterType="FsUserCourseCompanyStatistics" resultMap="FsUserCourseCompanyStatisticsResult">
+        <include refid="selectFsUserCourseCompanyStatisticsVo"/>
+        <where>
+            <if test="projectId != null "> and project_id = #{projectId}</if>
+            <if test="watchCount != null "> and watch_count = #{watchCount}</if>
+            <if test="answerCount != null "> and answer_count = #{answerCount}</if>
+            <if test="correctCount != null "> and correct_count = #{correctCount}</if>
+            <if test="receiveCount != null "> and receive_count = #{receiveCount}</if>
+            <if test="receiveAmount != null "> and receive_amount = #{receiveAmount}</if>
+            <if test="userCount != null "> and user_count = #{userCount}</if>
+            <if test="userBlacklistCount != null "> and user_blacklist_count = #{userBlacklistCount}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="companyName != null  and companyName != ''"> and company_name like concat('%', #{companyName}, '%')</if>
+            <if test="createDate != null "> and create_date = #{createDate}</if>
+        </where>
+    </select>
+
+    <select id="selectFsUserCourseCompanyStatisticsById" parameterType="Long" resultMap="FsUserCourseCompanyStatisticsResult">
+        <include refid="selectFsUserCourseCompanyStatisticsVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertFsUserCourseCompanyStatistics" parameterType="FsUserCourseCompanyStatistics" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_user_course_company_statistics
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="projectId != null">project_id,</if>
+            <if test="watchCount != null">watch_count,</if>
+            <if test="completeWatchCount != null">complete_watch_count,</if>
+            <if test="answerCount != null">answer_count,</if>
+            <if test="correctCount != null">correct_count,</if>
+            <if test="receiveCount != null">receive_count,</if>
+            <if test="receiveAmount != null">receive_amount,</if>
+            <if test="userCount != null">user_count,</if>
+            <if test="userBlacklistCount != null">user_blacklist_count,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="companyName != null">company_name,</if>
+            <if test="createDate != null">create_date,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateBy != null">update_by,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="projectId != null">#{projectId},</if>
+            <if test="watchCount != null">#{watchCount},</if>
+            <if test="completeWatchCount != null">#{completeWatchCount},</if>
+            <if test="answerCount != null">#{answerCount},</if>
+            <if test="correctCount != null">#{correctCount},</if>
+            <if test="receiveCount != null">#{receiveCount},</if>
+            <if test="receiveAmount != null">#{receiveAmount},</if>
+            <if test="userCount != null">#{userCount},</if>
+            <if test="userBlacklistCount != null">#{userBlacklistCount},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyName != null">#{companyName},</if>
+            <if test="createDate != null">#{createDate},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsUserCourseCompanyStatistics" parameterType="FsUserCourseCompanyStatistics">
+        update fs_user_course_company_statistics
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="projectId != null">project_id = #{projectId},</if>
+            <if test="watchCount != null">watch_count = #{watchCount},</if>
+            <if test="answerCount != null">answer_count = #{answerCount},</if>
+            <if test="correctCount != null">correct_count = #{correctCount},</if>
+            <if test="receiveCount != null">receive_count = #{receiveCount},</if>
+            <if test="receiveAmount != null">receive_amount = #{receiveAmount},</if>
+            <if test="userCount != null">user_count = #{userCount},</if>
+            <if test="userBlacklistCount != null">user_blacklist_count = #{userBlacklistCount},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="companyName != null">company_name = #{companyName},</if>
+            <if test="createDate != null">create_date = #{createDate},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsUserCourseCompanyStatisticsById" parameterType="Long">
+        delete from fs_user_course_company_statistics where id = #{id}
+    </delete>
+
+    <delete id="deleteFsUserCourseCompanyStatisticsByIds" parameterType="String">
+        delete from fs_user_course_company_statistics where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+
+    <select id="selectStatisticsByDate" resultType="FsUserCourseCompanyStatistics">
+        WITH watch_stats AS (
+<!--            看课统计-->
+        SELECT
+        project,
+        COUNT(DISTINCT CASE WHEN log_type = 2 THEN user_id END) AS courseCompleteNum,
+        COUNT(DISTINCT CASE WHEN log_type != 3 THEN user_id END) AS courseWatchNum
+        FROM fs_course_watch_log
+        WHERE create_time &gt; #{startTime}
+        AND create_time &lt; #{endTime}
+        AND company_id = #{companyId}
+        GROUP BY project
+        ),
+        answer_stats AS (
+
+<!--        答题统计-->
+        SELECT
+        l.project,
+        COUNT(DISTINCT a.user_id) as answerNum,
+        COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END) as answerRightNum
+        FROM fs_course_answer_logs a
+        INNER JOIN fs_course_watch_log l ON a.watch_log_id = l.log_id
+        WHERE a.create_time &gt;= #{startTime}
+        AND a.create_time &lt; #{endTime}
+        AND a.company_id = #{companyId}
+        GROUP BY l.project
+        ),
+        redpacket_stats AS (
+
+<!--        红包统计-->
+        SELECT
+        l.project,
+        COUNT(r.log_id) as redPacketNum,
+        IFNULL(SUM(r.amount), 0) as redPacketAmount
+        FROM fs_course_red_packet_log r
+        INNER JOIN fs_course_watch_log l ON r.watch_log_id = l.log_id
+        WHERE r.create_time &gt;= #{startTime}
+        AND r.create_time &lt; #{endTime}
+        AND r.company_id = #{companyId}
+        GROUP BY l.project
+        ),
+        user_stats AS (
+
+<!--        用户统计-->
+        SELECT
+        project_id as project,
+        COUNT(DISTINCT user_id) as userCount,
+        COUNT(DISTINCT CASE WHEN status = 2 THEN user_id END) as blacklist
+        FROM fs_user_company_user
+        WHERE create_time &gt;= #{startTime}
+        AND create_time &lt; #{endTime}
+        AND company_id = #{companyId}
+        GROUP BY project_id
+        )
+
+<!--        合并数据-->
+        SELECT
+        w.project AS projectId,
+        w.courseCompleteNum AS completeWatchCount,
+        w.courseWatchNum AS watchCount,
+        COALESCE(a.answerNum, 0) AS answerCount,
+        COALESCE(a.answerRightNum, 0) AS correctCount,
+        COALESCE(r.redPacketNum, 0) AS receiveCount,
+        COALESCE(r.redPacketAmount, 0) AS receiveAmount,
+        COALESCE(u.userCount, 0) AS userCount,
+        COALESCE(u.blacklist, 0) AS userBlacklistCount
+        FROM watch_stats w
+        LEFT JOIN answer_stats a ON w.project = a.project
+        LEFT JOIN redpacket_stats r ON w.project = r.project
+        LEFT JOIN user_stats u ON w.project = u.project
+    </select>
+
+
+    <select id="selectFsUserCourseCompanyStatisticsTotal" parameterType="FsUserCourseCompanyStatistics" resultType="FsUserCourseCompanyStatistics">
+        SELECT
+        id,
+        project_id,
+        COALESCE(SUM(complete_watch_count), 0) AS complete_watch_count,
+        COALESCE(SUM(watch_count), 0) AS watch_count,
+        COALESCE(SUM(answer_count), 0) AS answer_count,
+        COALESCE(SUM(correct_count), 0) AS correct_count,
+        COALESCE(SUM(receive_count), 0) AS receive_count,
+        COALESCE(SUM(receive_amount), 0) AS receive_amount,
+        COALESCE(SUM(user_count), 0) AS user_count,
+        COALESCE(SUM(user_blacklist_count), 0) AS user_blacklist_count,
+        company_id,
+        company_name,
+       create_date,
+       create_time,
+       update_time,
+       create_by,
+       update_by
+        FROM fs_user_course_company_statistics
+        <where>
+            <if test="projectId != null">AND project_id = #{projectId}</if>
+            <if test="watchCount != null">AND watch_count = #{watchCount}</if>
+            <if test="answerCount != null">AND answer_count = #{answerCount}</if>
+            <if test="correctCount != null">AND correct_count = #{correctCount}</if>
+            <if test="receiveCount != null">AND receive_count = #{receiveCount}</if>
+            <if test="receiveAmount != null">AND receive_amount = #{receiveAmount}</if>
+            <if test="userCount != null">AND user_count = #{userCount}</if>
+            <if test="userBlacklistCount != null">AND user_blacklist_count = #{userBlacklistCount}</if>
+            <if test="companyId != null">AND company_id = #{companyId}</if>
+            <if test="companyName != null and companyName != ''">
+                AND company_name LIKE concat('%', #{companyName}, '%')
+            </if>
+            <if test="createDate != null">AND create_date = #{createDate}</if>
+
+            <!-- ✅ 新增时间筛选 -->
+            <if test="beginTime != null and beginTime != ''">
+                AND DATE(create_date) <![CDATA[ >= ]]> #{beginTime}
+            </if>
+            <if test="endTime != null and endTime != ''">
+                AND DATE(create_date) <![CDATA[ <= ]]> #{endTime}
+            </if>
+        </where>
+
+        <!-- 排序:合计行放最后 -->
+        ORDER BY create_time DESC
+    </select>
+
+
+</mapper>