Просмотр исходного кода

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin 1 неделя назад
Родитель
Сommit
73e1f1e672
41 измененных файлов с 1771 добавлено и 231 удалено
  1. 1 1
      fs-admin/src/main/java/com/fs/api/controller/StatisticManageController.java
  2. 30 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseTrafficLogController.java
  3. 1 1
      fs-admin/src/main/java/com/fs/task/ComprehensiveStatisticsTask.java
  4. 16 2
      fs-admin/src/main/java/com/fs/task/SgTestController.java
  5. 3 2
      fs-company/src/main/java/com/fs/company/controller/qw/QwDeptController.java
  6. 12 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  7. 5 0
      fs-service/src/main/java/com/fs/company/domain/CompanyDeptUserInfo.java
  8. 53 0
      fs-service/src/main/java/com/fs/company/domain/CourseWatchLogData.java
  9. 49 0
      fs-service/src/main/java/com/fs/company/domain/CourseWatchLogStatistics.java
  10. 0 42
      fs-service/src/main/java/com/fs/company/dto/ComprehensiveStatisticsDTO.java
  11. 7 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyDeptMapper.java
  12. 60 0
      fs-service/src/main/java/com/fs/company/mapper/CourseWatchLogStatisticsMapper.java
  13. 47 15
      fs-service/src/main/java/com/fs/company/mapper/StatisticManageMapper.java
  14. 2 1
      fs-service/src/main/java/com/fs/company/service/IStatisticManageService.java
  15. 359 89
      fs-service/src/main/java/com/fs/company/service/impl/StatisticManageServiceImpl.java
  16. 33 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java
  17. 28 0
      fs-service/src/main/java/com/fs/course/param/StatisticsSummaryParam.java
  18. 15 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseTrafficLogService.java
  19. 119 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java
  20. 17 0
      fs-service/src/main/java/com/fs/course/vo/DeptFullPathVO.java
  21. 63 0
      fs-service/src/main/java/com/fs/course/vo/StatisticsSummaryVO.java
  22. 11 11
      fs-service/src/main/java/com/fs/hisStore/mapper/FsUserBillScrmMapper.java
  23. 1 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsUserScrmMapper.java
  24. 19 1
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  25. 18 0
      fs-service/src/main/java/com/fs/qw/param/QwUserByDeptParam.java
  26. 1 0
      fs-service/src/main/java/com/fs/qw/param/QwUserListParam.java
  27. 1 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  28. 6 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  29. 40 0
      fs-service/src/main/java/com/fs/statis/dto/ComprehensiveStatisticsDTO.java
  30. 17 0
      fs-service/src/main/java/com/fs/statis/param/ComprehensiveStatisticsParam.java
  31. 69 0
      fs-service/src/main/java/com/fs/utils/FileCacheService.java
  32. 5 8
      fs-service/src/main/resources/application-config-druid-cqtyt.yml
  33. 28 0
      fs-service/src/main/resources/mapper/company/CompanyDeptMapper.xml
  34. 146 0
      fs-service/src/main/resources/mapper/company/CourseWatchLogStatisticsMapper.xml
  35. 186 48
      fs-service/src/main/resources/mapper/company/StatisticManageMapper.xml
  36. 31 1
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  37. 272 8
      fs-user-app/src/main/java/com/fs/app/controller/store/UserScrmController.java
  38. BIN
      fs-user-app/src/main/resources/fx-old.jpg
  39. BIN
      fs-user-app/src/main/resources/fx.jpg
  40. 0 0
      fs-user-app/src/main/resources/qr.jpg
  41. BIN
      fs-user-app/src/main/resources/simsunb.ttf

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

@@ -52,7 +52,7 @@ public class StatisticManageController {
     }
 
     /**
-     * 根据公司id获取部门下拉
+     * 根据部门id获取用户下拉信息
      * @return
      */
     @GetMapping("/getSearchUserInfo")

+ 30 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseTrafficLogController.java

@@ -9,7 +9,9 @@ import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.course.param.FsCourseTrafficLogParam;
 import com.fs.course.param.InternetTrafficParam;
+import com.fs.course.param.StatisticsSummaryParam;
 import com.fs.course.vo.FsCourseTrafficLogListVO;
+import com.fs.course.vo.StatisticsSummaryVO;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -135,4 +137,32 @@ public class FsCourseTrafficLogController extends BaseController
         return R.ok().put("data", null);  // 返回计算结果
     }
 
+    /**
+     * 流量统计汇总
+     * @param param
+     * @return
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseTrafficLog:statistics')")
+    @GetMapping("/statisticsSummaryList")
+    public TableDataInfo statisticsSummaryList(StatisticsSummaryParam param)
+    {
+        List<StatisticsSummaryVO> list = fsCourseTrafficLogService.getStatisticsSummaryList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出汇总统计报表
+     * @param param
+     * @return
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseTrafficLog:statisticsExport')")
+    @GetMapping("/exportStatisticsSummary")
+    public AjaxResult exportStatisticsSummary(StatisticsSummaryParam param)
+    {
+        List<StatisticsSummaryVO> list = fsCourseTrafficLogService.getStatisticsSummaryListNotPage(param);
+        ExcelUtil<StatisticsSummaryVO> util = new ExcelUtil<StatisticsSummaryVO>(StatisticsSummaryVO.class);
+        return util.exportExcel(list, "看课流量统计汇总");
+
+    }
+
 }

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

@@ -22,7 +22,7 @@ public class ComprehensiveStatisticsTask {
      * 每隔一小时执行一次
      */
     public void execute() {
-        iStatisticManageService.executeTask();
+        iStatisticManageService.executeTask(null);
     }
 
 }

+ 16 - 2
fs-admin/src/main/java/com/fs/task/SgTestController.java

@@ -1,11 +1,15 @@
 package com.fs.task;
 
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
  import com.fs.company.service.IStatisticManageService;
 
 import javax.annotation.Resource;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 
 /**
  * @description:
@@ -29,8 +33,18 @@ public class SgTestController {
     }
 
     @RequestMapping("/statistic")
-    public void executeTask(){
-        iStatisticManageService.executeTask();
+    public void executeTask(@RequestParam(required = false) String dateTime){
+        Date date = null;
+        if (dateTime != null && !dateTime.isEmpty()) {
+            try {
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+                date = sdf.parse(dateTime);
+            } catch (ParseException e) {
+                // 处理日期解析异常
+                e.printStackTrace();
+            }
+        }
+        iStatisticManageService.executeTask(date);
     }
 
 }

+ 3 - 2
fs-company/src/main/java/com/fs/company/controller/qw/QwDeptController.java

@@ -36,6 +36,9 @@ public class QwDeptController extends BaseController
     @Autowired
     private TokenService tokenService;
 
+    @Autowired
+    QwCompanyMapper qwCompanyMapper;
+
     /**
      * 查询企业微信部门列表
      */
@@ -47,8 +50,6 @@ public class QwDeptController extends BaseController
         List<QwDept> list = qwDeptService.selectQwDeptList(qwDept);
         return getDataTable(list);
     }
-    @Autowired
-    QwCompanyMapper qwCompanyMapper;
 
     /**
     * 同步企业微信 部门信息

+ 12 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -235,6 +235,18 @@ public class QwUserController extends BaseController
     }
 
 
+    /**
+    * 查询部门下的 企业微信账号
+    */
+    @PostMapping("/getQwUserByDept")
+    public R getQwUserByDept(@RequestBody QwUserByDeptParam deptParam){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        deptParam.setCompanyId(loginUser.getCompany().getCompanyId());
+        return R.ok().put("data",qwUserService.getQwUserByDept(deptParam)) ;
+    }
+
+
+
 
     @PreAuthorize("@ss.hasPermi('qw:user:login')")
     @PostMapping("/getQwIpad")

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

@@ -30,6 +30,11 @@ public class CompanyDeptUserInfo {
     */
    private String deptName;
 
+   /**
+    * 部门名称
+    */
+   private Long parentId;
+
    /**
     * 员工id
     */

+ 53 - 0
fs-service/src/main/java/com/fs/company/domain/CourseWatchLogData.java

@@ -0,0 +1,53 @@
+package com.fs.company.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+/**
+ * @description: 接受单个人查询的结果
+ * @author: Guos
+ * @time: 2025/11/10 上午9:52
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class CourseWatchLogData {
+
+
+    private Long companyUserId;
+
+    private Long videoId;
+
+    private Long courseId;
+
+    private String project;
+
+    private Integer logType;
+
+    private java.util.Date createTime;
+
+    private BigDecimal redPacketAmount;
+
+    private Integer redPacketNum;
+
+    private Integer answerNum;
+
+    // toString method
+    @Override
+    public String toString() {
+        return "CourseWatchLog{" +
+                "companyUserId=" + companyUserId +
+                ", videoId=" + videoId +
+                ", courseId=" + courseId +
+                ", project='" + project + '\'' +
+                ", logType='" + logType + '\'' +
+                ", createTime=" + createTime +
+                '}';
+    }
+
+}

+ 49 - 0
fs-service/src/main/java/com/fs/company/domain/CourseWatchLogStatistics.java

@@ -0,0 +1,49 @@
+package com.fs.company.domain;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @description: 统计表实体类
+ * @author: Guos
+ * @time: 2025/11/10 上午10:11
+ */
+@Data
+public class CourseWatchLogStatistics {
+
+    private Long id;
+
+    private Long companyUserId;
+
+    private Long companyId;
+
+    private Integer deptId;
+
+    private java.util.Date statisticsTime;
+
+    private Long courseId;
+
+    private Long videoId;
+
+    private Long projectId;
+
+    private Integer logType;
+
+    private Integer sendCount;
+
+    private Date createTime;
+
+    private Date updateTime;
+
+    private Integer answerNum;
+
+    private Integer redPacketNum;
+
+    private BigDecimal redPacketAmount;
+
+
+}

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

@@ -1,42 +0,0 @@
-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;
-}

+ 7 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyDeptMapper.java

@@ -1,6 +1,7 @@
 package com.fs.company.mapper;
 
 import com.fs.company.domain.CompanyDept;
+import com.fs.course.vo.DeptFullPathVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
@@ -126,4 +127,10 @@ public interface CompanyDeptMapper
     List<CompanyDept> selectCompanyDeptByIds(@Param("depts")List<Long> deptIds);
 
     List<Long> getCurrentDeptIdDownTreeIds(@Param("deptId") Long deptId);
+
+    /**
+     * 根据公司id -> 部门 & 全路径查询
+     * @return
+     */
+    List<DeptFullPathVO> getDeptFullPathList(@Param("companyId") Long companyId);
 }

+ 60 - 0
fs-service/src/main/java/com/fs/company/mapper/CourseWatchLogStatisticsMapper.java

@@ -0,0 +1,60 @@
+package com.fs.company.mapper;
+
+import com.fs.company.domain.CourseWatchLogStatistics;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/11/10 上午10:13
+ */
+public interface CourseWatchLogStatisticsMapper {
+
+    /**
+     * 插入新的课程观看统计记录
+     */
+    int insert(CourseWatchLogStatistics record);
+
+    /**
+     * 根据主键更新记录
+     */
+    int updateByPrimaryKey(CourseWatchLogStatistics record);
+
+    /**
+     * 根据主键删除记录
+     */
+    int deleteByPrimaryKey(Long id);
+
+    /**
+     * 根据主键查询记录
+     */
+    CourseWatchLogStatistics selectByPrimaryKey(Long id);
+
+    /**
+     * 根据公司员工ID查询记录
+     */
+    List<CourseWatchLogStatistics> selectByCompanyUserId(Long companyUserId);
+
+    /**
+     * 根据统计时间范围查询记录
+     */
+    List<CourseWatchLogStatistics> selectByStatisticsTimeRange(@Param("startTime") Date startTime,
+                                                            @Param("endTime") Date endTime);
+    /**
+     * 获取需要更新的数据
+     * @return
+     */
+    List<CourseWatchLogStatistics> getNeedUpdateData(@Param("statisticsTime") Date statisticsTime);
+
+    /**
+     * 按照公司、部门更新之前先删除数据
+     * @param deptId
+     * @param statisticsTime
+     */
+    Integer deleteByStatisticsTimeAndDeptId(@Param("userId")Long userId,
+                                            @Param("deptId") Long deptId,
+                                            @Param("statisticsTime") Date statisticsTime);
+}

+ 47 - 15
fs-service/src/main/java/com/fs/company/mapper/StatisticManageMapper.java

