优化待办分类统计

This commit is contained in:
yanglin 2024-07-04 09:23:16 +08:00
parent bfcf3f3402
commit 1f4540bad7
6 changed files with 103 additions and 69 deletions

View File

@ -99,8 +99,7 @@ public class GroupTemplateService {
public GroupTemplates collectTemplates(List<ValueNode<NodeWrapper>> nodes) {
Set<String> groupNodeCodes = nodes.stream()
.map(ValueNode::getValue)
.map(NodeWrapper::unwrap)
.map(MessageGroupNode::getCode)
.map(NodeWrapper::getNodeCode)
.collect(toSet());
return collectTemplates(groupNodeCodes);
}

View File

@ -42,4 +42,8 @@ public class NodeWrapper implements NodeValue {
return node;
}
public String getNodeCode() {
return node.getCode();
}
}

View File

@ -31,7 +31,7 @@ public class RootNodeWrapper {
public RootNodeWrapper(RootNode<NodeWrapper> root) {
this.root = root;
this.code2Node = root.getValueNodes().stream()
.collect(toMap(node -> node.getValue().unwrap().getCode(), Function.identity()));
.collect(toMap(node -> node.getValue().getNodeCode(), Function.identity()));
}
public Optional<ValueNode<NodeWrapper>> findValueNode(String groupNodeCode) {
@ -72,7 +72,7 @@ public class RootNodeWrapper {
//noinspection unchecked
ValueNode<NodeWrapper> valueNode = current.tryCast(ValueNode.class);
if (valueNode != null)
paths.add(valueNode.getValue().unwrap().getCode());
paths.add(valueNode.getValue().getNodeCode());
current = current.getParent();
}
Collections.reverse(paths);

View File

@ -0,0 +1,51 @@
package cn.axzo.msg.center.message.service.todo;
import cn.axzo.maokai.api.vo.response.tree.ValueNode;
import cn.axzo.msg.center.domain.entity.Todo;
import cn.axzo.msg.center.message.service.group.GroupTemplates;
import cn.axzo.msg.center.message.service.group.NodeWrapper;
import cn.axzo.msg.center.service.enums.TodoType;
import lombok.RequiredArgsConstructor;
import java.util.Collections;
import java.util.List;
/**
* @author yanglin
*/
@RequiredArgsConstructor
class LeafNodeStats {
// count, type, template_code
private final List<Todo> stats;
private final GroupTemplates groupTemplates;
int getCount(ValueNode<NodeWrapper> node, TodoType todoType) {
return getCount(Collections.singletonList(node), todoType);
}
int getCount(List<ValueNode<NodeWrapper>> nodes, TodoType todoType) {
int count = 0;
for (Todo stat : stats) {
if (stat.getType() != todoType)
continue;
// 只统计叶子节点的数据, 显示列表不关心中间节点
boolean leafNodeContainsTemplate = nodes.stream()
.map(ValueNode::getValue)
.filter(NodeWrapper::isLeaf)
.anyMatch(node -> groupTemplates
.groupContainsTemplate(node.getNodeCode(), stat.getTemplateCode()));
if (leafNodeContainsTemplate)
count += stat.getCount();
}
return count;
}
int getCount(TodoType todoType) {
return stats.stream()
.filter(s -> s.getType() == todoType)
.mapToInt(Todo::getCount)
.sum();
}
}

View File

@ -15,6 +15,7 @@ import cn.axzo.msg.center.domain.entity.TodoBusiness;
import cn.axzo.msg.center.domain.persistence.BaseEntityExt;
import cn.axzo.msg.center.message.domain.param.MessageGroupNodeStatisticParam;
import cn.axzo.msg.center.message.service.group.GroupTemplateService;
import cn.axzo.msg.center.message.service.group.GroupTemplates;
import cn.axzo.msg.center.message.service.group.NodeWrapper;
import cn.axzo.msg.center.message.service.impl.PendingMessageNewServiceImpl;
import cn.axzo.msg.center.message.service.todo.cache.NodeStatCache;
@ -39,6 +40,7 @@ import cn.axzo.msg.center.service.pending.response.PendingMessageStatisticRespon
import cn.axzo.msg.center.service.pending.response.PersonTodoToBeDoneStatResponse;
import cn.axzo.msg.center.utils.DateFormatUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -49,14 +51,12 @@ import org.springframework.stereotype.Component;
import javax.annotation.Nullable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Function;
import java.util.function.Supplier;
import static cn.axzo.msg.center.inside.notices.utils.Queries.query;
@ -81,7 +81,6 @@ public class TodoRangeQueryService {
private final AnalysisConfig analysisConfig;
private final GroupTemplateService groupTemplateService;
private final NodeStatCache nodeStatCache;
private final ForkJoinPool statExecutor = new ForkJoinPool(80);
private final TodoTerminalHelper todoTerminalHelper;
private final AnalysisHelper analysisHelper;
@ -144,8 +143,8 @@ public class TodoRangeQueryService {
.collect(toSet());
CopiedToMeParam copiedToMeParam = request.getCopiedToMeParam();
Ref<List<Long>> ouCollector = Ref.create();
OuInfo ouInfo = OuInfo.create(request.getOuId(), request.getAppTerminalType(), request.determineToDoType());
LambdaQueryWrapper<Todo> query = todoQuery(ouInfo, ouCollector)
OuInfo ouInfo = new OuInfo(request.getOuId(), request.getAppTerminalType());
LambdaQueryWrapper<Todo> query = todoQuery(query(Todo.class), ouInfo, ouCollector)
// 查询的待办类型: COPIED_TO_ME, EXECUTABLE
.eq(Todo::getType, request.determineToDoType())
.eq(Todo::getExecutorPersonId, request.getPersonId())
@ -256,40 +255,26 @@ public class TodoRangeQueryService {
private PendingMessageStatisticResponseV2 countStatGroupedImpl(MessageGroupNodeStatisticParam request) {
List<ValueNode<NodeWrapper>> nodes = determineStatNodes(request);
Function<ValueNode<NodeWrapper>, GroupStat> nodeStatFun = valueNode -> {
int executableCount = countStatByNode(request, valueNode, TodoType.EXECUTABLE);
int copiedToMeCount = countStatByNode(request, valueNode, TodoType.COPIED_TO_ME);
try {
LeafNodeStats nodeStats = countStatByNodes(request, nodes);
List<GroupStat> stats = new ArrayList<>();
for (ValueNode<NodeWrapper> valueNode : nodes) {
GroupStat groupStat = new GroupStat();
stats.add(groupStat);
MessageGroupNode node = valueNode.getValue().unwrap();
groupStat.setGroupCode(node.getCode());
groupStat.setGroupName(node.getName());
groupStat.setGroupIcon(node.getIcon());
groupStat.setStat(new Stat(executableCount, copiedToMeCount));
return groupStat;
};
try {
groupStat.setStat(new Stat(
nodeStats.getCount(valueNode, TodoType.EXECUTABLE),
nodeStats.getCount(valueNode, TodoType.COPIED_TO_ME)));
}
PendingMessageStatisticResponseV2 resp = new PendingMessageStatisticResponseV2();
// 先提交任务
CompletableFuture<Integer> uniqueExecutableCountFuture = CompletableFuture.supplyAsync(
() -> countStatByNodes(request, nodes, TodoType.EXECUTABLE), statExecutor);
CompletableFuture<Integer> uniqueCopiedToMeCountFuture = CompletableFuture.supplyAsync(
() -> countStatByNodes(request, nodes, TodoType.COPIED_TO_ME), statExecutor);
// 用一个偏大的线程池, IO密集型任务, commonPool不够用
// 提交任务并获取结果
List<GroupStat> stats = statExecutor
.submit(() -> nodes
.parallelStream()
.map(nodeStatFun)
.collect(toList()))
.get();
resp.addGroupStats(stats);
// 一个端可能会有重复分类
resp.setTotalStat();
CompletableFuture.allOf(uniqueExecutableCountFuture, uniqueCopiedToMeCountFuture).join();
// 一个端不包含重复分类
int uniqueExecutableCount = uniqueExecutableCountFuture.get();
int uniqueCopiedToMeCount = uniqueCopiedToMeCountFuture.get();
resp.setUniqueTotalStat(new Stat(uniqueExecutableCount, uniqueCopiedToMeCount));
// 为了性能, 不做去重的统计了
resp.setUniqueTotalStat(new Stat(-1, -1));
return resp;
} catch (Exception e) {
log.warn("分类统计异常, request={}", request, e);
@ -307,28 +292,26 @@ public class TodoRangeQueryService {
return totalStat == null ? 0 : totalStat.getPendingCount();
}
private int countStatByNode(MessageGroupNodeStatisticParam request,
ValueNode<NodeWrapper> node, TodoType todoType) {
return countStatByNodes(request, Collections.singletonList(node), todoType);
}
private int countStatByNodes(MessageGroupNodeStatisticParam request,
List<ValueNode<NodeWrapper>> nodes, TodoType todoType) {
private LeafNodeStats countStatByNodes(MessageGroupNodeStatisticParam request, List<ValueNode<NodeWrapper>> nodes) {
List<String> templateCodes = groupTemplateService.collectTemplateCodes(nodes);
GroupTemplates groupTemplates = groupTemplateService.collectTemplates(nodes);
if (CollectionUtils.isEmpty(templateCodes))
return 0;
OuInfo ouInfo = OuInfo.create(request.getOuId(), request.getTerminalType(), todoType);
LambdaQueryWrapper<Todo> query = todoQuery(ouInfo)
return new LeafNodeStats(Collections.emptyList(), groupTemplates);
LambdaQueryWrapper<Todo> query = new QueryWrapper<Todo>()
// 1. group by bellow
.select("COUNT(*) AS count, template_code, type")
.lambda();
OuInfo ouInfo = new OuInfo(request.getOuId(), request.getTerminalType());
//noinspection unchecked
List<Todo> stats = todoDao.list(todoQuery(query, ouInfo, null)
.in(Todo::getTemplateCode, templateCodes)
.eq(Todo::getExecutorPersonId, request.getPersonId())
.in(CollectionUtils.isNotEmpty(request.getWorkspaceIds()), Todo::getOrgId, request.getWorkspaceIds())
.and(todoType == TodoType.EXECUTABLE, nested -> nested
.eq(Todo::getType, TodoType.EXECUTABLE)
.eq(Todo::getState, PendingMessageStateEnum.HAS_BEEN_SENT))
.and(todoType == TodoType.COPIED_TO_ME, nested -> nested
.eq(Todo::getType, TodoType.COPIED_TO_ME)
.eq(Todo::getState, PendingMessageStateEnum.CREATED));
return todoDao.count(query);
.in(Todo::getType, TodoType.EXECUTABLE, TodoType.COPIED_TO_ME)
.in(Todo::getState, PendingMessageStateEnum.HAS_BEEN_SENT, PendingMessageStateEnum.CREATED)
// 2. group by here
.groupBy(Todo::getTemplateCode, Todo::getType));
return new LeafNodeStats(stats, groupTemplates);
}
public List<ValueNode<NodeWrapper>> determineStatNodes(MessageGroupNodeStatisticParam request) {
@ -358,11 +341,8 @@ public class TodoRangeQueryService {
.likeRight(matchBusinessTitle, TodoBusiness::getTitle, title));
}
private LambdaQueryWrapper<Todo> todoQuery(OuInfo info) {
return todoQuery(info, null);
}
private LambdaQueryWrapper<Todo> todoQuery(OuInfo info, @Nullable Ref<List<Long>> ouCollector) {
private LambdaQueryWrapper<Todo> todoQuery(LambdaQueryWrapper<Todo> query,
OuInfo info, @Nullable Ref<List<Long>> ouCollector) {
List<Long> ouIds = Collections.emptyList();
// 工人端不通过ou做查询
if (info.terminalType != AppTerminalTypeEnum.C_WORKER_APP) {
@ -372,7 +352,7 @@ public class TodoRangeQueryService {
}
// 1. 查询 ouId = 0 的数据
// 2. 查询ouId下面所有的平台班组id当成ouId
return query(Todo.class)
return query
.eq(Todo::getIsDelete, TableIsDeleteEnum.NORMAL.value)
.in(CollectionUtils.isNotEmpty(ouIds), Todo::getOuId, ouIds);
}
@ -381,11 +361,6 @@ public class TodoRangeQueryService {
private static class OuInfo {
final Long ouId;
final AppTerminalTypeEnum terminalType;
final boolean includeZeroOuId;
static OuInfo create(Long ouId, AppTerminalTypeEnum terminalType, TodoType todoType) {
return new OuInfo(ouId, terminalType, todoType == TodoType.EXECUTABLE);
}
}
}

View File

@ -16,7 +16,6 @@ import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import java.util.HashMap;
/**
* @author yanglin
@ -167,6 +166,12 @@ public class Todo extends BaseEntityExt<Todo> {
// !! helper
/**
* 辅助字段, 用于统计数量
*/
@TableField(exist = false)
private int count;
@Override
public String toString() {
return JSON.toJSONString(this);