优化进度管理页面接口

This commit is contained in:
cjh
2026-06-24 15:39:34 +08:00
parent 2f9be5d98d
commit 98d6ded07e
3 changed files with 162 additions and 85 deletions

View File

@@ -0,0 +1,17 @@
package com.bim.api.config;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer longToStringCustomizer() {
return builder -> builder
.serializerByType(Long.class, ToStringSerializer.instance)
.serializerByType(Long.TYPE, ToStringSerializer.instance);
}
}

View File

@@ -47,13 +47,15 @@ public class BimController {
} }
@GetMapping("/progressData") @GetMapping("/progressData")
public Map<String, Object> getProgressData(@RequestParam String date) { public Map<String, Object> getProgressData(@RequestParam String date,
return progressDataService.getOrCreateTask(date); @RequestParam(required = false) Long modelId) {
return progressDataService.getOrCreateTask(date, modelId);
} }
@GetMapping("/progressData/result") @GetMapping("/progressData/result")
public Map<String, Object> getProgressDataResult(@RequestParam String date) { public Map<String, Object> getProgressDataResult(@RequestParam String date,
return progressDataService.getTaskResultByDate(date); @RequestParam(required = false) Long modelId) {
return progressDataService.getTaskResultByDate(date, modelId);
} }
@PostMapping("/getPjDayListByPositionId") @PostMapping("/getPjDayListByPositionId")

View File