@@ -2,8 +2,10 @@ package com.fs.company.mapper;
 
 import com.fs.company.domain.CompanyDeptUserInfo;
 import com.fs.company.domain.ComprehensiveDailyStats;
+import com.fs.company.domain.CourseWatchLogData;
 import com.fs.company.dto.CompanyDeptUserInfoDTO;
-import com.fs.company.dto.ComprehensiveStatisticsDTO;
+import com.fs.statis.dto.ComprehensiveStatisticsDTO;
+import com.fs.statis.param.ComprehensiveStatisticsParam;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.Date;
@@ -61,13 +63,6 @@ public interface StatisticManageMapper {
      */
     Integer deleteById(@Param("id") Long id);
 
-    //获取公司、部门、员工信息
-    List<CompanyDeptUserInfo> getCompanyAndDeptAndDeptUserList(@Param("companyId") Long companyId);
-
-    List<CompanyDeptUserInfo> getCompanyInfo();
-
-    CompanyDeptUserInfoDTO getStatisticNum(@Param("userIds") Long userIds);
-
     /**
      * 按照个人统计
      * @param dimension 维度
@@ -85,13 +80,14 @@ public interface StatisticManageMapper {
      */
     List<ComprehensiveDailyStats> getStatisticNumByDeptId(@Param("startTime") Date startTime, @Param("endTime") Date endTime, @Param("deptIds") Long... deptIds);
 
-    /**
-     * 按照公司统计
-     * @param startTime
-     * @param endTime
-     * @param companyIds
-     */
-    List<ComprehensiveDailyStats> getStatisticNumByCompanyId(@Param("startTime") Date startTime, @Param("endTime") Date endTime, @Param("companyIds") Long... companyIds);
+    //========================
+
+    //获取公司、部门、员工信息
+    List<CompanyDeptUserInfo> getCompanyAndDeptAndDeptUserList(@Param("companyId") Long companyId);
+
+    List<CompanyDeptUserInfo> getCompanyInfo();
+
+    CompanyDeptUserInfoDTO getStatisticNum(@Param("userIds") Long userIds);
 
     /**
      * 根据公司id获取部门下拉搜索信息
@@ -105,4 +101,40 @@ public interface StatisticManageMapper {
      */
     List<CompanyDeptUserInfo> getSearchUserInfo(@Param("id") Long id);
 
+    /**
+     *
+     * 获取部门统计数据
+     * @param companyUserId
+     * @param dateTime
+     * @return
+     */
+    List<CourseWatchLogData> getCourseWatchLogData(@Param("companyUserId") Long companyUserId, @Param("dateTime") Date dateTime);
+
+    /**
+     *
+     * 获取部门统计数据
+     * @param companyUserId
+     * @param videoId
+     * @param dateTime
+     * @return
+     */
+    CourseWatchLogData getAnswerAndRedPacketData(@Param("companyUserId")Long companyUserId, @Param("videoId")Long videoId, @Param("dateTime") Date dateTime);
+
+    /**
+     * 按照部门获取数据
+     * @param param
+     */
+    ComprehensiveStatisticsDTO getStatisticDataByDeptId(ComprehensiveStatisticsParam param);
+
+    /**
+     * 根据用户id获取用户的详细信息
+     * @param id
+     */
+    CompanyDeptUserInfo getUserInfoByUserId(@Param("userId") Long id);
+
+    /**
+     * 根据用户id获取用户的详细信息
+     * @param id
+     */
+    CompanyDeptUserInfo getUserInfoByDeptId(@Param("deptId") Long id);
 }

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

@@ -3,6 +3,7 @@ package com.fs.company.service;
 import com.fs.company.domain.CompanyDeptUserInfo;
 import com.fs.statis.param.ComprehensiveStatisticsParam;
 
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -39,6 +40,6 @@ public interface IStatisticManageService {
     /**
      * 用来执行定时任务
      */
-    void executeTask();
+    void executeTask(Date dateTime);
 
 }

+ 359 - 89
fs-service/src/main/java/com/fs/company/service/impl/StatisticManageServiceImpl.java

@@ -3,22 +3,27 @@ package com.fs.company.service.impl;
 import cn.hutool.core.date.StopWatch;
 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.domain.CourseWatchLogData;
+import com.fs.company.domain.CourseWatchLogStatistics;
+import com.fs.company.mapper.CourseWatchLogStatisticsMapper;
 import com.fs.company.mapper.StatisticManageMapper;
 import com.fs.company.service.IStatisticManageService;
+import com.fs.statis.dto.ComprehensiveStatisticsDTO;
 import com.fs.statis.param.ComprehensiveStatisticsParam;
+import com.google.common.collect.Lists;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.ObjectUtils;
 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.*;
+import java.util.stream.Collectors;
+
 /**
- * @description:
+ * @description: sql的代码以及整合了大部门,这里很多代码都可以整合按照不同的维度进行
  * @author: Guos
  * @time: 2025/10/30 上午9:21
  */
