修改统计数据显示问题

This commit is contained in:
MerCry 2026-03-04 17:21:33 +08:00
parent 14425ebf4a
commit 911f8cb583
13 changed files with 365 additions and 96 deletions

View File

@ -12,9 +12,14 @@ import java.io.Serializable;
@Data @Data
@TableName("corp_department") @TableName("corp_department")
public class CorpDepartment implements Serializable { public class CorpDepartment implements Serializable {
private static final long serialVersionUID = 1L;
private Long id; private Long id;
private String corpId; private String corpId;
private Long parentid; private Long parentid;
private Long orderNo; private Long orderNo;
private String name; private String name;

View File

@ -0,0 +1,14 @@
package com.ruoyi.excel.wecom.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class CorpDepartmentDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String corpId;
private String name;
}

View File

@ -153,81 +153,69 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
/** /**
* 创建所有日期的流量看板数据包含当日统计- 多线程版本 * 创建所有日期的流量看板数据包含当日统计- 优化版
* 采用分批处理策略控制并发数量避免CPU持续满载
*/ */
public void createAllReportData() { public void createAllReportData() {
List<CorpInfo> corpInfos = corpInfoMapper.selectCorpInfoList(new CorpInfo()); List<CorpInfo> corpInfos = corpInfoMapper.selectCorpInfoList(new CorpInfo());
corpInfos.forEach(item-> { int batchSize = 10;
for (CorpInfo item : corpInfos) {
try { try {
String corpId = item.getCorpId(); String corpId = item.getCorpId();
List<Date> allDate = customerExportService.getAllDate(corpId); List<Date> allDate = customerExportService.getAllDate(corpId);
// 创建任务列表 for (int i = 0; i < allDate.size(); i += batchSize) {
int end = Math.min(i + batchSize, allDate.size());
List<Date> batchDates = allDate.subList(i, end);
List<CompletableFuture<Void>> futures = new ArrayList<>(); List<CompletableFuture<Void>> futures = new ArrayList<>();
for (Date date : batchDates) {
// 为每个日期创建异步任务
for (Date date : allDate) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try { createReportData(corpId, date);
createReportData(corpId,date);
} catch (Exception e) {
throw new RuntimeException("处理日期 " + date + " 的报表数据失败: " + e.getMessage(), e);
}
}, executorService); }, executorService);
futures.add(future); futures.add(future);
} }
// 等待所有任务完成
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
} catch (Exception e) {
throw new RuntimeException("多线程处理流量看板数据时发生错误: " + e.getMessage(), e);
} }
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("多线程处理流量看板数据时发生错误: " + e.getMessage(), e); throw new RuntimeException("多线程处理流量看板数据时发生错误: " + e.getMessage(), e);
} }
}); }
} }
/** /**
* 创建所有部门指标数据 - 多线程版本 * 创建所有部门指标数据 - 优化版
* 采用分批处理策略控制并发数量避免CPU持续满载
*/ */
public void createAllDepartmentReportData() { public void createAllDepartmentReportData() {
List<CorpInfo> corpInfos = corpInfoMapper.selectCorpInfoList(new CorpInfo()); List<CorpInfo> corpInfos = corpInfoMapper.selectCorpInfoList(new CorpInfo());
corpInfos.forEach(item-> { int batchSize = 10;
try {
// 为每个日期创建异步任务 for (CorpInfo item : corpInfos) {
String finalCorpId = item.getCorpId(); try {
List<Date> allDate = customerExportService.getAllDate(finalCorpId); String corpId = item.getCorpId();
List<Date> allDate = customerExportService.getAllDate(corpId);
for (int i = 0; i < allDate.size(); i += batchSize) {
int end = Math.min(i + batchSize, allDate.size());
List<Date> batchDates = allDate.subList(i, end);
// 创建任务列表
List<CompletableFuture<Void>> futures = new ArrayList<>(); List<CompletableFuture<Void>> futures = new ArrayList<>();
for (Date date : batchDates) {
for (Date date : allDate) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try { createDepartmentReportData(corpId, date);
createDepartmentReportData(finalCorpId,date);
} catch (Exception e) {
throw new RuntimeException("处理日期 " + date + " 的部门报表数据失败: " + e.getMessage(), e);
}
}, executorService); }, executorService);
futures.add(future); futures.add(future);
} }
// 等待所有任务完成
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
} catch (Exception e) {
throw new RuntimeException("多线程处理部门报表数据时发生错误: " + e.getMessage(), e);
} }
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("多线程处理部门报表数据时发生错误: " + e.getMessage(), e); throw new RuntimeException("多线程处理部门报表数据时发生错误: " + e.getMessage(), e);
} }
}); }
} }
/** /**
@ -389,7 +377,8 @@ import java.util.concurrent.atomic.AtomicInteger;
* @return 统计结果列表 * @return 统计结果列表
*/ */
/** /**
* 计算统计数据 - 使用分页查询+单次遍历方案 * 计算统计数据 - 优化版
* SQL层面过滤日期减少数据传输和Java层过滤开销
* @param date 目标日期 (格式: yyyy-MM-dd) * @param date 目标日期 (格式: yyyy-MM-dd)
* @return 统计结果列表 * @return 统计结果列表
*/ */
@ -397,23 +386,21 @@ import java.util.concurrent.atomic.AtomicInteger;
// 1. 初始化累加器 // 1. 初始化累加器
StatisticsAccumulator accumulator = new StatisticsAccumulator(); StatisticsAccumulator accumulator = new StatisticsAccumulator();
// 2. 分页查询并累加统计 // 2. 分页查询并累加统计SQL层面直接过滤日期
int pageSize = 1000; // 每页1000条 int pageSize = 1000;
int pageNum = 1; int pageNum = 1;
LambdaQueryWrapper<CustomerExportData> wrapper= new LambdaQueryWrapper<>(); LambdaQueryWrapper<CustomerExportData> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerExportData::getCorpId,corpId); wrapper.eq(CustomerExportData::getCorpId, corpId)
.eq(CustomerExportData::getAddDate, date);
while (true) { while (true) {
// 分页查询不在SQL层过滤在Java层过滤以保证准确性
Page<CustomerExportData> page = new Page<>(pageNum, pageSize); Page<CustomerExportData> page = new Page<>(pageNum, pageSize);
Page<CustomerExportData> pageData = customerExportDataMapper.selectPage(page, wrapper); Page<CustomerExportData> pageData = customerExportDataMapper.selectPage(page, wrapper);
// 处理当前页数据
for (CustomerExportData data : pageData.getRecords()) { for (CustomerExportData data : pageData.getRecords()) {
// 处理该条数据累加到所有相关组的统计中
processDataRecord(data, date, accumulator); processDataRecord(data, date, accumulator);
} }
// 检查是否还有下一页
if (!pageData.hasNext()) { if (!pageData.hasNext()) {
break; break;
} }
@ -426,7 +413,8 @@ import java.util.concurrent.atomic.AtomicInteger;
/** /**
* 计算部门维度的统计数据当日 * 计算部门维度的统计数据当日- 优化版
* SQL层面过滤日期减少数据传输和Java层过滤开销
* @param targetDate 目标日期 (格式: yyyy-MM-dd) * @param targetDate 目标日期 (格式: yyyy-MM-dd)
* @return 部门统计结果列表 * @return 部门统计结果列表
*/ */
@ -434,17 +422,17 @@ import java.util.concurrent.atomic.AtomicInteger;
// 1. 初始化部门累加器 // 1. 初始化部门累加器
DepartmentStatisticsAccumulator accumulator = new DepartmentStatisticsAccumulator(); DepartmentStatisticsAccumulator accumulator = new DepartmentStatisticsAccumulator();
// 2. 分页查询并累加统计 // 2. 分页查询并累加统计SQL层面直接过滤日期
int pageSize = 1000; int pageSize = 1000;
int pageNum = 1; int pageNum = 1;
LambdaQueryWrapper<CustomerExportData> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<CustomerExportData> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerExportData::getCorpId,corpId); wrapper.eq(CustomerExportData::getCorpId, corpId)
.eq(CustomerExportData::getAddDate, targetDate);
while (true) { while (true) {
// 分页查询
Page<CustomerExportData> page = new Page<>(pageNum, pageSize); Page<CustomerExportData> page = new Page<>(pageNum, pageSize);
Page<CustomerExportData> pageData = customerExportDataMapper.selectPage(page, wrapper); Page<CustomerExportData> pageData = customerExportDataMapper.selectPage(page, wrapper);
// 处理当前页数据
for (CustomerExportData data : pageData.getRecords()) { for (CustomerExportData data : pageData.getRecords()) {
// 获取部门路径 // 获取部门路径
String departmentPath = data.getAddUserDepartment(); String departmentPath = data.getAddUserDepartment();
@ -454,7 +442,6 @@ import java.util.concurrent.atomic.AtomicInteger;
// 先计算所有条件避免重复判断 // 先计算所有条件避免重复判断
boolean isQValueMatch = matchesQValue(data, targetDate); boolean isQValueMatch = matchesQValue(data, targetDate);
boolean isDateMatch = matchesDate(data, targetDate);
boolean isSourceMatch = matchesSource(data); boolean isSourceMatch = matchesSource(data);
boolean isTimelyOrder = isTimelyOrder(data); boolean isTimelyOrder = isTimelyOrder(data);
String customerAttr = data.getTagGroup6(); String customerAttr = data.getTagGroup6();
@ -474,7 +461,7 @@ import java.util.concurrent.atomic.AtomicInteger;
String currentPath = pathBuilder.toString(); String currentPath = pathBuilder.toString();
DepartmentStatisticsAccumulator.DepartmentStats stats = accumulator.getDepartmentStats(currentPath); DepartmentStatisticsAccumulator.DepartmentStats stats = accumulator.getDepartmentStats(currentPath);
// 累加统计条件已预先计算直接使用 // 累加统计SQL已过滤日期所有数据都是目标日期的
if (isQValueMatch) { if (isQValueMatch) {
stats.setTotalOrderCount(stats.getTotalOrderCount() + 1); stats.setTotalOrderCount(stats.getTotalOrderCount() + 1);
if (isTimelyOrder) { if (isTimelyOrder) {
@ -484,7 +471,6 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
} }
if (isDateMatch) {
if (isSourceMatch) { if (isSourceMatch) {
stats.setTotalAcceptCount(stats.getTotalAcceptCount() + 1); stats.setTotalAcceptCount(stats.getTotalAcceptCount() + 1);
if (isParent) { if (isParent) {
@ -504,8 +490,6 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
} }
}
// 检查是否还有下一页 // 检查是否还有下一页
if (!pageData.hasNext()) { if (!pageData.hasNext()) {
break; break;

View File

@ -2,15 +2,14 @@ package com.ruoyi.excel.wecom.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.excel.wecom.domain.CorpDepartment; import com.ruoyi.excel.wecom.domain.CorpDepartment;
import com.ruoyi.excel.wecom.domain.CorpUser; import com.ruoyi.excel.wecom.dto.CorpDepartmentDTO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 部门mapper
*/
@Mapper @Mapper
public interface CorpDepartmentMapper extends BaseMapper<CorpDepartment> { public interface CorpDepartmentMapper extends BaseMapper<CorpDepartment> {
List<CorpDepartment> selectCorpDepartmentList(@Param("dto") CorpDepartmentDTO dto);
} }

View File

@ -0,0 +1,10 @@
package com.ruoyi.excel.wecom.service;
import com.ruoyi.excel.wecom.vo.CorpDepartmentVO;
import java.util.List;
public interface ICorpDepartmentService {
List<CorpDepartmentVO> selectCorpDepartmentTree(String corpId);
}

View File

@ -0,0 +1,125 @@
package com.ruoyi.excel.wecom.service.impl;
import com.ruoyi.excel.wecom.domain.CorpDepartment;
import com.ruoyi.excel.wecom.dto.CorpDepartmentDTO;
import com.ruoyi.excel.wecom.mapper.CorpDepartmentMapper;
import com.ruoyi.excel.wecom.service.ICorpDepartmentService;
import com.ruoyi.excel.wecom.vo.CorpDepartmentVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class CorpDepartmentServiceImpl implements ICorpDepartmentService {
@Autowired
private CorpDepartmentMapper corpDepartmentMapper;
@Override
public List<CorpDepartmentVO> selectCorpDepartmentTree(String corpId) {
CorpDepartmentDTO query = new CorpDepartmentDTO();
query.setCorpId(corpId);
List<CorpDepartment> allDepartments = corpDepartmentMapper.selectCorpDepartmentList(query);
Map<Long, CorpDepartment> deptMap = new HashMap<>();
Map<Long, Long> parentMap = new HashMap<>();
for (CorpDepartment dept : allDepartments) {
deptMap.put(dept.getId(), dept);
parentMap.put(dept.getId(), dept.getParentid());
}
Map<Long, String> ancestorsMap = new HashMap<>();
for (CorpDepartment dept : allDepartments) {
String fullPath = buildDepartmentPath(dept.getId(), deptMap, parentMap);
ancestorsMap.put(dept.getId(), fullPath);
}
return buildTree(allDepartments, ancestorsMap);
}
private String buildDepartmentPath(Long deptId, Map<Long, CorpDepartment> deptMap, Map<Long, Long> parentMap) {
if (deptId == null || !deptMap.containsKey(deptId)) {
return "";
}
CorpDepartment dept = deptMap.get(deptId);
String currentName = dept.getName();
Long parentId = parentMap.get(deptId);
if (parentId == null || parentId == 0L || !parentMap.containsKey(parentId)) {
return currentName;
}
String parentPath = buildDepartmentPath(parentId, deptMap, parentMap);
if (parentPath.isEmpty()) {
return currentName;
} else {
return parentPath + "/" + currentName;
}
}
private List<CorpDepartmentVO> buildTree(List<CorpDepartment> departments, Map<Long, String> ancestorsMap) {
List<CorpDepartmentVO> result = new ArrayList<>();
List<Long> ids = departments.stream().map(CorpDepartment::getId).collect(Collectors.toList());
for (CorpDepartment dept : departments) {
Long parentId = dept.getParentid();
if (parentId == null || parentId == 0L || !ids.contains(parentId)) {
CorpDepartmentVO vo = convertToVO(dept, ancestorsMap.get(dept.getId()));
recursionFn(departments, vo, ancestorsMap);
result.add(vo);
}
}
if (result.isEmpty()) {
for (CorpDepartment dept : departments) {
result.add(convertToVO(dept, ancestorsMap.get(dept.getId())));
}
}
return result;
}
private CorpDepartmentVO convertToVO(CorpDepartment dept, String ancestors) {
CorpDepartmentVO vo = new CorpDepartmentVO();
vo.setId(dept.getId());
vo.setName(dept.getName());
vo.setAncestors(ancestors);
return vo;
}
private void recursionFn(List<CorpDepartment> list, CorpDepartmentVO parent, Map<Long, String> ancestorsMap) {
List<CorpDepartmentVO> childList = getChildList(list, parent, ancestorsMap);
parent.setChildren(childList);
for (CorpDepartmentVO child : childList) {
if (hasChild(list, child)) {
recursionFn(list, child, ancestorsMap);
}
}
}
private List<CorpDepartmentVO> getChildList(List<CorpDepartment> list, CorpDepartmentVO parent, Map<Long, String> ancestorsMap) {
List<CorpDepartmentVO> result = new ArrayList<>();
for (CorpDepartment dept : list) {
if (dept.getParentid() != null && dept.getParentid().longValue() == parent.getId().longValue()) {
CorpDepartmentVO vo = convertToVO(dept, ancestorsMap.get(dept.getId()));
result.add(vo);
}
}
return result;
}
private boolean hasChild(List<CorpDepartment> list, CorpDepartmentVO t) {
for (CorpDepartment dept : list) {
if (dept.getParentid() != null && dept.getParentid().longValue() == t.getId().longValue()) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,19 @@
package com.ruoyi.excel.wecom.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class CorpDepartmentVO implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private String ancestors;
private List<CorpDepartmentVO> children;
}

View File

@ -0,0 +1,27 @@
<?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.ruoyi.excel.wecom.mapper.CorpDepartmentMapper">
<resultMap id="CorpDepartmentResult" type="com.ruoyi.excel.wecom.domain.CorpDepartment">
<id property="id" column="id"/>
<result property="corpId" column="corp_id"/>
<result property="parentid" column="parentid"/>
<result property="orderNo" column="order_no"/>
<result property="name" column="name"/>
</resultMap>
<select id="selectCorpDepartmentList" parameterType="com.ruoyi.excel.wecom.dto.CorpDepartmentDTO" resultMap="CorpDepartmentResult">
SELECT id, corp_id, parentid, order_no, name
FROM corp_department
<where>
<if test="dto.corpId != null and dto.corpId != ''">
AND corp_id = #{dto.corpId}
</if>
<if test="dto.name != null and dto.name != ''">
AND name LIKE CONCAT('%', #{dto.name}, '%')
</if>
</where>
ORDER BY order_no ASC, id ASC
</select>
</mapper>

View File

@ -130,7 +130,7 @@
sum(ifnull(daily_total_accepted,0)+ifnull(manager_accepted,0)) as totalIns sum(ifnull(daily_total_accepted,0)+ifnull(manager_accepted,0)) as totalIns
from department_statistics_data from department_statistics_data
<where> <where>
corp_id = #{corpId} and person_flag = true corp_id = #{corpId}
<if test="startDate != null"> <if test="startDate != null">
AND stat_date &gt;= #{startDate} AND stat_date &gt;= #{startDate}
</if> </if>

View File

@ -0,0 +1,27 @@
package com.ruoyi.web.controller.wocom;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.CorpContextHolder;
import com.ruoyi.excel.wecom.service.ICorpDepartmentService;
import com.ruoyi.excel.wecom.vo.CorpDepartmentVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/wecom/department")
public class CorpDepartmentController {
@Autowired
private ICorpDepartmentService corpDepartmentService;
@GetMapping("/tree")
public AjaxResult getDepartmentTree() {
String corpId = CorpContextHolder.getCurrentCorpId();
List<CorpDepartmentVO> tree = corpDepartmentService.selectCorpDepartmentTree(corpId);
return AjaxResult.success(tree);
}
}

View File

@ -111,7 +111,6 @@ public class DepartmentStatisticsDataController extends BaseController {
Date[] dateRange = calculateDateRange(dataType, year, week, month, startDate, endDate); Date[] dateRange = calculateDateRange(dataType, year, week, month, startDate, endDate);
startDate = dateRange[0]; startDate = dateRange[0];
endDate = dateRange[1]; endDate = dateRange[1];
Map<String, BigDecimal> map = departmentStatisticsDataService.getSummary(corpId, startDate, endDate, departmentPath, dataType); Map<String, BigDecimal> map = departmentStatisticsDataService.getSummary(corpId, startDate, endDate, departmentPath, dataType);
if(map == null) { if(map == null) {
return success(new HashMap<>()); return success(new HashMap<>());

View File

@ -61,3 +61,11 @@ export function getDepartmentStatisticsSummary(query) {
params: query params: query
}) })
} }
// 获取部门树
export function getDepartmentTree() {
return request({
url: '/wecom/department/tree',
method: 'get'
})
}

View File

@ -1,5 +1,27 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-row :gutter="20">
<el-col :span="4">
<el-card shadow="never" class="department-tree-card">
<div slot="header" class="clearfix">
<span>部门列表</span>
</div>
<el-tree
:data="departmentTree"
:props="defaultProps"
node-key="id"
:expand-on-click-node="false"
:default-expand-all="false"
@node-click="handleNodeClick"
highlight-current
>
<span class="tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
</span>
</el-tree>
</el-card>
</el-col>
<el-col :span="20">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px"> <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="数据类型" prop="dataType"> <el-form-item label="数据类型" prop="dataType">
<el-select <el-select
@ -148,11 +170,13 @@
:limit.sync="queryParams.pageSize" :limit.sync="queryParams.pageSize"
@pagination="getList" @pagination="getList"
/> />
</el-col>
</el-row>
</div> </div>
</template> </template>
<script> <script>
import { listDepartmentStatistics, exportDepartmentStatistics, getDepartmentStatisticsSummary } from "@/api/wecom/departmentStatistics" import { listDepartmentStatistics, exportDepartmentStatistics, getDepartmentStatisticsSummary, getDepartmentTree } from "@/api/wecom/departmentStatistics"
export default { export default {
name: "DepartmentStatistics", name: "DepartmentStatistics",
@ -178,6 +202,13 @@ export default {
totalIns: 0, totalIns: 0,
penetrationRate: 0 penetrationRate: 0
}, },
//
departmentTree: [],
//
defaultProps: {
children: 'children',
label: 'name'
},
// //
queryParams: { queryParams: {
pageNum: 1, pageNum: 1,
@ -228,10 +259,21 @@ export default {
created() { created() {
this.initYearOptions() this.initYearOptions()
this.initWeekOptions() this.initWeekOptions()
this.getDepartmentTreeData()
this.getList() this.getList()
}, },
methods: { methods: {
// //
getDepartmentTreeData() {
getDepartmentTree().then(response => {
this.departmentTree = response.data || []
})
},
//
handleNodeClick(data) {
this.queryParams.departmentPath = data.ancestors
this.handleQuery()
},
initYearOptions() { initYearOptions() {
const currentYear = new Date().getFullYear() const currentYear = new Date().getFullYear()
const years = [] const years = []
@ -363,3 +405,13 @@ export default {
} }
} }
</script> </script>
<style scoped>
.department-tree-card {
height: calc(100vh - 180px);
overflow-y: auto;
}
.tree-node {
font-size: 14px;
}
</style>