Browse Source

Merge remote-tracking branch 'origin/master'

yfh 1 week ago
parent
commit
1e2b6c4de3

+ 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 '用户每日统计数据表';

+ 6 - 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,8 @@ 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(), "请选择统计维度");
+        return R.ok().put("data", statisticManageService.statisticMain(param));
     }
 }

+ 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();
 }

+ 86 - 64
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.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.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 /**
@@ -28,84 +36,98 @@ 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统计为一个数组
-                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));
+    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;
             }
-        });
-        //在这里按照公司分组
-//        return companyDeptUserInfoDTOS;
-       return groupByCompanyAndCompanyName(companyDeptUserInfoDTOS);
+            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);
+                getStatisticNumByPersonal(DimensionEnum.PERSONAL.getValue(), param.getStartTime(), param.getEndTime(),userIds);
+            });
+        }
+        return null;
     }
 
-    /**
-     * 组装数据(主要针对userId是空或者是0L情况)
-     * @param companyDeptUserInfo
-     * @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;
+    @Override
+    public void executeTask() {
+        List<CompanyDeptUserInfo> companyDeptdUserList = statisticManageMapper.getCompanyAndDeptAndDeptUserList(null);
+        companyDeptdUserList.forEach(companyDeptUserInfo -> {
+            ComprehensiveDailyStats comprehensiveDailyStats = new ComprehensiveDailyStats();
+            comprehensiveDailyStats.setCompanyId(companyDeptUserInfo.getCompanyId());
+            comprehensiveDailyStats.setCompanyName(companyDeptUserInfo.getCompanyName());
+            comprehensiveDailyStats.setDeptId(companyDeptUserInfo.getDeptId());
+            comprehensiveDailyStats.setDeptName(companyDeptUserInfo.getDeptName());
+            comprehensiveDailyStats.setStatisticsTime(new Date());
+            comprehensiveDailyStats.setCreateTime(new Date());
+            comprehensiveDailyStats.setUpdateTime(new Date());
+            if(null != companyDeptUserInfo.getUserId()){
+                comprehensiveDailyStats.setUserId(companyDeptUserInfo.getUserId());
+                comprehensiveDailyStats.setUserName(companyDeptUserInfo.getUserName());
+                comprehensiveDailyStats.setNickName(companyDeptUserInfo.getNickName());
+                CompanyDeptUserInfoDTO statisticNum = statisticManageMapper.getStatisticNum(companyDeptUserInfo.getUserId());
+                comprehensiveDailyStats.setAnswerNum(statisticNum.getAnswerNum());
+                comprehensiveDailyStats.setCompleteNum(statisticNum.getCompleteNum());
+                comprehensiveDailyStats.setLineNum(statisticNum.getLineNum());
+                comprehensiveDailyStats.setActiveNum(statisticNum.getActiveNum());
+                comprehensiveDailyStats.setRedPacketNum(statisticNum.getRedPacketNum());
+            }
+            statisticManageMapper.insert(comprehensiveDailyStats);
+
+        });
     }
 
     /**
-     * 组装数据
-     * @param source
-     * @param target
+     * 获取统计数据
+     * @param dimension 维度
+     * @param startTime 开始时间
+     * @param endTime 结束时间
+     * @param userIds 用户id
      * @return
      */
-    private static CompanyDeptUserInfoDTO component(CompanyDeptUserInfo source, CompanyDeptUserInfoDTO target) {
-        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;
+    public List<ComprehensiveStatisticsDTO> getStatisticNumByPersonal(Integer dimension, Date startTime, Date endTime, Long... userIds) {
+       return statisticManageMapper.getStatisticNumByPersonal(dimension, startTime, endTime, userIds);
     }
 
 

+ 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;
+}
+

+ 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

+ 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 actvieNUm
+                 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>