Browse Source

Merge remote-tracking branch 'origin/master'

ct 1 week ago
parent
commit
d3a5255721
19 changed files with 979 additions and 107 deletions
  1. 26 0
      README.md
  2. 9 2
      fs-admin/src/main/java/com/fs/api/controller/StatisticManageController.java
  3. 99 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsUserPromoterApplyController.java
  4. 28 0
      fs-admin/src/main/java/com/fs/task/ComprehensiveStatisticsTask.java
  5. 52 0
      fs-common/src/main/java/com/fs/common/enums/DimensionEnum.java
  6. 1 1
      fs-service/src/main/java/com/fs/company/domain/CompanyDeptUserInfo.java
  7. 50 0
      fs-service/src/main/java/com/fs/company/domain/ComprehensiveDailyStats.java
  8. 6 6
      fs-service/src/main/java/com/fs/company/dto/CompanyDeptUserInfoDTO.java
  9. 42 0
      fs-service/src/main/java/com/fs/company/dto/ComprehensiveStatisticsDTO.java
  10. 18 2
      fs-service/src/main/java/com/fs/company/mapper/StatisticManageMapper.java
  11. 6 2
      fs-service/src/main/java/com/fs/company/service/IStatisticManageService.java
  12. 97 60
      fs-service/src/main/java/com/fs/company/service/impl/StatisticManageServiceImpl.java
  13. 13 2
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  14. 2 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  15. 40 0
      fs-service/src/main/java/com/fs/statis/param/ComprehensiveStatisticsParam.java
  16. 2 2
      fs-service/src/main/resources/application-config-druid-heyantang.yml
  17. 3 3
      fs-service/src/main/resources/application-config-druid-jnlzjk.yml
  18. 159 0
      fs-service/src/main/resources/application-druid-heyantang-test.yml
  19. 326 25
      fs-service/src/main/resources/mapper/company/StatisticManageMapper.xml

+ 26 - 0
README.md

@@ -50,3 +50,29 @@ COALESCE(JSON_UNQUOTE(JSON_EXTRACT(remark_mobiles, '$[0]')), ''),
 -- 创建索引
 
 CREATE INDEX idx_search_mobile ON qw_external_contact(search_mobile);