@@ -29,6 +34,9 @@ public class StatisticManageServiceImpl implements IStatisticManageService {
     @Resource
     private StatisticManageMapper statisticManageMapper;
 
+    @Resource
+    private CourseWatchLogStatisticsMapper courseWatchLogStatisticsMapper;
+
     /**
      * 统计
      * 按照部门分组情况
@@ -38,145 +46,407 @@ public class StatisticManageServiceImpl implements IStatisticManageService {
     public Object statisticMain(ComprehensiveStatisticsParam param) {
         if(param.getDimension() == DimensionEnum.COMPANY.getValue()){
             Assert.notNull(param.getId(), "按公司展示查询条件不能为空!");
-            return getStatisticNumByCompanyId(param.getStartTime(), param.getEndTime(), param.getId());
+            return getStatisticNumByCompanyId(param);
         }
         if(param.getDimension() == DimensionEnum.DEPARTMENT.getValue()){
             Assert.notNull(param.getId(), "按部门展示查询条件不能为空!");
-            return getStatisticNumByDeptId(param.getStartTime(), param.getEndTime(), param.getId());
+            return getStatisticNumByDeptId(param);
         }
         if (param.getDimension() == DimensionEnum.PERSONAL.getValue()){
             Assert.notNull(param.getId(), "按个人展示查询条件不能为空!");
-            return getStatisticNumByPersonal(DimensionEnum.PERSONAL.getValue(), param.getStartTime(), param.getEndTime(), param.getId());
+            return getStatisticNumByPersonal(param);
         }
         return null;
     }
 
     /**
-     * 获取搜索公司信息
+     * 获取个人统计数据
      */
-    @Override
-    public List<CompanyDeptUserInfo> getSearchCompanyInfo(){
-        return statisticManageMapper.getCompanyInfo();
+    public List getStatisticNumByDeptId(ComprehensiveStatisticsParam param){
+        //先获取部门下的用户的信息
+        Long id = param.getId();
+        List<CompanyDeptUserInfo> searchUserInfo = statisticManageMapper.getSearchUserInfo(id);
+        List result = Lists.newArrayList();
+        List<Date> dates = generateDateList(param.getStartTime(), param.getEndTime());
+        for (Date date : dates){
+            param.setStartTime(date);
+            param.setEndTime(date);
+            for (CompanyDeptUserInfo companyDeptUserInfo : searchUserInfo) {
+                String deptName = companyDeptUserInfo.getDeptName();
+                String companyName = companyDeptUserInfo.getCompanyName();
+                Long deptId = companyDeptUserInfo.getDeptId();
+                Long companyId = companyDeptUserInfo.getCompanyId();
+                param.setUserId(companyDeptUserInfo.getUserId());
+                ComprehensiveStatisticsDTO statisticDataByDeptId =
+                        statisticManageMapper.getStatisticDataByDeptId(param);
+                if(ObjectUtils.isEmpty(statisticDataByDeptId)){
+                    statisticDataByDeptId = new ComprehensiveStatisticsDTO();
+                    statisticDataByDeptId.setAnswerNum(BigDecimal.ZERO.intValue());
+                    statisticDataByDeptId.setRedPacketNum(BigDecimal.ZERO.intValue());
+                    statisticDataByDeptId.setSendCount(BigDecimal.ZERO.intValue());
+                    statisticDataByDeptId.setRedPacketAmount(BigDecimal.ZERO);
+                }
+                statisticDataByDeptId.setCompanyUserName(companyDeptUserInfo.getNickName());
+                statisticDataByDeptId.setCompanyUserId(companyDeptUserInfo.getUserId());
+                statisticDataByDeptId.setDeptName(deptName);
+                statisticDataByDeptId.setDeptId(deptId.intValue());
+                statisticDataByDeptId.setCompanyName(companyName);
+                statisticDataByDeptId.setCompanyId(companyId);
+                statisticDataByDeptId.setStatisticsTime(date);
+                result.add(statisticDataByDeptId);
+            }
+        }
+        return result;
     }
 
     /**
-     * 根据公司id获取部门下拉搜索信息
-     * @param id
+     * 获取个人统计数据
      */
-    @Override
-    public List<CompanyDeptUserInfo> getSearchDeptInfo(Long id){
-        return statisticManageMapper.getSearchDeptInfo(id);
+    public List getStatisticNumByPersonal(ComprehensiveStatisticsParam param){
+        //先获取用户的信息
+        Long id = param.getId();
+        CompanyDeptUserInfo userInfoByUserId = statisticManageMapper.getUserInfoByUserId(id);
+        List result = Lists.newArrayList();
+        List<Date> dates = generateDateList(param.getStartTime(), param.getEndTime());
+        for (Date date : dates){
+            param.setStartTime(date);
+            param.setEndTime(date);
+            param.setDeptId(userInfoByUserId.getDeptId());
+            param.setUserId(id);
+            ComprehensiveStatisticsDTO statisticDataByDeptId = statisticManageMapper.getStatisticDataByDeptId(param);
+            if(ObjectUtils.isEmpty(statisticDataByDeptId)){
+                statisticDataByDeptId = new ComprehensiveStatisticsDTO();
+                statisticDataByDeptId.setAnswerNum(BigDecimal.ZERO.intValue());
+                statisticDataByDeptId.setRedPacketNum(BigDecimal.ZERO.intValue());
+                statisticDataByDeptId.setSendCount(BigDecimal.ZERO.intValue());
+                statisticDataByDeptId.setRedPacketAmount(BigDecimal.ZERO);
+            }
+            statisticDataByDeptId.setDeptName(userInfoByUserId.getDeptName());
+            statisticDataByDeptId.setCompanyUserName(userInfoByUserId.getNickName());
+            statisticDataByDeptId.setCompanyUserId(userInfoByUserId.getUserId());
+            statisticDataByDeptId.setDeptName(userInfoByUserId.getDeptName());
+            statisticDataByDeptId.setDeptId(userInfoByUserId.getDeptId().intValue());
+            statisticDataByDeptId.setCompanyName(userInfoByUserId.getCompanyName());
+            statisticDataByDeptId.setCompanyId(userInfoByUserId.getCompanyId());
+            statisticDataByDeptId.setStatisticsTime(date);
+            result.add(statisticDataByDeptId);
+        }
+        return result;
     }
 
     /**
-     * 根据部门id获取用户下拉信息
-     * @param id
+     * 根据公司获取统计数据
      */
-    @Override
-    public List<CompanyDeptUserInfo> getSearchUserInfo(Long id){
-        return statisticManageMapper.getSearchUserInfo(id);
+    public List getStatisticNumByCompanyId(ComprehensiveStatisticsParam param){
+        //先从公司拿到公司和部门的信息
+        List<CompanyDeptUserInfo> searchDeptInfo = getSearchDeptInfo(param.getId());
+        List result = Lists.newArrayList();
+
+        List<Date> dates = generateDateList(param.getStartTime(), param.getEndTime());
+        for (Date date : dates){
+            param.setStartTime(date);
+            param.setEndTime(date);
+            for (CompanyDeptUserInfo companyDeptUserInfo : searchDeptInfo) {
+                String deptName = companyDeptUserInfo.getDeptName();
+                String companyName = companyDeptUserInfo.getCompanyName();
+                Long deptId = companyDeptUserInfo.getDeptId();
+                Long companyId = companyDeptUserInfo.getCompanyId();
+                param.setDeptId(deptId);
+                ComprehensiveStatisticsDTO statisticDataByDeptId =
+                        statisticManageMapper.getStatisticDataByDeptId(param);
+                if(ObjectUtils.isEmpty(statisticDataByDeptId)){
+                    statisticDataByDeptId = new ComprehensiveStatisticsDTO();
+                    statisticDataByDeptId.setAnswerNum(BigDecimal.ZERO.intValue());
+                    statisticDataByDeptId.setRedPacketNum(BigDecimal.ZERO.intValue());
+                    statisticDataByDeptId.setSendCount(BigDecimal.ZERO.intValue());
+                    statisticDataByDeptId.setRedPacketAmount(BigDecimal.ZERO);
+                }
+                statisticDataByDeptId.setDeptName(deptName);
+                statisticDataByDeptId.setDeptId(deptId.intValue());
+                statisticDataByDeptId.setCompanyName(companyName);
+                statisticDataByDeptId.setCompanyId(companyId);
+                statisticDataByDeptId.setStatisticsTime(date);
+                result.add(statisticDataByDeptId);
+            }
+        }
+        return result;
     }
 
     /**
-     * 获取个人统计数据
-     * @param dimension 维度
+     * 生成开始时间到结束时间之间的每日时间数组
      * @param startTime 开始时间
      * @param endTime 结束时间
-     * @param userIds 用户id
-     * @return
+     * @return 时间数组列表
      */
-    public List<ComprehensiveDailyStats> getStatisticNumByPersonal(Integer dimension, Date startTime, Date endTime, Long... userIds) {
-        return statisticManageMapper.getStatisticNumByPersonal(dimension, startTime, endTime, userIds);
+    public List<Date> generateDateList(Date startTime, Date endTime) {
+        List<Date> dateList = new ArrayList<>();
+        if (startTime == null || endTime == null) {
+            return dateList;
+        }
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(startTime);
+
+        // 设置时间为当天00:00:00,确保按天计算
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+
+        Date currentDate = calendar.getTime();
+        Date finalEndTime = getStartOfDay(endTime);
+
+        while (!currentDate.after(finalEndTime)) {
+            dateList.add(currentDate);
+            calendar.add(Calendar.DAY_OF_MONTH, 1);
+            currentDate = calendar.getTime();
+        }
+
+        return dateList;
     }
 
     /**
-     * 获取部门统计数据
-     * @param startTime 开始时间
-     * @param endTime 结束时间
-     * @param deptId 用户id
-     * @return
+     * 获取指定日期的开始时间(00:00:00)
+     * @param date 指定日期
+     * @return 当天开始时间
      */
-    public List<ComprehensiveDailyStats> getStatisticNumByDeptId(Date startTime, Date endTime, Long... deptId) {
-        return statisticManageMapper.getStatisticNumByDeptId(startTime, endTime, deptId);
+    private Date getStartOfDay(Date date) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        return calendar.getTime();
     }
 
     /**
-     * 获取部门统计数据
-     * @param startTime 开始时间
-     * @param endTime 结束时间
-     * @param deptId 用户id
-     * @return
+     * 获取搜索公司信息
+     */
+    @Override
+    public List<CompanyDeptUserInfo> getSearchCompanyInfo(){
+        return statisticManageMapper.getCompanyInfo();
+    }
+
+    /**
+     * 根据公司id获取部门下拉搜索信息
+     * @param id
+     */
+    @Override
+    public List<CompanyDeptUserInfo> getSearchDeptInfo(Long id){
+        return statisticManageMapper.getSearchDeptInfo(id);
+    }
+
+    /**
+     * 根据部门id获取用户下拉信息
+     * @param id
      */
-    public List<ComprehensiveDailyStats> getStatisticNumByCompanyId(Date startTime, Date endTime, Long... deptId) {
-        return statisticManageMapper.getStatisticNumByCompanyId(startTime, endTime, deptId);
+    @Override
+    public List<CompanyDeptUserInfo> getSearchUserInfo(Long id){
+        return statisticManageMapper.getSearchUserInfo(id);
     }
 
     /**
-     * 执行定时任务
+     * 执行定时任务3.0
+     * 公司,部门,人,【课程,项目,视频id】,【发送,看视频】,【答题,完课,红包领取,红包金额】
      * 还需要考虑在统计部门数据时,部门下面没有用户,某个时候这个部门下面又加入新的用户了,这个时候就会出现数据偏差
      */
     @Override
-    public void executeTask() {
+    public void executeTask(Date dateTime) {
         StopWatch stopWatch = new StopWatch();
         stopWatch.start("gs-执行数据统计任务");
         List<CompanyDeptUserInfo> companyDeptdUserList = statisticManageMapper.getCompanyAndDeptAndDeptUserList(null);
         log.info("统计人数列表:{}", companyDeptdUserList.size());
+        if(null == dateTime){
+            dateTime = new Date();
+        }
+        Date finalDateTime = dateTime;
         companyDeptdUserList.forEach(companyDeptUserInfo -> {
-            CompanyDeptUserInfoDTO statisticNum =  new CompanyDeptUserInfoDTO();
-            boolean empty = null == companyDeptUserInfo.getUserId(); //用户id为空返回true
-            if(!empty){
-                statisticNum = statisticManageMapper.getStatisticNum(companyDeptUserInfo.getUserId());
-            }
-            ComprehensiveDailyStats comprehensiveDailyStats = component(companyDeptUserInfo, statisticNum);
-            ComprehensiveDailyStats selectResult = null;
-            if(!empty){
-                //用户id不为空,我们就用用户id去查询这个日期是否有数据
-                selectResult = statisticManageMapper.selectByUserAndDate(comprehensiveDailyStats.getUserId(), comprehensiveDailyStats.getStatisticsTime());
-            }else{
-                //如果用户id为空,说明部门下面没有人(没人情况下,部门id只会在当天的日期中存在一条),先按照部门去查询后删除(或者直接按照id更新就可以了)
-                selectResult = statisticManageMapper.selectByDeptAndDate(companyDeptUserInfo.getDeptId(), comprehensiveDailyStats.getStatisticsTime());
-            }
-            if(!ObjectUtils.isEmpty(selectResult)){
-                comprehensiveDailyStats.setId(selectResult.getId());
-                statisticManageMapper.updateById(comprehensiveDailyStats);
+            Long userId = companyDeptUserInfo.getUserId();
+            courseWatchLogStatisticsMapper.deleteByStatisticsTimeAndDeptId(userId, companyDeptUserInfo.getDeptId(), finalDateTime);
+            //第一步通过人去查询【课程,项目,视频id】,【发送,看视频状态】
+            List<CourseWatchLogData> courseWatchLogData = statisticManageMapper.getCourseWatchLogData(userId, finalDateTime);
+            CourseWatchLogStatistics courseWatchLogStatistics = component(companyDeptUserInfo, finalDateTime);
+            if(CollectionUtils.isEmpty(courseWatchLogData)){
+                //如果为空,那么这个人其他数据都是0直接放入数据库
+                //TODO 需要判断当天是否有数据了,如果有数据,就需要更新(为空就刷新一下更新时间就可以了)
+                courseWatchLogStatisticsMapper.insert(courseWatchLogStatistics);
+                log.info("companyUserId:{}, 统计时间:{},无数据!", userId, finalDateTime);
             }else{
-                statisticManageMapper.insert(comprehensiveDailyStats);
+                //先按照project分组,这里过滤了为空的项目,有的数据project字段就是空的,这个统计时候去处理就麻烦了
+                Map<String, List<CourseWatchLogData>> projectIdCollect = courseWatchLogData.stream().filter(c -> c.getProject() != null)
+                        .collect(Collectors.groupingBy(CourseWatchLogData::getProject));
+                for (Map.Entry<String, List<CourseWatchLogData>> entry : projectIdCollect.entrySet()) {
+                    String projectId = entry.getKey();
+                    List<CourseWatchLogData> courseWatchLogDataList = entry.getValue();
+                    //有时候这个字段是空的
+                    courseWatchLogStatistics.setProjectId(Long.parseLong(projectId));
+                    int size = courseWatchLogDataList.size();
+                    if (size == 1) {
+                        CourseWatchLogData detail = courseWatchLogDataList.get(0);
+                        courseWatchLogStatistics.setCourseId(detail.getCourseId());
+                        courseWatchLogStatistics.setVideoId(detail.getVideoId());
+                        courseWatchLogStatistics.setLogType(detail.getLogType());
+                        courseWatchLogStatistics.setSendCount(1);
+                        courseWatchLogStatisticsMapper.insert(courseWatchLogStatistics);
+                    }else{
+                        //按照courseId分组
+                        Map<Long, List<CourseWatchLogData>> courseIdCollect = courseWatchLogDataList.stream()
+                                .collect(Collectors.groupingBy(CourseWatchLogData::getCourseId));
+                        for (Map.Entry<Long, List<CourseWatchLogData>> courseEntry : courseIdCollect.entrySet()) {
+                            Long courseId = courseEntry.getKey();
+                            List<CourseWatchLogData> byCourseIdList = courseEntry.getValue();
+                            courseWatchLogStatistics.setCourseId(courseId);
+                            int byCourseIdListSize = byCourseIdList.size();
+                            if(byCourseIdListSize == 1){
+                                CourseWatchLogData detail = courseWatchLogDataList.get(0);
+                                courseWatchLogStatistics.setVideoId(detail.getVideoId());
+                                courseWatchLogStatistics.setLogType(detail.getLogType());
+                                courseWatchLogStatistics.setSendCount(1);
+                                courseWatchLogStatisticsMapper.insert(courseWatchLogStatistics);
+                            }else{
+                                //按照VideoId分组
+                                Map<Long, List<CourseWatchLogData>> videoIdCollect = byCourseIdList.stream()
+                                        .collect(Collectors.groupingBy(CourseWatchLogData::getVideoId));
+                                for (Map.Entry<Long, List<CourseWatchLogData>> videoIdEntry : videoIdCollect.entrySet()) {
+                                    Long videoId = videoIdEntry.getKey();
+                                    List<CourseWatchLogData> byVideoIdList = videoIdEntry.getValue();
+                                    courseWatchLogStatistics.setVideoId(videoId);
+                                    int byVideoIdListSize = byVideoIdList.size();
+                                    if(byVideoIdListSize == 1){
+                                        CourseWatchLogData detail = courseWatchLogDataList.get(0);
+                                        courseWatchLogStatistics.setLogType(detail.getLogType());
+                                        courseWatchLogStatistics.setSendCount(1);
+                                        courseWatchLogStatisticsMapper.insert(courseWatchLogStatistics);
+                                    }else{
+                                        // 直接统计按照不同的logType进行数量统计
+                                        Map<Integer, List<CourseWatchLogData>> logTypeCollect = byVideoIdList.stream()
+                                                .collect(Collectors.groupingBy(CourseWatchLogData::getLogType));
+                                        for (Map.Entry<Integer, List<CourseWatchLogData>> logTypeEntry : logTypeCollect.entrySet()) {
+                                            Integer logType = logTypeEntry.getKey();
+                                            List<CourseWatchLogData> logTypeList = logTypeEntry.getValue();
+                                            courseWatchLogStatistics.setLogType(logType);
+                                            courseWatchLogStatistics.setSendCount(logTypeList.size());
+                                            courseWatchLogStatisticsMapper.insert(courseWatchLogStatistics);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                log.info("companyUserId:{}, 统计时间:{},有数据,数据条数:{}!", userId, finalDateTime, courseWatchLogData.size());
             }
         });
         stopWatch.stop();
+        stopWatch.start("反查数据更新红包等信息");
+        //上面代码其实都是差不多,为了方便之后优化代码,保持整个逻辑清晰,先将整个数据保存后,再反查数据将答题数量、红包领取数量,以及金额统计更新
+        updateCourseRedPacket(finalDateTime);
+        stopWatch.stop();
         log.info("gs-执行数据统计任务完成,耗时:{}", stopWatch.getTotalTimeSeconds());
     }
 
-    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());
-        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));
+    /**
+     * 通过反查数据来更新红包信息
+     */
+    public void updateCourseRedPacket(Date dateTime){
+        List<CourseWatchLogStatistics> needUpdateData = courseWatchLogStatisticsMapper.getNeedUpdateData(dateTime);
+        for(CourseWatchLogStatistics courseWatchLogStatistics:needUpdateData){
+            CourseWatchLogData answerAndRedPacketData = statisticManageMapper.getAnswerAndRedPacketData(courseWatchLogStatistics.getCompanyUserId(),
+                    courseWatchLogStatistics.getVideoId(), dateTime);
+            courseWatchLogStatistics.setAnswerNum(answerAndRedPacketData.getAnswerNum());
+            courseWatchLogStatistics.setRedPacketNum(answerAndRedPacketData.getRedPacketNum());
+            courseWatchLogStatistics.setRedPacketAmount(answerAndRedPacketData.getRedPacketAmount());
+            courseWatchLogStatisticsMapper.updateByPrimaryKey(courseWatchLogStatistics);
         }
-      return target;
     }
 
+    //返回一个所有数据都是空的对象
+    public CourseWatchLogStatistics component(CompanyDeptUserInfo companyDeptUserInfo, Date dateTime){
+        CourseWatchLogStatistics build = new CourseWatchLogStatistics();
+                build.setCompanyId(companyDeptUserInfo.getCompanyId());
+                build.setCompanyUserId(companyDeptUserInfo.getUserId());
+                build.setDeptId(companyDeptUserInfo.getDeptId().intValue());
+                build.setStatisticsTime(dateTime);
+                build.setLogType(null);
+                build.setSendCount(null);
+                build.setProjectId(null);
+                build.setVideoId(null);
+                build.setCourseId(null);
+                build.setRedPacketAmount(BigDecimal.ZERO);
+                build.setAnswerNum(BigDecimal.ZERO.intValue());
+                build.setRedPacketNum(BigDecimal.ZERO.intValue());
+                build.setCreateTime(new Date());
+                build.setUpdateTime(new Date());
+        return build;
+    }
+
+
+//    /**
+//     * 执行定时任务
+//     * 还需要考虑在统计部门数据时,部门下面没有用户,某个时候这个部门下面又加入新的用户了,这个时候就会出现数据偏差
+//     */
+//    @Override
+//    public void executeTask() {
+//        StopWatch stopWatch = new StopWatch();
+//        stopWatch.start("gs-执行数据统计任务");
+//        List<CompanyDeptUserInfo> companyDeptdUserList = statisticManageMapper.getCompanyAndDeptAndDeptUserList(null);
+//        log.info("统计人数列表:{}", companyDeptdUserList.size());
+//        companyDeptdUserList.forEach(companyDeptUserInfo -> {
+//            CompanyDeptUserInfoDTO statisticNum =  new CompanyDeptUserInfoDTO();
+//            boolean empty = null == companyDeptUserInfo.getUserId(); //用户id为空返回true
+//            if(!empty){
+//                statisticNum = statisticManageMapper.getStatisticNum(companyDeptUserInfo.getUserId());
+//            }
+//            ComprehensiveDailyStats comprehensiveDailyStats = component(companyDeptUserInfo, statisticNum);
+//            ComprehensiveDailyStats selectResult = null;
+//            if(!empty){
+//                //用户id不为空,我们就用用户id去查询这个日期是否有数据
+//                selectResult = statisticManageMapper.selectByUserAndDate(comprehensiveDailyStats.getUserId(), comprehensiveDailyStats.getStatisticsTime());
+//            }else{
+//                //如果用户id为空,说明部门下面没有人(没人情况下,部门id只会在当天的日期中存在一条),先按照部门去查询后删除(或者直接按照id更新就可以了)
+//                selectResult = statisticManageMapper.selectByDeptAndDate(companyDeptUserInfo.getDeptId(), comprehensiveDailyStats.getStatisticsTime());
+//            }
+//            if(!ObjectUtils.isEmpty(selectResult)){
+//                comprehensiveDailyStats.setId(selectResult.getId());
+//                statisticManageMapper.updateById(comprehensiveDailyStats);
+//            }else{
+//                statisticManageMapper.insert(comprehensiveDailyStats);
+//            }
+//        });
+//        stopWatch.stop();
+//        log.info("gs-执行数据统计任务完成,耗时:{}", stopWatch.getTotalTimeSeconds());
+//    }
+//
+//    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());
+//        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;
+//    }
 
 }
 

+ 33 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java

@@ -7,8 +7,10 @@ import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.course.domain.FsCourseTrafficLog;
 import com.fs.course.param.FsCourseTrafficLogParam;
+import com.fs.course.param.StatisticsSummaryParam;
 import com.fs.course.param.TrafficRecord;
 import com.fs.course.vo.FsCourseTrafficLogListVO;
+import com.fs.course.vo.StatisticsSummaryVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -195,4 +197,35 @@ public interface FsCourseTrafficLogMapper
     @Select("SELECT SUM(T.internet_traffic)/1024 AS totalInternetTraffic FROM fs_course_traffic_log T " +
             "WHERE DATE(T.create_time) = DATE(CURDATE() - INTERVAL 1 DAY) AND T.company_id = #{companyId} GROUP BY T.company_id ")
     Long sumTrafficByCompanyYesterday(Long companyId);
+
+    @Select({
+            "<script>" +
+            "SELECT \n" +
+            "t1.company_id,\n" +
+            "t2.company_name,\n" +
+            "<if test='param.statisticsType == 2'>\n" +
+                "t4.dept_id,\n" +
+                "t4.dept_name,\n" +
+            "</if>" +
+            "sum(internet_traffic) as totalInternetTraffic,\n" +
+            "CONCAT(#{param.startDate},'到',#{param.endDate}) as dateRange\n" +
+            "FROM fs_course_traffic_log t1\n" +
+            "inner join company t2 on t1.company_id = t2.company_id\n" +
+            "<if test='param.statisticsType == 2'>\n" +
+                "inner join company_user t3 on t1.company_user_id = t3.user_id\n" +
+                "inner join company_dept t4 on t4.dept_id = t3.dept_id\n" +
+            "</if>" +
+            "WHERE t1.create_time BETWEEN #{param.startDate} AND #{param.endDateQueryValue}\n" +
+            "<if test='param.companyId != null'>\n" +
+            "and t2.company_id = #{param.companyId}\n" +
+            "</if>" +
+            "<if test='param.statisticsType == 2'>\n" +
+                "GROUP BY t4.dept_id  " +
+            "</if>" +
+            "<if test='param.statisticsType == 1'>\n" +
+                "GROUP BY t1.company_id  " +
+            "</if>" +
+            "</script>"
+    })
+    List<StatisticsSummaryVO> getStatisticsSummaryList(@Param("param") StatisticsSummaryParam param);
 }

+ 28 - 0
fs-service/src/main/java/com/fs/course/param/StatisticsSummaryParam.java

@@ -0,0 +1,28 @@
+package com.fs.course.param;
+
+import lombok.Data;
+
+/**
+ * @author MixLiu
+ * @date 2025/11/10 下午2:26)
+ */
+@Data
+public class StatisticsSummaryParam {
+
+    /**
+     * 统计维度 1、按照公司 2、按照部门
+     */
+    private Integer statisticsType;
+
+    /**
+     * 公司id
+     */
+    private Long companyId;
+
+
+    private String startDate;
+
+    private String endDate;
+
+    private String endDateQueryValue;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseTrafficLogService.java

@@ -4,7 +4,9 @@ import java.util.List;
 import com.fs.course.domain.FsCourseTrafficLog;
 import com.fs.course.param.FsCourseTrafficLogParam;
 import com.fs.course.param.InternetTrafficParam;
+import com.fs.course.param.StatisticsSummaryParam;
 import com.fs.course.vo.FsCourseTrafficLogListVO;
+import com.fs.course.vo.StatisticsSummaryVO;
 
 /**
  * 短链课程流量记录Service接口
@@ -80,4 +82,17 @@ public interface IFsCourseTrafficLogService
      * 定时统计流量总数
      */
     void sumTrafficlog();
+
+    /**
+     * 流量统计汇总
+     * @param param
+     * @return
+     */
+    List<StatisticsSummaryVO> getStatisticsSummaryList(StatisticsSummaryParam param);
+    /**
+     * 获取流量统计列表(不分页)
+     * @param param
+     * @return
+     */
+    List<StatisticsSummaryVO> getStatisticsSummaryListNotPage(StatisticsSummaryParam param);
 }

+ 119 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java

@@ -1,23 +1,35 @@
 package com.fs.course.service.impl;
 
+import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
+import java.time.LocalDate;
 import java.util.*;
+import java.util.stream.Collectors;
 
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.PageDomain;
+import com.fs.common.core.page.TableSupport;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DictUtils;
 import com.fs.common.utils.date.DateUtil;
+import com.fs.common.utils.sql.SqlUtil;
 import com.fs.company.cache.ICompanyCacheService;
+import com.fs.company.mapper.CompanyDeptMapper;
 import com.fs.course.param.FsCourseTrafficLogParam;
 import com.fs.course.param.InternetTrafficParam;
+import com.fs.course.param.StatisticsSummaryParam;
 import com.fs.course.param.TrafficRecord;
+import com.fs.course.vo.DeptFullPathVO;
 import com.fs.course.vo.FsCourseTrafficLogListVO;
+import com.fs.course.vo.StatisticsSummaryVO;
 import com.fs.store.service.cache.IFsUserCourseCacheService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
+import com.github.pagehelper.PageHelper;
 import com.hc.openapi.tool.util.StringUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -47,6 +59,9 @@ public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
     @Autowired
     private IFsUserCourseCacheService fsUserCourseCacheService;
 
+    @Autowired
+    CompanyDeptMapper companyDeptMapper;
+
     /**
      * 查询短链课程流量记录
      *
@@ -402,4 +417,108 @@ public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
                 minutes % 60,
                 seconds % 60);
     }
+
+    /**
+     * 流量统计汇总
+     * @param param
+     * @return
+     */
+    public List<StatisticsSummaryVO> getStatisticsSummaryList(StatisticsSummaryParam param){
+
+        if(StringUtils.isBlank(param.getStartDate()) && StringUtils.isBlank(param.getEndDate())){
+            throw new CustomException("搜索必须要一个时间范围!");
+        }
+        param.setEndDateQueryValue(addOneDay(param.getEndDate()));
+        if(null == param.getStatisticsType()){
+            throw new CustomException("请选择统计类型");
+        }
+        Map<Long,DeptFullPathVO> deptMp = new HashMap<>();
+        boolean byDept = param.getStatisticsType().equals(2);
+        //按照部门方式汇总
+        if(byDept){
+            if(null == param.getCompanyId()){
+                throw new CustomException("请选择公司");
+            }
+            List<DeptFullPathVO> deptInfoList = companyDeptMapper.getDeptFullPathList(param.getCompanyId());
+            deptMp = deptInfoList.stream().collect(Collectors.toMap(DeptFullPathVO::getDeptId, item -> item));
+            if(CollectionUtils.isEmpty(deptInfoList)){
+                throw new CustomException("该公司下没有部门");
+            }
+        }
+        functionStartPage();
+        List<StatisticsSummaryVO> res =  fsCourseTrafficLogMapper.getStatisticsSummaryList(param);
+        //获取配置流量比例
+        SysConfig config = iSysConfigService.selectConfigByConfigKey("statis.config");
+        JSONObject jsonObject = JSONObject.parseObject(config.getConfigValue());
+        float trafficPrice = jsonObject.getFloatValue("trafficPrice");
+
+        for (StatisticsSummaryVO item : res) {
+            item.formatTraffic();
+            item.calculateAmount(new BigDecimal(trafficPrice));
+            if(byDept){
+                item.setDeptName(deptMp.get(item.getDeptId()).getFullPath());
+            }
+        }
+        return res;
+    }
+    public static String addOneDay(String dateStr) {
+        // 将字符串解析为LocalDate对象
+        LocalDate date = LocalDate.parse(dateStr);
+
+        // 加一天
+        LocalDate newDate = date.plusDays(1);
+
+        // 转换回字符串
+        return newDate.toString();
+    }
+    protected void functionStartPage()
+    {
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        Integer pageNum = pageDomain.getPageNum();
+        Integer pageSize = pageDomain.getPageSize();
+        if (com.fs.common.utils.StringUtils.isNotNull(pageNum) && com.fs.common.utils.StringUtils.isNotNull(pageSize))
+        {
+            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
+            Boolean reasonable = pageDomain.getReasonable();
+            PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
+        }
+    }
+
+    public List<StatisticsSummaryVO> getStatisticsSummaryListNotPage(StatisticsSummaryParam param){
+        if(StringUtils.isBlank(param.getStartDate()) && StringUtils.isBlank(param.getEndDate())){
+            throw new CustomException("必须要一个时间范围!");
+        }
+        param.setEndDateQueryValue(addOneDay(param.getEndDate()));
+        if(null == param.getStatisticsType()){
+            throw new CustomException("请选择统计类型");
+        }
+        Map<Long,DeptFullPathVO> deptMp = new HashMap<>();
+        boolean byDept = param.getStatisticsType().equals(2);
+        //按照部门方式汇总
+        if(byDept){
+            if(null == param.getCompanyId()){
+                throw new CustomException("请选择公司");
+            }
+            List<DeptFullPathVO> deptInfoList = companyDeptMapper.getDeptFullPathList(param.getCompanyId());
+            deptMp = deptInfoList.stream().collect(Collectors.toMap(DeptFullPathVO::getDeptId, item -> item));
+            if(CollectionUtils.isEmpty(deptInfoList)){
+                throw new CustomException("该公司下没有部门");
+            }
+        }
+
+        List<StatisticsSummaryVO> res =  fsCourseTrafficLogMapper.getStatisticsSummaryList(param);
+        //获取配置流量比例
+        SysConfig config = iSysConfigService.selectConfigByConfigKey("statis.config");
+        JSONObject jsonObject = JSONObject.parseObject(config.getConfigValue());
+        float trafficPrice = jsonObject.getFloatValue("trafficPrice");
+
+        for (StatisticsSummaryVO item : res) {
+            item.formatTraffic();
+            item.calculateAmount(new BigDecimal(trafficPrice));
+            if(byDept){
+                item.setDeptName(deptMp.get(item.getDeptId()).getFullPath());
+            }
+        }
+        return res;
+    }
 }

+ 17 - 0
fs-service/src/main/java/com/fs/course/vo/DeptFullPathVO.java

@@ -0,0 +1,17 @@
+package com.fs.course.vo;
+
+import lombok.Data;
+
+/**
+ * @author MixLiu
+ * @date 2025/11/11 上午10:44)
+ */
+@Data
+public class DeptFullPathVO {
+    //部门id
+    private Long deptId;
+    //部门名称
+    private String deptName;
+    //部门全路径
+    private String fullPath;
+}

+ 63 - 0
fs-service/src/main/java/com/fs/course/vo/StatisticsSummaryVO.java

@@ -0,0 +1,63 @@
+package com.fs.course.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * @author MixLiu
+ * @date 2025/11/10 下午3:47)
+ */
+@Data
+public class StatisticsSummaryVO {
+
+    private Long companyId;
+
+    @Excel(name = "公司名称")
+    private String companyName;
+
+    private Long deptId;
+
+    @Excel(name = "部门名称")
+    private String deptName;
+
+    @Excel(name = "统计日期范围")
+    private String dateRange;
+
+    // 原始字节数(不用于导出)
+    private Long totalInternetTraffic;
+
+    @Excel(name = "总流量 (GB)")
+    private String formattedTotalTraffic;
+
+    @Excel(name = "金额(元)")
+    private String totalAmount;
+
+    // 工具方法:根据 totalInternetTraffic 自动计算 formattedTotalTraffic
+    public void formatTraffic() {
+        if (this.totalInternetTraffic == null) {
+            this.formattedTotalTraffic = "0.0000 GB";
+        } else {
+            BigDecimal gb = new BigDecimal(this.totalInternetTraffic.doubleValue()).divide(new BigDecimal(1024 * 1024 * 1024)).setScale(4, BigDecimal.ROUND_HALF_UP);
+//            double gb = this.totalInternetTraffic.doubleValue() / (1024 * 1024 * 1024);
+            this.formattedTotalTraffic = String.format("%.4f GB", gb);
+        }
+    }
+
+    /**
+     * 根据 totalInternetTraffic 计算金额值
+     * @param proportion
+     */
+    public void calculateAmount(BigDecimal proportion){
+        if (this.totalInternetTraffic == null) {
+            this.totalAmount = "0.00(元)";
+        } else {
+            BigDecimal gb = new BigDecimal(this.totalInternetTraffic.doubleValue()).divide(new BigDecimal(1024 * 1024 * 1024)).setScale(4, BigDecimal.ROUND_HALF_UP);
+            BigDecimal bigDecimal = proportion.multiply(gb).setScale(2, BigDecimal.ROUND_HALF_UP);
+            this.totalAmount = String.format("%.2f(元)",bigDecimal);
+        }
+    }
+
+
+}

+ 11 - 11
fs-service/src/main/java/com/fs/hisStore/mapper/FsUserBillScrmMapper.java

@@ -11,15 +11,15 @@ import org.apache.ibatis.annotations.Select;
 
 /**
  * 用户账单Mapper接口
- * 
+ *
  * @author fs
  * @date 2022-04-03
  */
-public interface FsUserBillScrmMapper 
+public interface FsUserBillScrmMapper
 {
     /**
      * 查询用户账单
-     * 
+     *
      * @param id 用户账单ID
      * @return 用户账单
      */
@@ -27,7 +27,7 @@ public interface FsUserBillScrmMapper
 
     /**
      * 查询用户账单列表
-     * 
+     *
      * @param fsUserBill 用户账单
      * @return 用户账单集合
      */
@@ -35,7 +35,7 @@ public interface FsUserBillScrmMapper
 
     /**
      * 新增用户账单
-     * 
+     *
      * @param fsUserBill 用户账单
      * @return 结果
      */
@@ -43,7 +43,7 @@ public interface FsUserBillScrmMapper
 
     /**
      * 修改用户账单
-     * 
+     *
      * @param fsUserBill 用户账单
      * @return 结果
      */
@@ -51,7 +51,7 @@ public interface FsUserBillScrmMapper
 
     /**
      * 删除用户账单
-     * 
+     *
      * @param id 用户账单ID
      * @return 结果
      */
@@ -59,7 +59,7 @@ public interface FsUserBillScrmMapper
 
     /**
      * 批量删除用户账单
-     * 
+     *
      * @param ids 需要删除的数据ID
      * @return 结果
      */
@@ -77,7 +77,7 @@ public interface FsUserBillScrmMapper
             "<if test = 'maps.category != null and maps.category  != \"\"  '> " +
             "and category =#{maps.category} " +
             "</if>" +
-            " order by id desc "+
+            " order by bill_id desc "+
             "</script>"})
     List<FsUserBillScrm> selectFsUserBillListQuery(@Param("maps") FsUserBillQueryParam param);
     @Select({"<script> " +
@@ -95,11 +95,11 @@ public interface FsUserBillScrmMapper
             "<if test = 'maps.endTime != null and maps.endTime != \"\"   '> " +
             "and date_format(b.create_time,'%y%m%d') &lt;= date_format(#{maps.endTime},'%y%m%d') " +
             "</if>" +
-            " order by b.id desc "+
+            " order by b.bill_id desc "+
             "</script>"})
     List<FsUserBillVO> selectFsUserBillListVO(@Param("maps")FsUserBillParam fsUserBill);
     @Select({"<script> " +
-            "select ifnull(sum(number),0) from fs_user_bill " +
+            "select ifnull(sum(money),0) from fs_user_bill " +
             "where user_id=#{userId} " +
             "<if test = 'cate != null  and cate != \"\"  '> " +
             "and category=#{cate} " +

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/mapper/FsUserScrmMapper.java

@@ -158,7 +158,7 @@ public interface FsUserScrmMapper
             "</script>"})
     List<FsCompanyUserListQueryVO> selectFsCompanyUserListQuery(@Param("maps") FsUserScrm fsUser);
     @Select({"<script> " +
-            "select u.user_id,u.phone,u.avatar,u.nickname,ifnull((select sum(b.number) from fs_user_bill b where b.category='brokerage_price' and b.bill_type=1  and b.tui_user_id=u.user_id and b.user_id=#{userId} ),0)-ifnull((select sum(b.number) from fs_user_bill b where b.category='brokerage_price' and b.bill_type=0 and b.tui_user_id=u.user_id and b.user_id=#{userId}  ),0) as tui_money ,u.create_time from fs_user u " +
+            "select u.user_id,u.phone,u.avatar,u.nickname,ifnull((select sum(b.money) from fs_user_bill b where b.category='brokerage_price' and b.bill_type=1  and b.tui_user_id=u.user_id and b.user_id=#{userId} ),0)-ifnull((select sum(b.money) from fs_user_bill b where b.category='brokerage_price' and b.bill_type=0 and b.tui_user_id=u.user_id and b.user_id=#{userId}  ),0) as tui_money ,u.create_time from fs_user u " +
             "where u.spread_user_id=#{userId} " +
             "order by tui_money desc " +
             "</script>"})

+ 19 - 1
fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java

@@ -213,10 +213,11 @@ public interface QwUserMapper extends BaseMapper<QwUser>
             "            <if test=\"qwUserId != null  and qwUserId != ''\"> and qu.qw_user_id = #{qwUserId}</if>\n" +
             "            <if test=\"companyId != null \"> and qu.company_id = #{companyId}</if>\n" +
             "            <if test=\"nickName != null  and nickName != ''\"> and cu.nick_name like concat('%', #{nickName}, '%') </if>\n" +
-            "            <if test=\"qwUserName != null  and qwUserName != ''\"> and qu.qw_user_uame like concat( #{qwUserName}, '%') </if>\n" +
+            "            <if test=\"qwUserName != null  and qwUserName != ''\"> and qu.qw_user_name like concat('%', #{qwUserName}, '%') </if>\n" +
             "            <if test=\"companyUserId != null \"> and qu.company_user_id = #{companyUserId}</if>\n" +
             "            <if test=\"corpId != null \"> and qu.corp_id = #{corpId}</if>\n" +
             "            <if test=\"deptId != null \"> and qd.dept_id = #{deptId}</if>\n" +
+            "            <if test=\"deptName != null \"> and qd.dept_name like concat('%', #{deptName}, '%') </if>\n" +
             "            <if test=\"status != null \"> and qu.status = #{status}</if>\n" +
             "            <if test=\"type != null and sendType !=null and type==2 and (sendType==2 or sendType==4 or sendType==11) \"> and qu.app_key IS NOT NULL  </if>\n" +
             "</script>"})
@@ -436,6 +437,23 @@ public interface QwUserMapper extends BaseMapper<QwUser>
             "</script>")
     List<Long> selectQwUserListByCuDeptIdList(@Param("map") QwSop qwSop);
 
+    @Select("<script>" +
+            "select qu.id,qu.qw_user_id,qu.qw_user_name,qu.department,qu.corp_id,qu.company_id,qu.company_user_id, " +
+            "cu.nick_name, cu.user_name, qd.dept_name as departmentName from qw_user qu " +
+            "LEFT JOIN company_user cu ON cu.user_id=qu.company_user_id " +
+            "RIGHT JOIN qw_dept qd on qu.department=qd.dept_id and qd.corp_id=qu.corp_id " +
+            "where qu.is_del=0 and qu.company_user_id is not null "+
+            "            <if test=\"map.corpId != null \"> and qu.corp_id = #{map.corpId}</if>\n" +
+            "            <if test=\"map.companyId != null \"> and qu.company_id = #{map.companyId}</if>\n" +
+            "            <if test=\"map.deptIds != null and !map.deptIds.isEmpty() \">" +
+            "               AND qu.department IN " +
+            "                   <foreach collection='map.deptIds' item='item' open='(' separator=',' close=')'> " +
+            "                       #{item} " +
+            "                   </foreach> " +
+            "            </if>" +
+            "</script>")
+    List<QwUserVO> selectQwUserByDeptList(@Param("map") QwUserByDeptParam deptParam);
+
     /**
      * 根据外部联系人的fsUserId查询关联的企微员工信息
      * @param fsUserId 会员id

+ 18 - 0
fs-service/src/main/java/com/fs/qw/param/QwUserByDeptParam.java

@@ -0,0 +1,18 @@
+package com.fs.qw.param;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class QwUserByDeptParam {
+
+    private List<Long> deptIds;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    private String corpId;
+}

+ 1 - 0
fs-service/src/main/java/com/fs/qw/param/QwUserListParam.java

@@ -48,6 +48,7 @@ public class QwUserListParam {
     private String nickName;
 
     private String userName;
+    private String deptName;
 
     private Integer type;
 

+ 1 - 0
fs-service/src/main/java/com/fs/qw/service/IQwUserService.java

@@ -146,6 +146,7 @@ public interface IQwUserService
     QwUser getByQwUserIdAndCorId(String qwUserId, String corpId);
 
     R loginQwIpad(QwLoginHookParam loginParam);
+    List<QwUserVO> getQwUserByDept(QwUserByDeptParam deptParam);
 
     R qrCodeStatus(QwLoginHookParam loginParam);
 

+ 6 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -1244,6 +1244,12 @@ public class QwUserServiceImpl implements IQwUserService
         redisCache.setCacheObject("qrCodeUid:qwUserId:"+loginParam.getQwUserId(),data.getUuid(),10, TimeUnit.MINUTES);
         return R.ok().put("qrCode",qrData.getQrcode()).put("qrCode64",qrData.getQrcodeData());
     }
+
+    @Override
+    public List<QwUserVO> getQwUserByDept(QwUserByDeptParam deptParam) {
+        return qwUserMapper.selectQwUserByDeptList(deptParam);
+    }
+
     void updateIpadStatus(Long id ,Integer status){
         QwUser u = new QwUser();
         u.setId(id);

+ 40 - 0
fs-service/src/main/java/com/fs/statis/dto/ComprehensiveStatisticsDTO.java

@@ -0,0 +1,40 @@
+package com.fs.statis.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/11/11 下午1:29
+ */
+@Data
+public class ComprehensiveStatisticsDTO {
+
+    private Long companyId;
+
+    private String companyName;
+
+    private Long companyUserId;
+
+    private String companyUserName;
+
+    private Integer deptId;
+
+    private String deptName;
+
+    private Integer sendCount;
+
+    private Integer logType;
+
+    private Integer answerNum;
+
+    private Integer redPacketNum;
+
+    private BigDecimal redPacketAmount;
+
+    private Date statisticsTime;
+
+}

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

@@ -36,5 +36,22 @@ public class ComprehensiveStatisticsParam {
      * id 在不同的维度下,id代表的意义不同
      */
     private Long id;
+
+    private Long deptId;
+
+    private Long userId;
+
+    private Long videoId;
+
+    private Long courseId;
+
+    private Long projectId;
+
+    private String companyName;
+
+    private String deptName;
+
+    private Long logType;
+
 }
 

+ 69 - 0
fs-service/src/main/java/com/fs/utils/FileCacheService.java

@@ -0,0 +1,69 @@
+package com.fs.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+@Slf4j
+@Service
+public class FileCacheService {
+
+    @Value("${getTuiImg:''}")
+    private String getTuiImg;
+
+    @Value("${getTuiFont:''}")
+    private String getTuiFont;
+
+    @Value("${tuiImgCache:0}")
+    private String tuiImgCache;
+
+    private byte[] templateBytes;
+    private byte[] fontBytes;
+    @PostConstruct
+    public void init() {
+        if(StringUtils.equals("1", tuiImgCache)) {
+            try {
+                // 检查配置是否为空
+                if (StringUtils.isNotBlank(getTuiImg) && Files.exists(Paths.get(getTuiImg))) {
+                    this.templateBytes = Files.readAllBytes(Paths.get(getTuiImg));
+                } else {
+                    log.warn("模板图片路径为空或文件不存在: {}", getTuiImg);
+                }
+
+                if (StringUtils.isNotBlank(getTuiFont) && Files.exists(Paths.get(getTuiFont))) {
+                    this.fontBytes = Files.readAllBytes(Paths.get(getTuiFont));
+                } else {
+                    log.warn("字体文件路径为空或文件不存在: {}", getTuiFont);
+                }
+            } catch (Exception e) {
+                log.error("文件缓存初始化异常,应用将继续启动: {}", e.getMessage(), e);
+            }
+        }
+    }
+
+
+    /**
+     * 获取模板图片的 InputStream
+     */
+    public InputStream getTemplateStream() {
+        return new ByteArrayInputStream(templateBytes);
+    }
+
+    /**
+     * 获取字体文件的 InputStream
+     */
+    public InputStream getFontStream() {
+        return new ByteArrayInputStream(fontBytes);
+    }
+
+    // 可选:提供重新加载方法(如需热更新)
+    public void reload() throws IOException {
+        this.templateBytes = Files.readAllBytes(Paths.get(getTuiImg));
+        this.fontBytes = Files.readAllBytes(Paths.get(getTuiFont));
+    }
+}

+ 5 - 8
fs-service/src/main/resources/application-config-druid-cqtyt.yml

@@ -10,13 +10,8 @@ logging:
 wx:
   miniapp:
     configs:
-      - appid:
-        secret:
-        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
-        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
-        msgDataFormat: JSON
-      - appid:
-        secret:
+      - appid: wxb2d969ad2a2e71f3
+        secret: d1fe2a8939f41aa875b3fe68db6b216d
         token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
         aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
         msgDataFormat: JSON
@@ -96,5 +91,7 @@ ipad:
 wx_miniapp_temp:
   pay_order_temp_id: V
   inquiry_temp_id: 9
-
+getTuiImg: 'C:\Tools\ylrm_his_scrm\images\fx.jpg'
+getTuiFont: 'C:\Tools\ylrm_his_scrm\images\simsunb.ttf'
+tuiImgCache: '1'
 

+ 28 - 0
fs-service/src/main/resources/mapper/company/CompanyDeptMapper.xml

@@ -198,4 +198,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         )
         SELECT dept_id FROM cte ORDER BY level;
     </select>
+
+    <select id="getDeptFullPathList" parameterType="Long" resultType="com.fs.course.vo.DeptFullPathVO">
+        WITH RECURSIVE dept_path AS (
+            SELECT
+                dept_id,
+                parent_id,
+                dept_name,
+                company_id,
+                dept_name AS full_path
+            FROM company_dept
+            WHERE parent_id = 0
+              AND company_id = #{companyId}
+            UNION ALL
+            SELECT
+                c.dept_id,
+                c.parent_id,
+                c.dept_name,
+                c.company_id,
+                CONCAT(d.full_path, '_', c.dept_name) AS full_path
+            FROM company_dept c
+                     INNER JOIN dept_path d
+                                ON c.parent_id = d.dept_id
+            WHERE c.company_id =  #{companyId}
+        )
+        SELECT dept_id, dept_name, full_path
+        FROM dept_path
+        ORDER BY full_path
+    </select>
 </mapper>

+ 146 - 0
fs-service/src/main/resources/mapper/company/CourseWatchLogStatisticsMapper.xml

@@ -0,0 +1,146 @@
+<?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.company.mapper.CourseWatchLogStatisticsMapper">
+
+    <!-- 结果映射 -->
+    <resultMap id="BaseResultMap" type="CourseWatchLogStatistics">
+        <id column="id" property="id" />
+        <result column="company_user_id" property="companyUserId" />
+        <result column="company_id" property="companyId" />
+        <result column="dept_id" property="deptId" />
+        <result column="statistics_time" property="statisticsTime" />
+        <result column="course_id" property="courseId" />
+        <result column="video_id" property="videoId" />
+        <result column="project_id" property="projectId" />
+        <result column="log_type" property="logType" />
+        <result column="send_count" property="sendCount" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+    <!-- 插入记录 -->
+    <insert id="insert" parameterType="CourseWatchLogStatistics">
+        INSERT INTO fs_statistics(
+            id,
+            company_user_id,
+            company_id,
+            dept_id,
+            statistics_time,
+            course_id,
+            video_id,
+            project_id,
+            log_type,
+            answer_num,
+            red_packet_num,
+            red_packet_amount,
+            send_count,
+            create_time,
+            update_time
+        ) VALUES (
+            #{id},
+            #{companyUserId},
+            #{companyId},
+            #{deptId},
+            #{statisticsTime},
+            #{courseId},
+            #{videoId},
+            #{projectId},
+            #{logType},
+            #{answerNum},
+            #{redPacketNum},
+            #{redPacketAmount},
+            #{sendCount},
+            #{createTime},
+            #{updateTime}
+        )
+    </insert>
+
+    <!-- 根据主键更新 -->
+    <update id="updateByPrimaryKey" parameterType="CourseWatchLogStatistics">
+        UPDATE fs_statistics
+        <set>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="deptId != null">dept_id = #{deptId},</if>
+            <if test="statisticsTime != null">statistics_time = #{statisticsTime},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="projectId != null">project_id = #{projectId},</if>
+            <if test="logType != null">log_type = #{logType},</if>
+            <if test="answerNum != null">answer_num = #{answerNum},</if>
+            <if test="redPacketNum != null">red_packet_num = #{redPacketNum},</if>
+            <if test="redPacketAmount != null">red_packet_amount = #{redPacketAmount},</if>
+            <if test="sendCount != null">send_count = #{sendCount},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </set>
+        WHERE id = #{id}
+    </update>
+
+    <!-- 根据主键删除 -->
+    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
+        DELETE FROM fs_statistics WHERE id = #{id}
+    </delete>
+
+    <!-- 根据主键查询 -->
+    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
+        SELECT * FROM fs_statistics WHERE id = #{id}
+    </select>
+
+    <!-- 根据公司员工ID查询 -->
+    <select id="selectByCompanyUserId" parameterType="java.lang.Long" resultMap="BaseResultMap">
+        SELECT * FROM fs_statistics WHERE company_user_id = #{companyUserId}
+    </select>
+
+    <!-- 根据统计时间范围查询 -->
+    <select id="selectByStatisticsTimeRange" resultMap="BaseResultMap">
+        SELECT * FROM fs_statistics
+        WHERE statistics_time BETWEEN #{startTime} AND #{endTime}
+    </select>
+
+    <resultMap id="getNeedUpDataMap" type="com.fs.company.domain.CourseWatchLogStatistics">
+        <id column="id" property="id" />
+        <result column="company_user_id" property="companyUserId" />
+        <result column="company_id" property="companyId" />
+        <result column="dept_id" property="deptId" />
+        <result column="project_id" property="projectId" />
+        <result column="course_id" property="courseId" />
+        <result column="video_id" property="videoId" />
+        <result column="statistics_time" property="statisticsTime" />
+    </resultMap>
+
+    <select id="getNeedUpdateData" resultMap="getNeedUpDataMap">
+        SELECT
+            fs.id,
+            fs.company_user_id,
+            fs.company_id,
+            fs.dept_id,
+            fs.project_id,
+            fs.course_id,
+            fs.video_id,
+            fs.statistics_time
+        FROM
+            fs_statistics AS fs
+        WHERE
+            fs.company_user_id IS NOT NULL
+          AND fs.video_id IS NOT NULL
+          AND fs.log_type = 2
+          and DATE_FORMAT(fs.statistics_time, '%Y-%m-%d' ) = DATE_FORMAT(#{statisticsTime}, '%Y-%m-%d')
+    </select>
+
+    <delete id="deleteByStatisticsTimeAndDeptId">
+        DELETE FROM fs_statistics
+        <where>
+            dept_id = #{deptId}
+            and DATE_FORMAT(statistics_time, '%Y-%m-%d' ) = DATE_FORMAT(#{statisticsTime}, '%Y-%m-%d')
+            <if test="userId != null">
+                and  company_user_id = #{userId}
+            </if>
+        </where>
+    </delete>
+
+
+
+</mapper>

+ 186 - 48
fs-service/src/main/resources/mapper/company/StatisticManageMapper.xml

@@ -4,34 +4,6 @@
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.company.mapper.StatisticManageMapper">
 
-    <resultMap id="CompanyDeptUserListMap" type="com.fs.company.domain.CompanyDeptUserInfo">
-        <id column="company_id" property="companyId" />
-        <result column="company_name" property="companyName"/>
-        <result column="dept_id" property="deptId"/>
-        <result column="dept_name" property="deptName"/>
-        <result column="user_id" property="userId"/>
-        <result column="user_name" property="userName"/>
-        <result column="nick_name" property="nickName"/>
-    </resultMap>
-
-    <select id="getCompanyAndDeptAndDeptUserList" resultMap="CompanyDeptUserListMap">
-        SELECT
-            ci.company_id,
-            ci.company_name,
-            cd.dept_id,
-            cd.dept_name,
-            cu.user_id,
-            cu.user_name,
-            cu.nick_name
-        FROM company AS ci
-        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 (
             SELECT
@@ -58,17 +30,6 @@
         SELECT * FROM t1,t2,t3,t4,t5
     </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, 
@@ -95,15 +56,6 @@
         </where>
     </select>
 
-    <select id="getStatisticNumByCompanyId" resultType="com.fs.company.domain.ComprehensiveDailyStats">
-        select <include refid="Base_Column_List"/> from user_daily_stats as uds
-        <where>
-            uds.company_id = #{companyIds[0]}
-            and statistics_time >=date_format(#{startTime},'%y%m%d')
-            and statistics_time &lt;= date_format(#{endTime},'%y%m%d')
-        </where>
-    </select>
-
     <!-- 1. 插入数据(全字段插入) -->
     <insert id="insert" parameterType="com.fs.company.domain.ComprehensiveDailyStats">
         INSERT INTO user_daily_stats (
@@ -238,4 +190,190 @@
         DELETE FROM user_daily_stats WHERE user_id = #{userId} AND statistics_time = date_format(#{statisticsTime},'%y%m%d')
     </delete>
 
+<!--   上面的方法需要逐个删除 -->
+
+    <resultMap id="CompanyDeptUserListMap" type="com.fs.company.domain.CompanyDeptUserInfo">
+        <id column="company_id" property="companyId" />
+        <result column="company_name" property="companyName"/>
+        <result column="dept_id" property="deptId"/>
+        <result column="dept_name" property="deptName"/>
+        <result column="parent_id" property="parentId"/>
+        <result column="user_id" property="userId"/>
+        <result column="user_name" property="userName"/>
+        <result column="nick_name" property="nickName"/>
+    </resultMap>
+    
+    <select id="getCompanyAndDeptAndDeptUserList" resultMap="CompanyDeptUserListMap">
+        SELECT
+            ci.company_id,
+            ci.company_name,
+            cd.dept_id,
+            cd.dept_name,
+            cd.parent_id,
+            cu.user_id,
+            cu.user_name,
+            cu.nick_name
+        FROM company AS ci
+        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="getCompanyInfo" resultMap="CompanyDeptUserListMap">
+        SELECT
+            ci.company_id,
+            ci.company_name
+        FROM
+            company AS ci
+        WHERE
+            ci.is_del = 0
+    </select>
+
+    <select id="getSearchDeptInfo" resultMap="CompanyDeptUserListMap">
+        SELECT
+            ci.company_id,
+            ci.company_name,
+            cd.parent_id,
+            cd.dept_id,
+            cd.dept_name
+        FROM company AS ci
+        LEFT JOIN company_dept AS cd ON ci.company_id = cd.company_id AND cd.STATUS = 0
+        where ci.company_id = #{id}
+    </select>
+
+    <select id="getSearchUserInfo" resultMap="CompanyDeptUserListMap">
+        SELECT
+            ci.company_id,
+            ci.company_name,
+            cd.dept_id,
+            cd.dept_name,
+            cu.user_id,
+            cu.user_name,
+            cu.nick_name
+        FROM company AS ci
+                 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 and cu.dept_id = #{id}
+    </select>
+
+    <select id="getCourseWatchLogData" resultType="com.fs.company.domain.CourseWatchLogData">
+        SELECT
+            company_user_id,
+            video_id,
+            course_id,
+            project,
+            log_type,
+            create_time
+        FROM
+            fs_course_watch_log
+        WHERE
+            company_user_id = #{companyUserId}
+          and DATE_FORMAT(create_time, '%Y-%m-%d') = DATE_FORMAT(#{dateTime}, '%Y-%m-%d')
+    </select>
+
+    <select id="getAnswerAndRedPacketData" resultType="com.fs.company.domain.CourseWatchLogData">
+        WITH t1 as (
+            SELECT
+                count(DISTINCT fcal.watch_log_id) as answer_num
+            FROM
+                fs_course_answer_logs AS fcal
+            WHERE
+                fcal.company_user_id = #{companyUserId}
+              and fcal.video_id = #{videoId}
+              AND DATE_FORMAT( fcal.create_time, '%Y-%m-%d') = DATE_FORMAT(#{dateTime}, '%Y-%m-%d')
+        ),t2 as (
+            SELECT
+                fcrpl.log_id,
+                fcrpl.amount
+            FROM
+                fs_course_red_packet_log AS fcrpl
+            WHERE
+                fcrpl.company_user_id =  #{companyUserId}
+              AND fcrpl.video_id =  #{videoId}
+              AND DATE_FORMAT(fcrpl.create_time, '%Y-%m-%d') = DATE_FORMAT(#{dateTime}, '%Y-%m-%d')
+        ),
+             t3 AS ( SELECT count(t2.log_id)  as red_packet_num FROM t2 ),
+             t4 AS ( SELECT sum(t2.amount) as red_packet_amount FROM t2 )
+        SELECT #{companyUserId}, t1.* , t3.* , t4.* FROM t1, t3, t4
+    </select>
+
+    <select id="getStatisticDataByDeptId" resultType="com.fs.statis.dto.ComprehensiveStatisticsDTO">
+        SELECT
+            SUM(IFNULL(fs.send_count, 0)) as sendCount,
+            SUM(IFNULL(fs.log_type, 0)) as logType,
+            SUM(IFNULL(fs.answer_num, 0)) as answerNum,
+            SUM(IFNULL(fs.red_packet_num, 0)) as redPacketNum,
+            SUM(IFNULL(fs.red_packet_amount, 0)) as redPacketAmount,
+            fs.statistics_time
+        FROM
+        fs_statistics AS fs
+        WHERE
+          DATE_FORMAT(fs.statistics_time, '%Y-%m-%d' ) >= DATE_FORMAT(#{startTime}, '%Y-%m-%d' )
+          ANd DATE_FORMAT(fs.statistics_time, '%Y-%m-%d' ) &lt;= DATE_FORMAT(#{endTime}, '%Y-%m-%d' )
+        <if test="deptId != null">
+             and fs.dept_id = #{deptId}
+        </if>
+        <if test="userId != null">
+            and fs.company_user_id =#{userId}
+        </if>
+        <if test="videoId != null">
+            and fs.video_id = #{videoId}
+        </if>
+        <if test="projectId != null">
+            and fs.project_id= #{projectId}
+        </if>
+        <if test="logType != null">
+            and fs.log_type = #{logType}
+        </if>
+        <if test="courseId != null">
+            and fs.course_id = #{courseId}
+        </if>
+        GROUP BY fs.statistics_time
+        order by fs.statistics_time
+        <if test="dimension != null">
+            <if test="dimension == 2">
+                , fs.dept_id
+            </if>
+            <if test="dimension == 3">
+                , fs.company_user_id
+            </if>
+        </if>
+    </select>
+
+    <select id="getUserInfoByUserId" resultMap="CompanyDeptUserListMap">
+        select
+            c.company_id,
+            c.company_name,
+            cd.dept_id,
+            cd.dept_name,
+            cd.parent_id,
+            cu.user_id,
+            cu.user_name,
+            cu.nick_name
+        from company_user as cu
+        left join company_dept as cd on cu.dept_id = cd.dept_id AND cd.STATUS = 0
+        left join company as c on c.company_id = cd.company_id and c.is_del = 0
+        where cu.user_id = #{userId}
+    </select>
+
+    <select id="getUserInfoByDeptId" resultType="com.fs.company.domain.CompanyDeptUserInfo">
+        select
+            c.company_id,
+            c.company_name,
+            cd.dept_id,
+            cd.dept_name,
+            cd.parent_id,
+            cu.user_id,
+            cu.user_name,
+            cu.nick_name
+        from company_dept as cd
+        left join company as c on cd.company_id = c.company_id and c.is_del = 0
+        left join company_user as cu on cu.dept_id = cd.dept_id AND cu.del_flag = 0
+        where cd.dept_id = #{deptId}
+    </select>
+
+
 </mapper>

+ 31 - 1
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -52,7 +52,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectFsUserVo">
-        select user_id,qw_ext_id,sex,is_buy,`level`,course_ma_open_id,is_push,is_add_qw,source,login_device,is_individuation_push,store_open_id,password,jpush_id, is_vip,vip_start_date,vip_end_date,vip_level,vip_status,nick_name,integral_status, avatar, phone, integral,sign_num, status, tui_user_id, tui_time, tui_user_count, ma_open_id, mp_open_id, union_id, is_del, user_code, remark, create_time, update_time, last_ip, balance,is_weixin_auth,parent_id,qw_user_id,company_id,company_user_id from fs_user
+        select user_id,qw_ext_id,sex,is_buy,`level`,course_ma_open_id,is_push,is_add_qw,source,login_device,is_individuation_push,store_open_id,password,jpush_id, is_vip,vip_start_date,vip_end_date,vip_level,vip_status,nick_name,integral_status, avatar, phone, integral,sign_num, status, tui_user_id, tui_time, tui_user_count, ma_open_id, mp_open_id, union_id, is_del, user_code, remark, create_time, update_time, last_ip, balance,is_weixin_auth,parent_id,qw_user_id,company_id,company_user_id,is_promoter,now_money,brokerage_price,spread_user_id, spread_time,pay_count, spread_count,user_type from fs_user
     </sql>
 
     <select id="selectFsUserList" parameterType="FsUser" resultMap="FsUserResult">
@@ -75,6 +75,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userCode != null  and userCode != ''"> and user_code = #{userCode}</if>
             <if test="lastIp != null  and lastIp != ''"> and last_ip = #{lastIp}</if>
             <if test="balance != null "> and balance = #{balance}</if>
+            <if test="level != null ">and level = #{level}</if>
+            <if test="spreadUserId != null ">and spread_user_id = #{spreadUserId}</if>
+            <if test="spreadTime != null ">and spread_time = #{spreadTime}</if>
+            <if test="userType != null  and userType != ''">and user_type = #{userType}</if>
+            <if test="isPromoter != null ">and is_promoter = #{isPromoter}</if>
+            <if test="payCount != null ">and pay_count = #{payCount}</if>
+            <if test="spreadCount != null ">and spread_count = #{spreadCount}</if>
+            <if test="addres != null  and addres != ''">and addres = #{addres}</if>
+            <if test="companyId != null ">and company_id = #{companyId}</if>
+            <if test="companyUserId != null ">and company_user_id = #{companyUserId}</if>
+            <if test="registerDate != null ">and DATE_FORMAT(register_date,'%Y-%m-%d') =
+                DATE_FORMAT(#{registerDate},'%Y-%m-%d')
+            </if>
+            <if test="registerCode != null   and registerCode != '' ">and register_code = #{registerCode}</if>
+            <if test="source != null  and source != '' ">and source = #{source}</if>
+            <if test="isShow != null  ">and is_show = #{isShow}</if>
+            <!--<if test="qwRepeat != null  ">and qw_repeat = #{qwRepeat}</if>
+            <if test="userRepeat != null  ">and user_repeat = #{userRepeat}</if>-->
+            <if test="payOrder != null  ">and pay_order = #{payOrder}</if>
         </where>
     </select>
 
@@ -559,6 +578,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyId != null">company_id,</if>
             <if test="companyUserId != null">company_user_id,</if>
             <if test="level != null">`level`,</if>
+            <if test="spreadUserId != null">spread_user_id,</if>
+            <if test="spreadTime != null">spread_time,</if>
+            <if test="isPromoter != null">is_promoter,</if>
+            <if test="payCount != null">pay_count,</if>
+            <if test="spreadCount != null">spread_count,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="nickName != null">#{nickName},</if>
@@ -603,6 +627,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyId != null">#{companyId},</if>
             <if test="companyUserId != null">#{companyUserId},</if>
             <if test="level != null">#{level},</if>
+            <if test="spreadUserId != null">#{spreadUserId},</if>
+            <if test="spreadTime != null">#{spreadTime},</if>
+            <if test="isPromoter != null">#{isPromoter},</if>
+            <if test="payCount != null">#{payCount},</if>
+            <if test="spreadCount != null">#{spreadCount},</if>
          </trim>
     </insert>
 
@@ -650,6 +679,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="courseMaOpenId != null">course_ma_open_id = #{courseMaOpenId},</if>
             <if test="parentId != null">parent_id = #{parentId},</if>
             <if test="qwExtId != null">qw_ext_id = #{qwExtId},</if>
+            <if test="isPromoter != null">is_promoter = #{isPromoter},</if>
             <if test="companyId != null">company_id = #{companyId},</if>
             <if test="orderCount != null">order_count = #{orderCount},</if>
             <if test="companyUserId != null">company_user_id = #{companyUserId},</if>

+ 272 - 8
fs-user-app/src/main/java/com/fs/app/controller/store/UserScrmController.java

@@ -1,9 +1,11 @@
 package com.fs.app.controller.store;
 
 
+import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.hutool.core.img.ImgUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.extra.qrcode.QrCodeUtil;
+import cn.hutool.extra.servlet.ServletUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.app.annotation.Login;
 import com.fs.app.controller.AppBaseController;
@@ -13,18 +15,21 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.common.exception.CustomException;
 import com.fs.common.param.BaseQueryParam;
+import com.fs.core.config.WxMaConfiguration;
 import com.fs.course.param.newfs.FsUserCourseBeMemberParam;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
-import com.fs.hisStore.domain.FsUserScrm;
 import com.fs.hisStore.domain.FsUserBillScrm;
 import com.fs.hisStore.domain.FsUserPromoterApplyScrm;
+import com.fs.hisStore.domain.FsUserScrm;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
 import com.fs.hisStore.vo.FsStoreOrderTuiVO;
 import com.fs.hisStore.vo.FsStoreProductRelationQueryVO;
 import com.fs.hisStore.vo.FsUserExtractVO;
 import com.fs.hisStore.vo.FsUserTuiVO;
+import com.fs.utils.FileCacheService;
+import com.fs.wx.miniapp.config.WxMaProperties;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
@@ -36,14 +41,24 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.FileImageOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
 import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.math.BigDecimal;
+import java.util.Iterator;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 
 
 @Api("个人中心")
@@ -71,7 +86,10 @@ public class UserScrmController extends AppBaseController {
     private IFsUserCourseVideoService courseVideoService;
     @Autowired
     private IFsUserScrmService fsUserService;
-
+    @Autowired
+    private FileCacheService fileCacheService;
+    @Autowired
+    private WxMaProperties maProperties;
     /**
      * 获取用户信息
      * @param request
@@ -113,8 +131,8 @@ public class UserScrmController extends AppBaseController {
 
     @Login
     @ApiOperation("获取推荐海报")
-    @GetMapping("/getTuiImg")
-    public R getTuiImg(HttpServletRequest request){
+    @GetMapping("/getTuiImgOld")
+    public R getTuiImgOld(HttpServletRequest request){
         try {
             FsUserScrm user=userService.selectFsUserById(Long.parseLong(getUserId()));
             if(StringUtils.isEmpty(user.getUserCode())){
@@ -208,6 +226,251 @@ public class UserScrmController extends AppBaseController {
         }
     }
 
+    @Login
+    @ApiOperation("获取推荐海报")
+    @GetMapping("/getTuiImg")
+    public R getTuiImg(HttpServletRequest request){
+        log.info("获取推荐海报 headers: {}", ServletUtil.getHeaderMap(request));
+
+        try {
+            String userId = getUserId();
+            log.info("获取用户ID: {}", userId);
+            if (StringUtils.isEmpty(userId)) {
+                log.error("用户ID为空");
+                return R.error("用户ID不能为空");
+            }
+            // 检查是否已存在海报,避免重复生成
+            String resultUrl = "profile/tui/tui-" + userId + ".jpg";
+            File existingPoster = new File(fsConfig.getTuiImgPath() + "/tui-" + userId + ".jpg");
+            if (existingPoster.exists() && existingPoster.lastModified() > System.currentTimeMillis() - 24 * 60 * 60 * 1000) {
+                log.info("使用已存在的海报: {}", resultUrl);
+                return R.ok().put("url", resultUrl);
+            }
+            FsUserScrm user = userService.selectFsUserById(Long.parseLong(userId));
+            if (user == null) {
+                log.error("未找到用户信息,用户ID: {}", userId);
+                return R.error("用户信息不存在");
+            }
+            log.info("查询到用户信息: 用户ID={}, 昵称={}", userId, user.getNickName());
+
+            // 确保用户有邀请码
+            if(StringUtils.isEmpty(user.getUserCode())){
+                log.info("用户邀请码为空,开始生成新的邀请码");
+                FsUserScrm userMap = new FsUserScrm();
+                userMap.setUserId(user.getUserId());
+                userMap.setUserCode(OrderUtils.genUserCode());
+                userService.updateFsUser(userMap);
+                user.setUserCode(userMap.getUserCode());
+                log.info("成功生成并更新用户邀请码: {}", userMap.getUserCode());
+            }
+            return generatePosterOptimized(user, userId);
+        } catch (NumberFormatException e) {
+            log.error("用户ID格式错误: {}", e.getMessage(), e);
+            return R.error("用户ID格式错误");
+        } catch (Exception e){
+            log.error("获取推荐海报失败: {}", e.getMessage(), e);
+            return R.error("操作异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 优化的海报生成方法 - 在内存中完成所有图片操作
+     */
+    private R generatePosterOptimized(FsUserScrm user, String userId) {
+        log.info("开始生成用户海报,用户ID: {}", userId);
+
+        try {
+            // 确保输出目录存在
+            String outputDir = fsConfig.getTuiImgPath();
+            File outputDirFile = new File(outputDir);
+            if (!outputDirFile.exists()) {
+                outputDirFile.mkdirs();
+            }
+            // 并行加载资源
+            CompletableFuture<BufferedImage> templateFuture = CompletableFuture.supplyAsync(() -> {
+                try {
+                    log.info("开始加载海报模板图片");
+                    try (InputStream templateStream = fileCacheService.getTemplateStream()) {
+                        BufferedImage image = ImageIO.read(templateStream);
+                        log.info("海报模板图片加载成功");
+                        return image;
+                    }
+                } catch (Exception e) {
+                    log.error("加载模板图片失败: {}", e.getMessage(), e);
+                    throw new RuntimeException("加载模板图片失败", e);
+                }
+            });
+            CompletableFuture<Font> fontFuture = CompletableFuture.supplyAsync(() -> {
+                try {
+                    log.info("开始加载字体文件");
+                    try (InputStream fontStream = fileCacheService.getFontStream()) {
+                        Font font = Font.createFont(Font.TRUETYPE_FONT, fontStream);
+                        font = font.deriveFont(Font.PLAIN, 50f);
+                        log.info("字体文件加载成功");
+                        return font;
+                    }
+                } catch (Exception e) {
+                    log.error("字体加载失败,使用默认字体: {}", e.getMessage());
+                    return new Font(Font.SANS_SERIF, Font.PLAIN, 50);
+                }
+            });
+            CompletableFuture<BufferedImage> qrFuture = CompletableFuture.supplyAsync(() -> {
+                try {
+                    // 改为小程序码生成
+                    WxMaService wxService = WxMaConfiguration.getMaService(maProperties.getConfigs().get(0).getAppid());
+                    byte[] qrCodeBytes = wxService.getQrcodeService().createWxaCodeUnlimitBytes(
+                            user.getUserCode(),           // scene - 分销用户码
+                            "pages_shopping/promotion/distribution",   // page - 目标页面(需要你确认)
+                            false,                        // checkPath - 检查页面是否存在
+                            "release",                   // envVersion - 版本(release/trial/develop)
+                            300,                         // width - 宽度
+                            true,                        // autoColor - 自动配置线条颜色
+                            null,                        // lineColor - 线条颜色(null使用默认)
+                            false                        // isHyaline - 是否需要透明底色
+                    );
+
+                    return ImageIO.read(new ByteArrayInputStream(qrCodeBytes));
+
+                } catch (Exception e) {
+                    log.error("生成二维码失败: {}", e.getMessage(), e);
+                    throw new RuntimeException("生成二维码失败", e);
+                }
+            });
+            // 等待所有资源加载完成
+            BufferedImage templateImage = templateFuture.get(10, TimeUnit.SECONDS);
+            Font font = fontFuture.get(10, TimeUnit.SECONDS);
+            BufferedImage qrImage = qrFuture.get(10, TimeUnit.SECONDS);
+            if (templateImage == null) {
+                log.error("模板图片加载失败");
+                return R.error("模板图片加载失败");
+            }
+            // 在内存中完成所有图片合成操作
+            BufferedImage finalImage = createFinalPoster(templateImage, font, qrImage, user);
+            // 只进行一次文件写入
+            String outputPath = outputDir + "/tui-" + userId + ".jpg";
+            File outputFile = new File(outputPath);
+
+            // 使用高质量的JPEG编码
+            writeHighQualityJPEG(finalImage, outputFile);
+
+            String resultUrl = "profile/tui/tui-" + userId + ".jpg";
+            log.info("海报生成完成,返回URL: {}", resultUrl);
+            return R.ok().put("url", resultUrl);
+        } catch (Exception e) {
+            log.error("生成海报过程出现异常: {}", e.getMessage(), e);
+            return R.error("生成海报失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 在内存中完成所有图片合成操作
+     */
+    private BufferedImage createFinalPoster(BufferedImage templateImage, Font font,
+                                            BufferedImage qrImage, FsUserScrm user) {
+        log.info("开始在内存中合成最终海报");
+
+        // 创建最终图片
+        BufferedImage finalImage = new BufferedImage(
+                templateImage.getWidth(),
+                templateImage.getHeight(),
+                BufferedImage.TYPE_INT_RGB
+        );
+
+        Graphics2D g2d = finalImage.createGraphics();
+        try {
+            // 设置高质量渲染
+            setupHighQualityRendering(g2d);
+
+            // 绘制模板图片
+            g2d.drawImage(templateImage, 0, 0, null);
+
+            // 绘制文本
+            drawUserText(g2d, font, user, templateImage.getWidth());
+
+            // 绘制二维码
+            drawQRCode(g2d, qrImage, templateImage);
+
+            log.info("海报合成完成");
+            return finalImage;
+
+        } finally {
+            g2d.dispose();
+        }
+    }
+
+    /**
+     * 设置高质量渲染参数
+     */
+    private void setupHighQualityRendering(Graphics2D g2d) {
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+    }
+
+    /**
+     * 绘制用户文本信息
+     */
+    private void drawUserText(Graphics2D g2d, Font font, FsUserScrm user, int imageWidth) {
+        g2d.setFont(font);
+        g2d.setColor(Color.BLACK);
+
+        String nickname = StringUtils.isEmpty(user.getNickName()) ? "用户" : user.getNickName();
+        FontMetrics fm = g2d.getFontMetrics();
+
+        // 绘制邀请文本
+        String nicknameText = nickname + "邀您加入";
+        int textWidth = fm.stringWidth(nicknameText);
+        int x = (imageWidth - textWidth) / 2;
+        int y = 900;
+        g2d.drawString(nicknameText, x, y);
+
+        // 绘制邀请码
+        String inviteText = "邀请码:" + user.getUserCode();
+        int inviteTextWidth = fm.stringWidth(inviteText);
+        int inviteX = (imageWidth - inviteTextWidth) / 2;
+        int inviteY = 1000;
+        g2d.drawString(inviteText, inviteX, inviteY);
+
+        log.info("用户文本绘制完成");
+    }
+
+    /**
+     * 绘制二维码
+     */
+    private void drawQRCode(Graphics2D g2d, BufferedImage qrImage, BufferedImage templateImage) {
+        int qrX =  100;
+        int qrY = templateImage.getHeight()-qrImage.getHeight()-100;
+        g2d.drawImage(qrImage, qrX, qrY, null);
+        log.info("二维码绘制完成");
+    }
+    /**
+     * 高质量JPEG图片写入
+     */
+    private void writeHighQualityJPEG(BufferedImage image, File outputFile) throws IOException {
+        Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
+        if (!writers.hasNext()) {
+            throw new IllegalStateException("No JPEG writers found");
+        }
+
+        ImageWriter writer = writers.next();
+        ImageWriteParam param = writer.getDefaultWriteParam();
+
+        if (param.canWriteCompressed()) {
+            param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+            param.setCompressionQuality(0.9f); // 高质量压缩
+        }
+
+        try (FileImageOutputStream output = new FileImageOutputStream(outputFile)) {
+            writer.setOutput(output);
+            writer.write(null, new IIOImage(image, null, null), param);
+        } finally {
+            writer.dispose();
+        }
+
+        log.info("高质量JPEG图片写入完成: {}", outputFile.getAbsolutePath());
+    }
+
     @Login
     @ApiOperation("获取我的推荐人")
     @GetMapping("/getMyTuiList")
@@ -218,16 +481,17 @@ public class UserScrmController extends AppBaseController {
         return R.ok().put("data",listPageInfo);
     }
 
-    @Login
+//    @Login
     @ApiOperation("获取用户金额")
     @GetMapping("/getTuiMoney")
     public R getMemberTuiMoney(HttpServletRequest request){
         try {
-            FsUserScrm user=userService.selectFsUserById(Long.parseLong(getUserId()));
-            BigDecimal yesterdayMoney=userBillService.selectFsUserBillTotalByUserId(getUserId(),"brokerage_price");
+            String userId=getUserId();
+            FsUserScrm user=userService.selectFsUserById(Long.parseLong(userId));
+            BigDecimal yesterdayMoney=userBillService.selectFsUserBillTotalByUserId(userId,"brokerage_price");
             return R.ok().put("nowMoney",user.getNowMoney()).put("brokeragePrice",user.getBrokeragePrice()).put("yesterdayMoney",yesterdayMoney);
         } catch (Exception e){
-
+            log.error("获取用户金额异常",e);
             return R.error("操作异常");
         }
     }

BIN
fs-user-app/src/main/resources/fx-old.jpg


BIN
fs-user-app/src/main/resources/fx.jpg


+ 0 - 0
fs-user-app/src/main/resources/qr.jpg


BIN
fs-user-app/src/main/resources/simsunb.ttf