@@ -1,5 +1,6 @@
package com.bim.api.service; package com.bim.api.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bim.api.entity.ActAmtResponse; import com.bim.api.entity.ActAmtResponse;
import com.bim.api.entity.PartCodeRelation; import com.bim.api.entity.PartCodeRelation;
@@ -15,6 +16,7 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -42,36 +44,38 @@ public class ProgressDataService extends ServiceImpl<PartCodeRelationMapper, Par
this.redisTemplate = redisTemplate; this.redisTemplate = redisTemplate;
} }
public Map<String, Object> getOrCreateTask(String date) { public Map<String, Object> getOrCreateTask(String date, Long modelId) {
String normalizedDate = normalizeDate(date); String normalizedDate = normalizeDate(date);
String taskScope = buildTaskScope(normalizedDate, modelId);
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("date", normalizedDate); result.put("date", normalizedDate);
result.put("modelId", modelId == null ? null : String.valueOf(modelId));
String status = getTaskStatusByDate(normalizedDate); String status = getTaskStatusByDate(taskScope);
if ("COMPLETED".equals(status)) { if ("COMPLETED".equals(status)) {
Object cachedData = redisTemplate.opsForValue().get(buildTaskKey(normalizedDate, SUFFIX_RESULT)); Object cachedData = redisTemplate.opsForValue().get(buildTaskKey(taskScope, SUFFIX_RESULT));
if (cachedData != null) { if (cachedData != null) {
Integer cachedTotal = (Integer) redisTemplate.opsForValue().get(buildTaskKey(normalizedDate, SUFFIX_TOTAL)); Integer cachedTotal = (Integer) redisTemplate.opsForValue().get(buildTaskKey(taskScope, SUFFIX_TOTAL));
int dbTotal = list().size(); int dbTotal = listRelationsByModel(modelId).size();
if (cachedTotal != null && cachedTotal == dbTotal) { if (cachedTotal != null && cachedTotal == dbTotal && hasCurrentResultShape(cachedData)) {
result.put("status", "COMPLETED"); result.put("status", "COMPLETED");
return result; return result;
} }
clearTaskCache(normalizedDate); clearTaskCache(taskScope);
} else { } else {
result.put("status", "COMPLETED"); result.put("status", "COMPLETED");
return result; return result;
} }
} }
String processing = (String) redisTemplate.opsForValue().get(buildProcessingKey(normalizedDate)); String processing = (String) redisTemplate.opsForValue().get(buildProcessingKey(taskScope));
if (processing != null) { if (processing != null) {
result.put("status", "PROCESSING"); result.put("status", "PROCESSING");
return result; return result;
} }
Boolean lockResult = redisTemplate.opsForValue().setIfAbsent( Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(
buildProcessingKey(normalizedDate), buildProcessingKey(taskScope),
"1", "1",
TASK_TTL_MINUTES, TASK_TTL_MINUTES,
TimeUnit.MINUTES TimeUnit.MINUTES
@@ -81,29 +85,31 @@ public class ProgressDataService extends ServiceImpl<PartCodeRelationMapper, Par
return result; return result;
} }
initTask(normalizedDate); initTask(taskScope);
calculateProgressAsync(normalizedDate); calculateProgressAsync(normalizedDate, taskScope, modelId);
result.put("status", "STARTED"); result.put("status", "STARTED");
return result; return result;
} }
public Map<String, Object> getTaskResultByDate(String date) { public Map<String, Object> getTaskResultByDate(String date, Long modelId) {
String normalizedDate = normalizeDate(date); String normalizedDate = normalizeDate(date);
String taskScope = buildTaskScope(normalizedDate, modelId);
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("date", normalizedDate); result.put("date", normalizedDate);
result.put("modelId", modelId == null ? null : String.valueOf(modelId));
String status = getTaskStatusByDate(normalizedDate); String status = getTaskStatusByDate(taskScope);
result.put("status", status); result.put("status", status);
if (status == null) { if (status == null) {
String processing = (String) redisTemplate.opsForValue().get(buildProcessingKey(normalizedDate)); String processing = (String) redisTemplate.opsForValue().get(buildProcessingKey(taskScope));
result.put("status", processing != null ? "PROCESSING" : "NOT_FOUND"); result.put("status", processing != null ? "PROCESSING" : "NOT_FOUND");
return result; return result;
} }
Integer total = (Integer) redisTemplate.opsForValue().get(buildTaskKey(normalizedDate, SUFFIX_TOTAL)); Integer total = (Integer) redisTemplate.opsForValue().get(buildTaskKey(taskScope, SUFFIX_TOTAL));
Integer current = (Integer) redisTemplate.opsForValue().get(buildTaskKey(normalizedDate, SUFFIX_CURRENT)); Integer current = (Integer) redisTemplate.opsForValue().get(buildTaskKey(taskScope, SUFFIX_CURRENT));
result.put("total", total != null ? total : 0); result.put("total", total != null ? total : 0);
result.put("current", current != null ? current : 0); result.put("current", current != null ? current : 0);
@@ -115,12 +121,12 @@ public class ProgressDataService extends ServiceImpl<PartCodeRelationMapper, Par
result.put("progress", progress); result.put("progress", progress);
if ("COMPLETED".equals(status)) { if ("COMPLETED".equals(status)) {
Object data = redisTemplate.opsForValue().get(buildTaskKey(normalizedDate, SUFFIX_RESULT)); Object data = redisTemplate.opsForValue().get(buildTaskKey(taskScope, SUFFIX_RESULT));
result.put("data", data); result.put("data", data);
} }
if ("FAILED".equals(status)) { if ("FAILED".equals(status)) {
Object error = redisTemplate.opsForValue().get(buildTaskKey(normalizedDate, SUFFIX_ERROR)); Object error = redisTemplate.opsForValue().get(buildTaskKey(taskScope, SUFFIX_ERROR));
if (error != null) { if (error != null) {
result.put("error", error); result.put("error", error);
} }
@@ -140,16 +146,20 @@ public class ProgressDataService extends ServiceImpl<PartCodeRelationMapper, Par
} }
} }
private String buildTaskKey(String date, String suffix) { private String buildTaskScope(String date, Long modelId) {
return KEY_TASK_PREFIX + date + suffix; return modelId == null ? date : date + ":model:" + modelId;
} }
private String buildProcessingKey(String date) { private String buildTaskKey(String taskScope, String suffix) {
return KEY_PROCESSING_PREFIX + date; return KEY_TASK_PREFIX + taskScope + suffix;
} }
private String getTaskStatusByDate(String date) { private String buildProcessingKey(String taskScope) {
return (String) redisTemplate.opsForValue().get(buildTaskKey(date, SUFFIX_STATUS)); return KEY_PROCESSING_PREFIX + taskScope;
}
private String getTaskStatusByDate(String taskScope) {
return (String) redisTemplate.opsForValue().get(buildTaskKey(taskScope, SUFFIX_STATUS));
} }
private Map<String, Object> buildActAmtRequest(String projectId, String positionId, String period) { private Map<String, Object> buildActAmtRequest(String projectId, String positionId, String period) {
@@ -175,25 +185,28 @@ public class ProgressDataService extends ServiceImpl<PartCodeRelationMapper, Par
setTaskValue(date, SUFFIX_CURRENT, 0); setTaskValue(date, SUFFIX_CURRENT, 0);
} }
private void calculateProgressAsync(String date) { private void calculateProgressAsync(String date, String taskScope, Long modelId) {
setTaskValue(date, SUFFIX_STATUS, "PROCESSING"); setTaskValue(taskScope, SUFFIX_STATUS, "PROCESSING");
new Thread(() -> { new Thread(() -> {
try { try {
List<PartCodeRelation> relations = list(); List<PartCodeRelation> relations = listRelationsByModel(modelId);
int total = relations.size(); int total = relations.size();
setTaskValue(date, SUFFIX_TOTAL, total); setTaskValue(taskScope, SUFFIX_TOTAL, total);
setTaskValue(date, SUFFIX_CURRENT, 0); setTaskValue(taskScope, SUFFIX_CURRENT, 0);
List<Map<String, Object>> results = new ArrayList<>(); List<Map<String, Object>> results = new ArrayList<>();
List<Map<String, Object>> rawActAmtResults = new ArrayList<>(); List<Map<String, Object>> rawActAmtResults = new ArrayList<>();
int current = 0; int current = 0;
for (PartCodeRelation relation : relations) { Map<String, List<PartCodeRelation>> groupedRelations = groupRelationsByPart(relations);
for (List<PartCodeRelation> partRelations : groupedRelations.values()) {
PartCodeRelation relation = partRelations.get(0);
Map<String, Object> item = new HashMap<>(); Map<String, Object> item = new HashMap<>();
item.put("partId", relation.getPartId()); item.put("partId", relation.getPartId());
item.put("codeData", relation.getCodeData()); item.put("codeData", relation.getCodeData());
item.put("codeList", buildCodeList(partRelations));
item.put("isLeaf", relation.getIsLeaf()); item.put("isLeaf", relation.getIsLeaf());
try { try {
@@ -213,7 +226,7 @@ public class ProgressDataService extends ServiceImpl<PartCodeRelationMapper, Par
rawEntry.put("request", requestParams); rawEntry.put("request", requestParams);
rawEntry.put("isLeaf", relation.getIsLeaf()); rawEntry.put("isLeaf", relation.getIsLeaf());
double progressData; ProgressAmount progressAmount;
if (Boolean.TRUE.equals(relation.getIsLeaf())) { if (Boolean.TRUE.equals(relation.getIsLeaf())) {
ProjectProgressParams params = new ProjectProgressParams(); ProjectProgressParams params = new ProjectProgressParams();
params.setProjectId(PROJECT_ID); params.setProjectId(PROJECT_ID);
@@ -223,7 +236,7 @@ public class ProgressDataService extends ServiceImpl<PartCodeRelationMapper, Par
PjDayListResponse pjDayResponse = thirdPartyAuthUtil.findPjDayListByPositionId(params); PjDayListResponse pjDayResponse = thirdPartyAuthUtil.findPjDayListByPositionId(params);
rawEntry.put("api", "findPjDayListByPositionId"); rawEntry.put("api", "findPjDayListByPositionId");
rawEntry.put("response", pjDayResponse); rawEntry.put("response", pjDayResponse);
progressData = calculateAverageByPjDay(pjDayResponse); progressAmount = calculateProgressAmountByPjDay(pjDayResponse);
} else { } else {
ActAmtResponse actAmtResponse = thirdPartyAuthUtil.findActAmtByPositionId( ActAmtResponse actAmtResponse = thirdPartyAuthUtil.findActAmtByPositionId(
PROJECT_ID, PROJECT_ID,
@@ -232,15 +245,18 @@ public class ProgressDataService extends ServiceImpl<PartCodeRelationMapper, Par
); );
rawEntry.put("api", "findActAmtByPositionId"); rawEntry.put("api", "findActAmtByPositionId");
rawEntry.put("response", actAmtResponse); rawEntry.put("response", actAmtResponse);
progressData = calculateAverageByActAmt(actAmtResponse); progressAmount = calculateProgressAmountByActAmt(actAmtResponse);
} }
rawActAmtResults.add(rawEntry); rawActAmtResults.add(rawEntry);
double progressData = progressAmount.ratio();
if (Double.isNaN(progressData) || Double.isInfinite(progressData)) { if (Double.isNaN(progressData) || Double.isInfinite(progressData)) {
progressData = 0; progressData = 0;
} }
item.put("progressData", progressData); item.put("progressData", progressData);
item.put("progressNumerator", progressAmount.numerator());
item.put("progressDenominator", progressAmount.denominator());
} catch (Exception e) { } catch (Exception e) {
item.put("progressData", 0); item.put("progressData", 0);
item.put("error", e.getMessage()); item.put("error", e.getMessage());
@@ -257,84 +273,126 @@ public class ProgressDataService extends ServiceImpl<PartCodeRelationMapper, Par
} }
results.add(item); results.add(item);
current++; current += partRelations.size();
setTaskValue(date, SUFFIX_CURRENT, current); setTaskValue(taskScope, SUFFIX_CURRENT, current);
} }
setTaskValue(date, SUFFIX_RESULT, results); setTaskValue(taskScope, SUFFIX_RESULT, results);
setTaskValue(date, SUFFIX_RAW_ACT_AMT, rawActAmtResults); setTaskValue(taskScope, SUFFIX_RAW_ACT_AMT, rawActAmtResults);
setTaskValue(date, SUFFIX_CURRENT, total); setTaskValue(taskScope, SUFFIX_CURRENT, total);
setTaskValue(date, SUFFIX_STATUS, "COMPLETED"); setTaskValue(taskScope, SUFFIX_STATUS, "COMPLETED");
redisTemplate.delete(buildProcessingKey(date)); redisTemplate.delete(buildProcessingKey(taskScope));
} catch (Exception e) { } catch (Exception e) {
setTaskValue(date, SUFFIX_STATUS, "FAILED"); setTaskValue(taskScope, SUFFIX_STATUS, "FAILED");
setTaskValue(date, SUFFIX_ERROR, e.getMessage()); setTaskValue(taskScope, SUFFIX_ERROR, e.getMessage());
redisTemplate.delete(buildProcessingKey(date)); redisTemplate.delete(buildProcessingKey(taskScope));
} }
}).start(); }).start();
} }
private void setTaskValue(String date, String suffix, Object value) { private void setTaskValue(String taskScope, String suffix, Object value) {
redisTemplate.opsForValue().set( redisTemplate.opsForValue().set(
buildTaskKey(date, suffix), buildTaskKey(taskScope, suffix),
value, value,
TASK_TTL_MINUTES, TASK_TTL_MINUTES,
TimeUnit.MINUTES TimeUnit.MINUTES
); );
} }
private void clearTaskCache(String date) { private void clearTaskCache(String taskScope) {
redisTemplate.delete(buildTaskKey(date, SUFFIX_STATUS)); redisTemplate.delete(buildTaskKey(taskScope, SUFFIX_STATUS));
redisTemplate.delete(buildTaskKey(date, SUFFIX_TOTAL)); redisTemplate.delete(buildTaskKey(taskScope, SUFFIX_TOTAL));
redisTemplate.delete(buildTaskKey(date, SUFFIX_CURRENT)); redisTemplate.delete(buildTaskKey(taskScope, SUFFIX_CURRENT));
redisTemplate.delete(buildTaskKey(date, SUFFIX_RESULT)); redisTemplate.delete(buildTaskKey(taskScope, SUFFIX_RESULT));
redisTemplate.delete(buildTaskKey(date, SUFFIX_ERROR)); redisTemplate.delete(buildTaskKey(taskScope, SUFFIX_ERROR));
redisTemplate.delete(buildTaskKey(date, SUFFIX_RAW_ACT_AMT)); redisTemplate.delete(buildTaskKey(taskScope, SUFFIX_RAW_ACT_AMT));
redisTemplate.delete(buildProcessingKey(date)); redisTemplate.delete(buildProcessingKey(taskScope));
} }
private double calculateAverageByActAmt(ActAmtResponse response) { private List<PartCodeRelation> listRelationsByModel(Long modelId) {
if (response == null || response.getData() == null || response.getData().isEmpty()) { return list(new LambdaQueryWrapper<PartCodeRelation>()
return 0; .eq(modelId != null, PartCodeRelation::getModelId, modelId));
}
private boolean hasCurrentResultShape(Object cachedData) {
if (!(cachedData instanceof List<?> cachedList)) {
return false;
} }
double sum = 0; if (cachedList.isEmpty()) {
int count = 0; return true;
}
Object first = cachedList.get(0);
return first instanceof Map<?, ?> cachedItem
&& cachedItem.containsKey("codeList")
&& cachedItem.containsKey("progressNumerator")
&& cachedItem.containsKey("progressDenominator");
}
private Map<String, List<PartCodeRelation>> groupRelationsByPart(List<PartCodeRelation> relations) {
Map<String, List<PartCodeRelation>> grouped = new LinkedHashMap<>();
for (PartCodeRelation relation : relations) {
if (relation == null || relation.getPartId() == null || relation.getPartId().isBlank()) {
continue;
}
String key = relation.getPartId() + "|" + relation.getIsLeaf();
grouped.computeIfAbsent(key, ignored -> new ArrayList<>()).add(relation);
}
return grouped;
}
private List<Map<String, Object>> buildCodeList(List<PartCodeRelation> relations) {
List<Map<String, Object>> codeList = new ArrayList<>();
for (PartCodeRelation relation : relations) {
Map<String, Object> codeItem = new HashMap<>();
codeItem.put("codeId", relation.getCodeId());
codeItem.put("code", relation.getCodeId());
codeItem.put("codeData", relation.getCodeData());
codeItem.put("modelId", relation.getModelId() == null ? null : String.valueOf(relation.getModelId()));
codeList.add(codeItem);
}
return codeList;
}
private ProgressAmount calculateProgressAmountByActAmt(ActAmtResponse response) {
if (response == null || response.getData() == null || response.getData().isEmpty()) {
return new ProgressAmount(0, 0);
}
double numeratorSum = 0;
double denominatorSum = 0;
for (ActAmtResponse.ActAmtData data : response.getData()) { for (ActAmtResponse.ActAmtData data : response.getData()) {
if (data == null) { if (data == null) {
continue; continue;
}
Double numerator = data.getActAmt();
Double denominator = data.getMeteringAmt();
double ratio = 0;
if (numerator != null && denominator != null && denominator != 0) {
ratio = numerator / denominator;
} }
sum += ratio; numeratorSum += safeDouble(data.getActAmt());
count++; denominatorSum += safeDouble(data.getMeteringAmt());
} }
return count == 0 ? 0 : sum / count; return new ProgressAmount(numeratorSum, denominatorSum);
} }
private double calculateAverageByPjDay(PjDayListResponse response) { private ProgressAmount calculateProgressAmountByPjDay(PjDayListResponse response) {
if (response == null || response.getData() == null || response.getData().isEmpty()) { if (response == null || response.getData() == null || response.getData().isEmpty()) {
return 0; return new ProgressAmount(0, 0);
} }
double sum = 0; double numeratorSum = 0;
int count = 0; double denominatorSum = 0;
for (PjDayListResponse.PjDayData data : response.getData()) { for (PjDayListResponse.PjDayData data : response.getData()) {
if (data == null) { if (data == null) {
continue; continue;
} }
Double numerator = data.getTotalAmt(); numeratorSum += safeDouble(data.getTotalAmt());
Double denominator = data.getMeteringAmt(); denominatorSum += safeDouble(data.getMeteringAmt());
double ratio = 0; }
if (numerator != null && denominator != null && denominator != 0) { return new ProgressAmount(numeratorSum, denominatorSum);
ratio = numerator / denominator; }
}
sum += ratio; private double safeDouble(Double value) {
count++; return value == null || Double.isNaN(value) || Double.isInfinite(value) ? 0 : value;
}
private record ProgressAmount(double numerator, double denominator) {
double ratio() {
return denominator == 0 ? 0 : numerator / denominator;
} }
return count == 0 ? 0 : sum / count;
} }
} }