+
+
+-- 新加统计表
+
+CREATE TABLE IF NOT EXISTS user_daily_stats (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '记录ID',
+    company_id BIGINT NOT NULL COMMENT '公司ID',
+    company_name VARCHAR(255) NOT NULL COMMENT '公司名称',
+    dept_id BIGINT NOT NULL COMMENT '部门ID',
+    dept_name VARCHAR(255) NOT NULL COMMENT '部门名称',
+    user_id BIGINT COMMENT '用户ID',
+    user_name VARCHAR(100) COMMENT '用户名',
+    nick_name VARCHAR(100) COMMENT '用户昵称',
+    statistics_time DATE NOT NULL COMMENT '统计日期',
+    line_num INT NOT NULL DEFAULT 0 COMMENT 't1统计数(线路数)',
+    active_num INT NOT NULL DEFAULT 0 COMMENT 't2统计数(活跃数)',
+    complete_num INT NOT NULL DEFAULT 0 COMMENT '完成数(t4)',
+    answer_num INT NOT NULL DEFAULT 0 COMMENT '答题数(t5)',
+    red_packet_num INT NOT NULL DEFAULT 0 COMMENT '红包数量(t6)',
+    red_packet_amount DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '红包金额',
+    create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
+    update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
+    UNIQUE KEY uk_user_date (user_id, statistics_time) COMMENT '用户+日期唯一约束',
+    KEY idx_company_date (company_id, statistics_time) COMMENT '公司+日期索引',
+    KEY idx_dept_date (dept_id, create_time) COMMENT '部门+日期索引'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户每日统计数据表';

+ 9 - 2
fs-admin/src/main/java/com/fs/api/controller/StatisticManageController.java

@@ -3,8 +3,11 @@ package com.fs.api.controller;
 
 import com.fs.common.core.domain.R;
 import com.fs.company.service.IStatisticManageService;
+import com.fs.statis.param.ComprehensiveStatisticsParam;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.Assert;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -24,7 +27,11 @@ public class StatisticManageController {
     private IStatisticManageService statisticManageService;
 
     @PostMapping("/statisticMain")
-    public R statisticMain() {
-        return R.ok().put("data", statisticManageService.statisticMain());
+    public R statisticMain(@RequestBody ComprehensiveStatisticsParam param) {
+        Assert.notNull(param.getDimension(), "请选择统计维度");
+        statisticManageService.executeTask();
+//        return R.ok().put("data", statisticManageService.statisticMain(param));
+        return R.ok("success");
+
     }
 }

+ 99 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsUserPromoterApplyController.java

@@ -0,0 +1,99 @@
+package com.fs.hisStore.controller;
+
+import com.fs.common.annotation.Log;
+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.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsUserPromoterApplyScrm;
+import com.fs.hisStore.param.FsUsePromoterApplyParam;
+import com.fs.hisStore.service.IFsUserPromoterApplyScrmService;
+import com.fs.hisStore.vo.FsUserPromoterApplyVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 推广员申请Controller
+ *
+ * @author fs
+ * @date 2023-02-28
+ */
+@RestController
+@RequestMapping("/store/userPromoterApply")
+public class FsUserPromoterApplyController extends BaseController
+{
+    @Autowired
+    private IFsUserPromoterApplyScrmService fsUserPromoterApplyService;
+
+    /**
+     * 查询推广员申请列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUsePromoterApplyParam fsUserPromoterApply)
+    {
+        startPage();
+        List<FsUserPromoterApplyVO> list = fsUserPromoterApplyService.selectFsUserPromoterApplyListVO(fsUserPromoterApply);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出推广员申请列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:export')")
+    @Log(title = "推广员申请", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserPromoterApplyScrm fsUserPromoterApply)
+    {
+        List<FsUserPromoterApplyScrm> list = fsUserPromoterApplyService.selectFsUserPromoterApplyList(fsUserPromoterApply);
+        ExcelUtil<FsUserPromoterApplyScrm> util = new ExcelUtil<FsUserPromoterApplyScrm>(FsUserPromoterApplyScrm.class);
+        return util.exportExcel(list, "userPromoterApply");
+    }
+
+    /**
+     * 获取推广员申请详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:query')")
+    @GetMapping(value = "/{applyId}")
+    public AjaxResult getInfo(@PathVariable("applyId") Long applyId)
+    {
+        return AjaxResult.success(fsUserPromoterApplyService.selectFsUserPromoterApplyById(applyId));
+    }
+
+    /**
+     * 新增推广员申请
+     */
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:add')")
+    @Log(title = "推广员申请", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserPromoterApplyScrm fsUserPromoterApply)
+    {
+        return toAjax(fsUserPromoterApplyService.insertFsUserPromoterApply(fsUserPromoterApply));
+    }
+
+    /**
+     * 修改推广员申请
+     */
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:edit')")
+    @Log(title = "推广员申请", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserPromoterApplyScrm fsUserPromoterApply)
+    {
+        return toAjax(fsUserPromoterApplyService.updateFsUserPromoterApply(fsUserPromoterApply));
+    }
+
+    /**
+     * 删除推广员申请
+     */
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:remove')")
+    @Log(title = "推广员申请", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{applyIds}")
+    public AjaxResult remove(@PathVariable Long[] applyIds)
+    {
+        return toAjax(fsUserPromoterApplyService.deleteFsUserPromoterApplyByIds(applyIds));
+    }
+}

+ 28 - 0
fs-admin/src/main/java/com/fs/task/ComprehensiveStatisticsTask.java

@@ -0,0 +1,28 @@
+package com.fs.task;
+
+import com.fs.company.service.IStatisticManageService;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/11/3 下午4:12
+ */
+@AllArgsConstructor
+@Component("comprehensiveStatisticsTask")
+public class ComprehensiveStatisticsTask {
+
+    @Resource
+    private final IStatisticManageService iStatisticManageService;
+
+    /**
+     * 每隔一小时执行一次
+     */
+    public void execute() {
+        iStatisticManageService.executeTask();
+    }
+
+}

+ 52 - 0
fs-common/src/main/java/com/fs/common/enums/DimensionEnum.java

@@ -0,0 +1,52 @@
+package com.fs.common.enums;
+
+/**
+ * @description: 统计维度枚举
+ * @author: Guos
+ * @time: 2025/11/3 上午11:26
+ */
+public enum DimensionEnum {
+
+    /**
+     * 个人维度
+     */
+    PERSONAL(1, "个人"),
+
+    /**
+     * 公司维度
+     */
+    COMPANY(2, "公司"),
+
+    /**
+     * 部门维度
+     */
+    DEPARTMENT(3, "部门");
+
+    private final Integer value;
+    private final String description;
+
+    DimensionEnum(Integer value, String description) {
+        this.value = value;
+        this.description = description;
+    }
+
+    public Integer getValue() {
+        return value;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * 根据值获取枚举
+     */
+    public static DimensionEnum fromValue(Integer value) {
+        for (DimensionEnum dimension : DimensionEnum.values()) {
+            if (dimension.getValue().equals(value)) {
+                return dimension;
+            }
+        }
+        return null;
+    }
+}

+ 1 - 1
fs-service/src/main/java/com/fs/company/domain/CompanyDeptUserInfo.java

@@ -13,7 +13,7 @@ public class CompanyDeptUserInfo {
    /**
     * 公司id
     */
-   private Integer companyId;
+   private Long companyId;
 
    /**
     * 公司名称

+ 50 - 0
fs-service/src/main/java/com/fs/company/domain/ComprehensiveDailyStats.java

@@ -0,0 +1,50 @@
+package com.fs.company.domain;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/11/3 下午4:58
+ */
+@Data
+public class ComprehensiveDailyStats {
+
+    private Long id;                     // 记录ID
+
+    private Long companyId;              // 公司ID
+
+    private String companyName;          // 公司名称
+
+    private Long deptId;                 // 部门ID
+
+    private String deptName;             // 部门名称
+
+    private Long userId;                 // 用户ID
+
+    private String userName;             // 用户名
+
+    private String nickName;             // 用户昵称
+
+    private Date statisticsTime;    // 统计日期
+
+    private Integer lineNum;             // t1统计数(线路数)
+
+    private Integer activeNum;           // t2统计数(活跃数)
+
+    private Integer completeNum;         // 完成数(t4)
+
+    private Integer answerNum;           // 答题数(t5)
+
+    private Integer redPacketNum;        // 红包数量(t6)
+
+    private BigDecimal redPacketAmount;  // 红包金额
+
+    private Date createTime;    // 记录创建时间
+
+    private Date updateTime;    // 记录更新时间
+
+}

+ 6 - 6
fs-service/src/main/java/com/fs/company/dto/CompanyDeptUserInfoDTO.java

@@ -13,7 +13,7 @@ public class CompanyDeptUserInfoDTO {
     /**
      * 公司id
      */
-    private Integer companyId;
+    private Long companyId;
 
     /**
      * 公司名称
@@ -33,25 +33,25 @@ public class CompanyDeptUserInfoDTO {
     /**
      * 进线数量
      */
-    private Long lineNum;
+    private Integer lineNum;
 
     /**
      * 激活数
      */
-    private Long activeNum;
+    private Integer activeNum;
 
     /**
      * 完课数量
      */
-    private Long completeNum;
+    private Integer completeNum;
 
     /**
      * 答题数量
      */
-    private Long answerNum;
+    private Integer answerNum;
 
     /**
      * 红包数量
      */
-    private Long redPacketNum;
+    private Integer redPacketNum;
 }

+ 42 - 0
fs-service/src/main/java/com/fs/company/dto/ComprehensiveStatisticsDTO.java

@@ -0,0 +1,42 @@
+package com.fs.company.dto;
+
+import lombok.Data;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/11/3 下午3:24
+ */
+@Data
+public class ComprehensiveStatisticsDTO {
+
+    /**
+     * 日期
+     */
+    private String dateStr;
+
+    /**
+     * 进线数量
+     */
+    private Long lineNum;
+
+    /**
+     * 激活数
+     */
+    private Long activeNum;
+
+    /**
+     * 完课数量
+     */
+    private Long completeNum;
+
+    /**
+     * 答题数量
+     */
+    private Long answerNum;
+
+    /**
+     * 红包数量
+     */
+    private Long redPacketNum;
+}

+ 18 - 2
fs-service/src/main/java/com/fs/company/mapper/StatisticManageMapper.java

@@ -1,9 +1,12 @@
 package com.fs.company.mapper;
 
 import com.fs.company.domain.CompanyDeptUserInfo;
+import com.fs.company.domain.ComprehensiveDailyStats;
 import com.fs.company.dto.CompanyDeptUserInfoDTO;
+import com.fs.company.dto.ComprehensiveStatisticsDTO;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -13,10 +16,23 @@ import java.util.List;
  */
 public interface StatisticManageMapper {
 
+    //插入数据
+    Integer insert(ComprehensiveDailyStats comprehensiveDailyStats);
 
     //获取公司、部门、员工信息
-    List<CompanyDeptUserInfo> getCompanyAndDeptAndDeptUserList();
+    List<CompanyDeptUserInfo> getCompanyAndDeptAndDeptUserList(@Param("companyId") Long companyId);
 
+    List<CompanyDeptUserInfo> getCompanyInfo();
 
-    CompanyDeptUserInfoDTO getStatisticNum(@Param("userIds") Long[] userIds);
+
+    CompanyDeptUserInfoDTO getStatisticNum(@Param("userIds") Long userIds);
+
+    /**
+     * 按照个人统计
+     * @param dimension 维度
+     * @param startTime
+     * @param endTime
+     * @param userIds
+     */
+    List<ComprehensiveStatisticsDTO> getStatisticNumByPersonal(@Param("dimension")Integer dimension, @Param("startTime") Date startTime, @Param("endTime") Date endTime, @Param("userIds") Long... userIds);
 }

+ 6 - 2
fs-service/src/main/java/com/fs/company/service/IStatisticManageService.java

@@ -1,6 +1,6 @@
 package com.fs.company.service;
 
-import com.fs.company.dto.CompanyDeptUserInfoDTO;
+import com.fs.statis.param.ComprehensiveStatisticsParam;
 
 import java.util.List;
 import java.util.Map;
@@ -12,6 +12,10 @@ import java.util.Map;
  */
 public interface IStatisticManageService {
 
-    List<Map<String, Object>> statisticMain();
+    List<Map<String, Object>> statisticMain(ComprehensiveStatisticsParam param);
 
+    /**
+     * 用来执行定时任务
+     */
+    void executeTask();
 }

+ 97 - 60
fs-service/src/main/java/com/fs/company/service/impl/StatisticManageServiceImpl.java

@@ -1,19 +1,27 @@
 package com.fs.company.service.impl;
 
+import cn.hutool.core.util.ObjectUtil;
+import com.fs.common.enums.DimensionEnum;
+import com.fs.common.utils.bean.BeanUtils;
 import com.fs.company.domain.CompanyDeptUserInfo;
+import com.fs.company.domain.ComprehensiveDailyStats;
 import com.fs.company.dto.CompanyDeptUserInfoDTO;
+import com.fs.company.dto.ComprehensiveStatisticsDTO;
 import com.fs.company.mapper.StatisticManageMapper;
 import com.fs.company.service.IStatisticManageService;
+import com.fs.statis.param.ComprehensiveStatisticsParam;
 import com.google.common.collect.Maps;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.collections4.CollectionUtils;
 import com.google.common.collect.Lists;
-import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
 
 import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 /**
@@ -28,84 +36,113 @@ public class StatisticManageServiceImpl implements IStatisticManageService {
     @Resource
     private StatisticManageMapper statisticManageMapper;
 
-
     /**
      * 统计
      * 按照部门分组情况
      * @return
      */
     @Override
-    public List<Map<String, Object>> statisticMain() {
-        List<CompanyDeptUserInfo> companyDeptdUserList = statisticManageMapper.getCompanyAndDeptAndDeptUserList();
-        if(CollectionUtils.isEmpty(companyDeptdUserList)){return null;}
-        //按照部门分组
-        Map<Long, List<CompanyDeptUserInfo>> deptInfos = companyDeptdUserList.stream()
-            .collect(Collectors.groupingBy(CompanyDeptUserInfo::getDeptId));
-        //创建一个新的List来接收
-        List<CompanyDeptUserInfoDTO> companyDeptUserInfoDTOS = Lists.newArrayList();
-        deptInfos.forEach((deptId, companyDeptUserInfos) -> {
-            //目前没看见没部门的情况,第一个判断是size为1的情况并且userId为null的情况或者0L的情况,就不用走数据去查询,直接都是0
-            int size = companyDeptUserInfos.size();
-            if(size == 1 && (companyDeptUserInfos.get(0).getUserId() == null || 0L == companyDeptUserInfos.get(0).getUserId())) {
-                CompanyDeptUserInfo companyDeptUserInfo = companyDeptUserInfos.get(0);
-                companyDeptUserInfoDTOS.add(component(companyDeptUserInfo));
-            }else{
-                //将所有userId统计为一个数组
+    public List<Map<String, Object>> statisticMain(ComprehensiveStatisticsParam param) {
+        List<Map<String, Object>> result = Lists.newArrayList();
+
+        if (param.getDimension() == DimensionEnum.PERSONAL.getValue()){
+            Assert.notNull(param.getId(), "按个人展示查询条件不能为空!");
+            List<CompanyDeptUserInfo> companyDeptdUserList = statisticManageMapper.getCompanyAndDeptAndDeptUserList(null);
+            //从所有数据中去匹配userId = param.getId的
+            Optional<CompanyDeptUserInfo> first = companyDeptdUserList.stream()
+                    .filter(e -> e.getUserId().equals(param.getId()))
+                    .findFirst();
+            if (!first.isPresent()) {
+                return result;
+            }
+            CompanyDeptUserInfo companyDeptUserInfo = first.get();
+            Map<String, Object> map = Maps.newHashMap();
+            map.put("id", companyDeptUserInfo.getUserId());
+            map.put("name", companyDeptUserInfo.getNickName());
+            List<ComprehensiveStatisticsDTO> statisticNumByPersonal =
+                    getStatisticNumByPersonal(DimensionEnum.PERSONAL.getValue(), param.getStartTime(), param.getEndTime(), param.getId());
+            map.put("list", statisticNumByPersonal);
+            result.add(map);
+            return result;
+        }
+        if(param.getDimension() == DimensionEnum.COMPANY.getValue()){
+            //按照公司统计,如果id为空就要展示全部公司,如果不为空就展示id查询的公司
+            if(ObjectUtil.isEmpty(param.getId())){
+                //得到所有公司信息
+                List<CompanyDeptUserInfo> companyInfo = statisticManageMapper.getCompanyInfo();
+                for (CompanyDeptUserInfo companyDeptUserInfo : companyInfo){
+                    getStatisticNumByPersonal(DimensionEnum.COMPANY.getValue(), param.getStartTime(), param.getEndTime(),
+                            companyDeptUserInfo.getCompanyId());
+                }
+            }
+        }
+        if(param.getDimension() == DimensionEnum.DEPARTMENT.getValue()){
+            Assert.notNull(param.getId(), "按部门展示公司不能为空!");
+            List<CompanyDeptUserInfo> companyDeptdUserList = statisticManageMapper.getCompanyAndDeptAndDeptUserList(param.getId());
+            //按照部门分组
+            Map<Long, List<CompanyDeptUserInfo>> deptInfos = companyDeptdUserList.stream()
+                    .collect(Collectors.groupingBy(CompanyDeptUserInfo::getDeptId));
+            deptInfos.forEach((deptId, companyDeptUserInfos) -> {
                 Long[] userIds = companyDeptUserInfos.stream().map(CompanyDeptUserInfo::getUserId).toArray(Long[]::new);
-                CompanyDeptUserInfo companyDeptUserInfo = companyDeptUserInfos.get(0);
-                CompanyDeptUserInfoDTO statisticNum = statisticManageMapper.getStatisticNum(userIds);
-                companyDeptUserInfoDTOS.add(component(companyDeptUserInfo, statisticNum));
+                getStatisticNumByPersonal(DimensionEnum.PERSONAL.getValue(), param.getStartTime(), param.getEndTime(),userIds);
+            });
+        }
+        return null;
+    }
+
+    @Override
+    public void executeTask() {
+        List<CompanyDeptUserInfo> companyDeptdUserList = statisticManageMapper.getCompanyAndDeptAndDeptUserList(null);
+        companyDeptdUserList.forEach(companyDeptUserInfo -> {
+            CompanyDeptUserInfoDTO statisticNum =  new CompanyDeptUserInfoDTO();
+            if(null != companyDeptUserInfo.getUserId()){
+                statisticNum = statisticManageMapper.getStatisticNum(companyDeptUserInfo.getUserId());
             }
+            ComprehensiveDailyStats comprehensiveDailyStats = component(companyDeptUserInfo, statisticNum);
+            statisticManageMapper.insert(comprehensiveDailyStats);
         });
-        //在这里按照公司分组
-//        return companyDeptUserInfoDTOS;
-       return groupByCompanyAndCompanyName(companyDeptUserInfoDTOS);
     }
 
     /**
-     * 组装数据(主要针对userId是空或者是0L情况)
-     * @param companyDeptUserInfo
+     * 获取统计数据
+     * @param dimension 维度
+     * @param startTime 开始时间
+     * @param endTime 结束时间
+     * @param userIds 用户id
      * @return
      */
-    private static CompanyDeptUserInfoDTO component(CompanyDeptUserInfo companyDeptUserInfo) {
-        CompanyDeptUserInfoDTO companyDeptUserInfoDTO = new CompanyDeptUserInfoDTO();
-        BeanUtils.copyProperties(companyDeptUserInfo, companyDeptUserInfoDTO);
-        companyDeptUserInfoDTO.setLineNum(0L);
-        companyDeptUserInfoDTO.setActiveNum(0L);
-        companyDeptUserInfoDTO.setCompleteNum(0L);
-        companyDeptUserInfoDTO.setAnswerNum(0L);
-        companyDeptUserInfoDTO.setRedPacketNum(0L);
-        return companyDeptUserInfoDTO;
+    public List<ComprehensiveStatisticsDTO> getStatisticNumByPersonal(Integer dimension, Date startTime, Date endTime, Long... userIds) {
+       return statisticManageMapper.getStatisticNumByPersonal(dimension, startTime, endTime, userIds);
     }
 
-    /**
-     * 组装数据
-     * @param source
-     * @param target
-     * @return
-     */
-    private static CompanyDeptUserInfoDTO component(CompanyDeptUserInfo source, CompanyDeptUserInfoDTO target) {
+    private ComprehensiveDailyStats component(CompanyDeptUserInfo source, CompanyDeptUserInfoDTO statisticNum){
+        ComprehensiveDailyStats target = new ComprehensiveDailyStats();
         target.setCompanyId(source.getCompanyId());
         target.setCompanyName(source.getCompanyName());
         target.setDeptId(source.getDeptId());
         target.setDeptName(source.getDeptName());
-        return target;
-    }
-
-    private List<Map<String, Object>> groupByCompanyAndCompanyName(List<CompanyDeptUserInfoDTO> source){
-        //按照companyId和companyName分组
-        Map<Integer, List<CompanyDeptUserInfoDTO>> companyInfos = source.stream()
-                .collect(Collectors.groupingBy(CompanyDeptUserInfoDTO::getCompanyId));
-        List<Map<String, Object>> resultList = Lists.newArrayList();
-        companyInfos.forEach((companyId, companyDeptUserInfoDTOS) -> {
-            CompanyDeptUserInfoDTO companyDeptUserInfoDTO = companyDeptUserInfoDTOS.get(0);
-            Map<String, Object> companyInfoMap = Maps.newHashMap();
-            companyInfoMap.put("companyId", companyId);
-            companyInfoMap.put("companyName", companyDeptUserInfoDTO.getCompanyName());
-            companyInfoMap.put("deptInfos", companyDeptUserInfoDTOS);
-            resultList.add(companyInfoMap);
-        });
-        return resultList;
+        target.setStatisticsTime(new Date());
+        target.setCreateTime(new Date());
+        target.setUpdateTime(new Date());
+        if(null != source.getUserId()){
+            target.setUserId(source.getUserId());
+            target.setUserName(source.getUserName());
+            target.setNickName(source.getNickName());
+            target.setAnswerNum(statisticNum.getAnswerNum());
+            target.setCompleteNum(statisticNum.getCompleteNum());
+            target.setLineNum(statisticNum.getLineNum());
+            target.setActiveNum(statisticNum.getActiveNum());
+            target.setRedPacketNum(statisticNum.getRedPacketNum());
+            target.setRedPacketAmount(new BigDecimal(0.00));
+        }else{
+            target.setAnswerNum(0);
+            target.setCompleteNum(0);
+            target.setLineNum(0);
+            target.setActiveNum(0);
+            target.setRedPacketNum(0);
+            target.setRedPacketAmount(new BigDecimal(0.00));
+        }
+      return target;
     }
 
 

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

@@ -477,8 +477,19 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
 
     void batchUpdateFsUserWatchLog(@Param("list") List<FsCourseWatchLog> list);
 
-    @Select("select * from fs_course_watch_log where user_id = #{userId} and video_id = #{videoId} and send_type = 1 order by log_id desc limit 1")
-    FsCourseWatchLog getCourseWatchLogByUser(@Param("userId") Long userId, @Param("videoId") Long videoId);
+    @Select("<script>" +
+            "select * from fs_course_watch_log " +
+            "<where>" +
+            "   <if test='periodId != null'>and period_id = #{periodId}</if>" +
+            "   and send_type = 1" +
+            "   and user_id = #{userId}" +
+            "   and video_id = #{videoId}" +
+            "</where>" +
+            "order by log_id desc limit 1" +
+            "</script>")
+    FsCourseWatchLog getCourseWatchLogByUser(@Param("userId") Long userId,
+                                             @Param("videoId") Long videoId,
+                                             @Param("periodId") Long periodId);
 
     /**
      * 根据条件查询条数

+ 2 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -2084,7 +2084,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         }
         //公开课
         if (param.getIsOpenCourse()!=null && param.getIsOpenCourse()==1){
-            FsCourseWatchLog watchCourseVideo = courseWatchLogMapper.getCourseWatchLogByUser(param.getUserId(), param.getVideoId());
+            FsCourseWatchLog watchCourseVideo = courseWatchLogMapper.getCourseWatchLogByUser(param.getUserId(), param.getVideoId(),null);
             //添加判断:该用户是否已经存在此课程的看课记录,并且看课记录的销售id不是传入的销售id
             if(watchCourseVideo != null){
                 FsCourseWatchLog updateLog = new FsCourseWatchLog();
@@ -2158,7 +2158,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
         //查询看课记录
 //        FsCourseWatchLog watchCourseVideo = courseWatchLogMapper.getWatchCourseVideoByFsUser(param.getUserId(), param.getVideoId(), param.getCompanyUserId());
-        FsCourseWatchLog watchCourseVideo = courseWatchLogMapper.getCourseWatchLogByUser(param.getUserId(), param.getVideoId());
+        FsCourseWatchLog watchCourseVideo = courseWatchLogMapper.getCourseWatchLogByUser(param.getUserId(), param.getVideoId(),param.getPeriodId());
 
         if (!isUserCoursePeriodValid(param)) {
             return ResponseResult.fail(504, "请观看最新的课程项目");

+ 40 - 0
fs-service/src/main/java/com/fs/statis/param/ComprehensiveStatisticsParam.java

@@ -0,0 +1,40 @@
+package com.fs.statis.param;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @description: 综合统计入参
+ * @author: Guos
+ * @time: 2025/11/3 上午11:22
+ */
+@Data
+public class ComprehensiveStatisticsParam {
+
+    /**
+     * 统计维度
+     */
+    private Integer dimension;
+
+    /**
+     * 开始时间
+     */
+    private Date startTime;
+
+    /**
+     * 结束时间
+     */
+    private Date endTime;
+
+    /**
+     * 名称
+     */
+    private String name;
+
+    /**
+     * id 在不同的维度下,id代表的意义不同
+     */
+    private Long id;
+}
+

+ 2 - 2
fs-service/src/main/resources/application-config-druid-heyantang.yml

@@ -37,8 +37,8 @@ wx:
       port: 6379
       timeout: 2000
     configs:
-      - appId: wx9df02b2236d4fbf2 # 第一个公众号的appid
-        secret: 4f685750d40ab334b6f2f54b7d121389 # 公众号的appsecret
+      - appId: wx375d93939326f0b5 # 第一个公众号的appid
+        secret: 4059109327ec47d0b468c12aa76e5f78 # 公众号的appsecret
         token: PPKOdAlCoMO # 接口配置里的Token值
         aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
 aifabu:  #爱链接

+ 3 - 3
fs-service/src/main/resources/application-config-druid-jnlzjk.yml

@@ -57,8 +57,8 @@ watch:
   password3: v9xsKuqn_$d2y
 
 fs :
-  commonApi: http://127.0.0.1:7771
-  h5CommonApi: http://127.0.0.1:7771
+  commonApi: http://10.206.0.12:7771
+  h5CommonApi: http://10.206.0.12:7771
   jwt:
     # 加密秘钥
     secret: 3e6d9c0b4a7f1e2d5c4e0d3c6b9a2f5e
@@ -91,7 +91,7 @@ headerImg:
   imgUrl:
 
 ipad:
-  ipadUrl: http://ipadjnlzjk.ylrztop.com
+  ipadUrl: http://ipad.ljhehualu.com
   aiApi: http://49.232.181.28:3000/api
   voiceApi: http://129.28.187.88:8667
   commonApi: http://129.28.187.88:7771

+ 159 - 0
fs-service/src/main/resources/application-druid-heyantang-test.yml

@@ -0,0 +1,159 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-druid-heyantang,common
+    # redis 配置
+    redis:
+        # 地址
+        host: 127.0.0.1
+        # 端口,默认为6379
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password:
+        # 连接超时时间
+        timeout: 20s
+        lettuce:
+            pool:
+                # 连接池中的最小空闲连接
+                min-idle: 0
+                # 连接池中的最大空闲连接
+                max-idle: 8
+                # 连接池的最大数据库连接数
+                max-active: 8
+                # #连接池最大阻塞等待时间(使用负值表示没有限制)
+                max-wait: -1ms
+    datasource:
+        #        clickhouse:
+        #            type: com.alibaba.druid.pool.DruidDataSource
+        #            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
+        #            url: jdbc:clickhouse://cc-2vc8zzo26w0l7m2l6.public.clickhouse.ads.aliyuncs.com/sop?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
+        #            username: rt_2024
+        #            password: Yzx_19860213
+        #            initialSize: 10
+        #            maxActive: 100
+        #            minIdle: 10
+        #            maxWait: 6000
+        mysql:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://118.24.172.242:2345/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_c123232014^$
+                # 从库数据源
+                slave:
+                    # 从数据源开关/默认关闭
+                    enabled: false
+                    url:
+                    username:
+                    password:
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+        sop:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://118.24.172.242:2345/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_c123232014^$
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+rocketmq:
+    name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
+    producer:
+        group: my-producer-group
+        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+    consumer:
+        group: test-group
+        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+openIM:
+    secret: openIM123
+    userID: imAdmin
+    url: https://web.im.fbylive.com/api
+#是否使用新im
+im:
+    type: NONE
+#是否为新商户,新商户不走mpOpenId
+isNewWxMerchant: false

+ 326 - 25
fs-service/src/main/resources/mapper/company/StatisticManageMapper.xml

@@ -27,39 +27,340 @@
         LEFT JOIN company_dept AS cd ON ci.company_id = cd.company_id AND cd.STATUS = 0
         LEFT JOIN company_user AS cu ON cu.dept_id = cd.dept_id AND cu.del_flag = 0
         WHERE ci.is_del = 0 AND cd.del_flag = 0
+        <if test="companyId != null">
+            and ci.company_id = #{companyId}
+        </if>
     </select>
 
     <select id="getStatisticNum" resultType="com.fs.company.dto.CompanyDeptUserInfoDTO">
-        with t1 as (
+        WITH t1 AS (
             SELECT
-            qec.id,
-            qec.company_user_id,
-            qec.fs_user_id,
-            qec.qw_user_id,
-            qec.user_id
+                count(*) AS lineNum
             FROM
-            qw_external_contact AS qec
+                qw_external_contact AS qec
             WHERE
-            date_format(qec.create_time,'%y%m%d') >= date_format(now(),'%y%m%d') and
-            qec.create_time &lt;= now()
-            and qec.company_user_id IN (
-            <foreach collection="userIds" item="i" separator=",">
-                #{i}
-            </foreach>
-            )
-        ),t2 as (
-            select count(t1.id) as lineNum from t1
-        ),t3 as (
-            select count(t1.id) as activeNum from t1 where t1.fs_user_id is not null
-        ),t4 as(
-            select count(fcwl.qw_external_contact_id) as completeNum from t1 inner join fs_course_watch_log as fcwl on t1.id = fcwl.qw_external_contact_id where fcwl.log_type = 2
-        ),t5 as (
-            select count(fcal.log_id) as answerNum  from t1 inner join fs_course_answer_logs as fcal where fcal.user_id = t1.user_id
-        ), t6 as (
-            select count(fcrpl.log_id) as redPacketNum  from t1 inner join fs_course_red_packet_log as fcrpl where fcrpl.user_id = t1.user_id
+                date_format( qec.create_time, '%y%m%d' ) = date_format(now(), '%y%m%d' )
+              AND qec.company_user_id = 327
+        ),
+        t2 AS (
+                 SELECT
+                     count( qec.fs_user_id ) AS activeNum
+                 FROM
+                     qw_external_contact AS qec
+                 WHERE
+                     date_format( qec.create_time, '%y%m%d' ) = date_format(now(), '%y%m%d' )
+                   AND qec.fs_user_id IS NOT NULL
+                   AND qec.company_user_id = 327
+             ),
+             t3 AS ( SELECT count(*) AS completeNum FROM fs_course_watch_log AS fcwl WHERE date_format(fcwl.create_time, '%y%m%d' ) = date_format( now(), '%y%m%d') and fcwl.log_type = 2 AND fcwl.company_user_id = 327 ),
+             t4 AS ( SELECT count(*) AS answerNum FROM fs_course_answer_logs AS fcal WHERE date_format(fcal.create_time, '%y%m%d' ) = date_format( now(), '%y%m%d')  and fcal.company_user_id = 327 ),
+             t5 AS ( SELECT count(*) AS redPacketNum FROM fs_course_red_packet_log AS fcrpl WHERE date_format(fcrpl.create_time, '%y%m%d' ) = date_format( now(), '%y%m%d' )  and  fcrpl.company_user_id = 327 )
+        SELECT * FROM t1,t2,t3,t4,t5
+    </select>
+
+    <select id="getStatisticNumByPersonal" resultType="com.fs.company.dto.ComprehensiveStatisticsDTO">
+        WITH RECURSIVE date_range AS (
+            SELECT #{startTime} AS dt
+            UNION ALL
+            SELECT DATE_ADD(dt, INTERVAL 1 DAY)
+            FROM date_range
+            WHERE dt &lt; #{endTime}
+        ),
+        t1 AS (
+            SELECT
+            COUNT(qec.id) AS t1_count,
+            d.dt AS create_time
+            FROM date_range d
+            LEFT JOIN qw_external_contact qec
+            ON DATE(qec.create_time) = d.dt
+        <if test="userIds != null">
+            <if test="dimension == 1">
+                <choose>
+                    <when test="userIds.length > 1 ">
+                        AND qec.company_user_id IN (
+                        <foreach collection="userIds" item="i" separator=",">
+                            #{i}
+                        </foreach>
+                        )
+                    </when>
+                    <otherwise>
+                        AND qec.company_user_id = #{userIds[0]}
+                    </otherwise>
+                </choose>
+            </if>
+            <if test="dimension == 2">
+                AND qec.company_id = #{userIds[0]}
+            </if>
+        </if>
+            GROUP BY d.dt
+        ),
+        t2 AS (
+            SELECT
+            COUNT(qec.id) AS t2_count,
+            d.dt AS create_time
+            FROM date_range d
+            LEFT JOIN qw_external_contact qec
+            ON DATE(qec.create_time) = d.dt
+        <if test="userIds != null">
+            <if test="dimension == 1">
+                <choose>
+                    <when test="userIds.length > 1 ">
+                        AND qec.company_user_id IN (
+                        <foreach collection="userIds" item="i" separator=",">
+                            #{i}
+                        </foreach>
+                        )
+                    </when>
+                    <otherwise>
+                        AND qec.company_user_id = #{userIds[0]}
+                    </otherwise>
+                </choose>
+            </if>
+            <if test="dimension == 2">
+                AND qec.company_id = #{userIds[0]}
+            </if>
+        </if>
+            AND qec.fs_user_id IS NOT NULL
+            GROUP BY d.dt
+        ),
+        t4 AS (
+            SELECT
+            d.dt AS create_time,
+            COUNT(fcwl.qw_external_contact_id) AS completeNum
+            FROM
+            date_range d
+            LEFT JOIN fs_course_watch_log AS fcwl
+            ON DATE(fcwl.create_time) = d.dt
+            AND fcwl.log_type = 2
+        <if test="userIds != null">
+            <if test="dimension == 1">
+                <choose>
+                    <when test="userIds.length > 1 ">
+                        AND fcwl.company_user_id IN (
+                        <foreach collection="userIds" item="i" separator=",">
+                            #{i}
+                        </foreach>
+                        )
+                    </when>
+                    <otherwise>
+                        AND fcwl.company_user_id = #{userIds[0]}
+                    </otherwise>
+                </choose>
+            </if>
+            <if test="dimension == 2">
+                AND fcwl.company_id = #{userIds[0]}
+            </if>
+        </if>
+            GROUP BY d.dt
+        ),
+        t5 AS (
+            SELECT
+            d.dt AS create_time,
+            COUNT(fcal.log_id) AS answerNum
+            FROM
+            date_range d
+            LEFT JOIN fs_course_answer_logs AS fcal
+            ON DATE(fcal.create_time) = d.dt
+        <if test="userIds != null">
+            <if test="dimension == 1">
+                <choose>
+                    <when test="userIds.length > 1 ">
+                        AND fcal.company_user_id IN (
+                        <foreach collection="userIds" item="i" separator=",">
+                            #{i}
+                        </foreach>
+                        )
+                    </when>
+                    <otherwise>
+                        AND fcal.company_user_id = #{userIds[0]}
+                    </otherwise>
+                </choose>
+            </if>
+            <if test="dimension == 2">
+                AND fcal.company_id = #{userIds[0]}
+            </if>
+        </if>
+            GROUP BY d.dt
+        ),
+        t6 AS (
+            SELECT
+            d.dt AS create_time,
+            COUNT(fcrpl.log_id) AS redPacketNum
+            FROM
+            date_range d
+            LEFT JOIN fs_course_red_packet_log AS fcrpl
+            ON DATE(fcrpl.create_time) = d.dt
+        <if test="userIds != null">
+            <if test="dimension == 1">
+                <choose>
+                    <when test="userIds.length > 1 ">
+                        AND fcrpl.company_user_id IN (
+                        <foreach collection="userIds" item="i" separator=",">
+                            #{i}
+                        </foreach>
+                        )
+                    </when>
+                    <otherwise>
+                        AND fcrpl.company_user_id = #{userIds[0]}
+                    </otherwise>
+                </choose>
+            </if>
+            <if test="dimension == 2">
+                AND fcrpl.company_id = #{userIds[0]}
+            </if>
+        </if>
+            GROUP BY d.dt
+        )
+        SELECT
+            t1.create_time as dateStr,
+            t1.t1_count as lineNum,
+            t2.t2_count as activeNum,
+            t4.completeNum,
+            t5.answerNum,
+            t6.redPacketNum
+        FROM t1
+        INNER JOIN t2 ON t1.create_time = t2.create_time
+        INNER JOIN t4 ON t1.create_time = t4.create_time
+        INNER JOIN t5 ON t1.create_time = t5.create_time
+        INNER JOIN t6 ON t1.create_time = t6.create_time
+        ORDER BY t1.create_time
+    </select>
+
+    <select id="getCompanyInfo" resultType="com.fs.company.domain.CompanyDeptUserInfo">
+        SELECT
+            ci.company_id,
+            ci.company_name
+        FROM
+            company AS ci
+        WHERE
+            ci.is_del = 0
+    </select>
+
+    <!-- 基础字段映射(复用) -->
+    <sql id="Base_Column_List">
+        id, company_id, company_name, dept_id, dept_name, 
+        user_id, user_name, nick_name, statistics_time, 
+        line_num, active_num, complete_num, answer_num, 
+        red_packet_num, red_packet_amount, create_time, update_time
+    </sql>
+
+    <!-- 1. 插入数据(全字段插入) -->
+    <insert id="insert" parameterType="com.fs.company.domain.ComprehensiveDailyStats">
+        INSERT INTO user_daily_stats (
+        company_id, company_name, dept_id, dept_name,
+        user_id, user_name, nick_name, statistics_time,
+        line_num, active_num, complete_num, answer_num,
+        red_packet_num, red_packet_amount, create_time, update_time
+        ) VALUES (
+        #{companyId}, #{companyName}, #{deptId}, #{deptName},
+        #{userId}, #{userName}, #{nickName}, #{statisticsTime},
+        #{lineNum}, #{activeNum}, #{completeNum}, #{answerNum},
+        #{redPacketNum}, #{redPacketAmount}, #{createTime}, #{updateTime}
         )
-        select t2.lineNum, t3.activeNum, t4.completeNum, t5.answerNum, t6.redPacketNum from t2, t3, t4, t5, t6
+    </insert>
+
+    <!-- 2. 插入或更新(根据唯一索引uk_user_date,存在则更新,不存在则插入) -->
+    <insert id="insertOrUpdate" parameterType="com.fs.company.domain.ComprehensiveDailyStats">
+        INSERT INTO user_daily_stats (
+            company_id, company_name, dept_id, dept_name,
+            user_id, user_name, nick_name, statistics_time,
+            line_num, active_num, complete_num, answer_num,
+            red_packet_num, red_packet_amount, create_time, update_time
+        ) VALUES (
+                     #{companyId}, #{companyName}, #{deptId}, #{deptName},
+                     #{userId}, #{userName}, #{nickName}, #{statisticsTime},
+                     #{lineNum}, #{activeNum}, #{completeNum}, #{answerNum},
+                     #{redPacketNum}, #{redPacketAmount}, NOW(), NOW()
+                 ) ON DUPLICATE KEY UPDATE
+            company_id = VALUES(company_id),
+            company_name = VALUES(company_name),
+            dept_id = VALUES(dept_id),
+            dept_name = VALUES(dept_name),
+            user_name = VALUES(user_name),
+            nick_name = VALUES(nick_name),
+            line_num = VALUES(line_num),
+            active_num = VALUES(active_num),
+            complete_num = VALUES(complete_num),
+            answer_num = VALUES(answer_num),
+            red_packet_num = VALUES(red_packet_num),
+            red_packet_amount = VALUES(red_packet_amount),
+            update_time = NOW()
+    </insert>
+
+    <!-- 3. 根据ID更新数据(全字段更新) -->
+    <update id="updateById" parameterType="com.fs.company.domain.ComprehensiveDailyStats">
+        UPDATE user_daily_stats
+        SET
+            company_id = #{companyId},
+            company_name = #{companyName},
+            dept_id = #{deptId},
+            dept_name = #{deptName},
+            user_id = #{userId},
+            user_name = #{userName},
+            nick_name = #{nickName},
+            statistics_time = #{statisticsTime},
+            line_num = #{lineNum},
+            active_num = #{activeNum},
+            complete_num = #{completeNum},
+            answer_num = #{answerNum},
+            red_packet_num = #{redPacketNum},
+            red_packet_amount = #{redPacketAmount},
+            update_time = NOW()
+        WHERE id = #{id}
+    </update>
+
+    <!-- 4. 根据用户ID和统计日期更新(部分字段更新,按需调整) -->
+    <update id="updateByUserAndDate" parameterType="com.fs.company.domain.ComprehensiveDailyStats">
+        UPDATE user_daily_stats
+        SET
+            line_num = #{lineNum},
+            active_num = #{activeNum},
+            complete_num = #{completeNum},
+            answer_num = #{answerNum},
+            red_packet_num = #{redPacketNum},
+            red_packet_amount = #{redPacketAmount},
+            update_time = NOW()
+        WHERE user_id = #{userId} AND statistics_time = #{statisticsTime}
+    </update>
+
+    <!-- 5. 根据ID查询 -->
+    <select id="selectById" resultType="com.fs.company.domain.ComprehensiveDailyStats" parameterType="java.lang.Long">
+        SELECT <include refid="Base_Column_List"/> FROM user_daily_stats WHERE id = #{id}
     </select>
 
+    <!-- 6. 根据用户ID和统计日期查询 -->
+    <select id="selectByUserAndDate" resultType="com.fs.company.domain.ComprehensiveDailyStats">
+        SELECT <include refid="Base_Column_List"/>
+        FROM user_daily_stats
+        WHERE user_id = #{userId} AND statistics_time = #{statisticsTime}
+    </select>
+
+    <!-- 7. 根据公司ID和日期范围查询 -->
+    <select id="selectByCompanyAndDateRange" resultType="com.fs.company.domain.ComprehensiveDailyStats">
+        SELECT <include refid="Base_Column_List"/>
+        FROM user_daily_stats
+        WHERE company_id = #{companyId}
+        AND statistics_time BETWEEN #{startTime} AND #{endTime}
+        ORDER BY statistics_time ASC
+    </select>
+
+    <!-- 8. 根据部门ID和日期范围查询 -->
+    <select id="selectByDeptAndDateRange" resultType="com.fs.company.domain.ComprehensiveDailyStats">
+        SELECT <include refid="Base_Column_List"/>
+        FROM user_daily_stats
+        WHERE dept_id = #{deptId}
+        AND statistics_time BETWEEN #{startTime} AND #{endTime}
+        ORDER BY statistics_time ASC
+    </select>
+
+    <!-- 9. 删除数据(根据ID) -->
+    <delete id="deleteById" parameterType="java.lang.Long">
+        DELETE FROM user_daily_stats WHERE id = #{id}
+    </delete>
+
+    <!-- 10. 删除数据(根据用户ID和统计日期) -->
+    <delete id="deleteByUserAndDate">
+        DELETE FROM user_daily_stats WHERE user_id = #{userId} AND statistics_time = #{statisticsTime}
+    </delete>
 
 </mapper>