新增 查询经营效率接口

This commit is contained in:
Lemon
2026-04-21 09:13:04 +08:00
parent 216613b67c
commit 51ec0edaa4
5 changed files with 370 additions and 2 deletions

View File

@@ -8,9 +8,11 @@ import com.jsowell.common.exception.BusinessException;
import com.jsowell.common.response.RestApiResponse;
import com.jsowell.pile.dto.MerchantOrderReportDTO;
import com.jsowell.pile.dto.ParkingCouponRecordQueryDTO;
import com.jsowell.pile.dto.business.BusinessEfficiencyQueryDTO;
import com.jsowell.pile.dto.business.BusinessOperationAnalysisQueryDTO;
import com.jsowell.pile.dto.business.BusinessScaleQueryDTO;
import com.jsowell.pile.service.BusinessFinancialService;
import com.jsowell.pile.vo.uniapp.business.BusinessEfficiencyVO;
import com.jsowell.pile.vo.uniapp.business.BusinessGunEfficiencyAnalysisVO;
import com.jsowell.pile.vo.uniapp.business.BusinessOperationAnalysisVO;
import com.jsowell.pile.vo.uniapp.business.BusinessScaleVO;
@@ -148,4 +150,32 @@ public class BusinessFinancialController extends BaseController {
}
return response;
}
/**
* 查询经营效率
*
* @param dto 查询参数
* @return 经营效率数据(含指标卡片和曲线图)
*/
@PostMapping("/businessEfficiency")
public RestApiResponse<?> getBusinessEfficiency(@RequestBody BusinessEfficiencyQueryDTO dto) {
logger.info("查询经营效率 params:{}", JSONObject.toJSONString(dto));
RestApiResponse<?> response;
try {
if (dto == null) {
throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR);
}
BusinessEfficiencyVO result = businessFinancialService.getBusinessEfficiency(dto);
response = new RestApiResponse<>(result);
logger.info("查询经营效率成功 startTime:{}, endTime:{}, selectedMetricCode:{}",
dto.getStartTime(), dto.getEndTime(), dto.getSelectedMetricCode());
} catch (BusinessException e) {
logger.warn("查询经营效率业务异常 code:{}, message:{}", e.getCode(), e.getMessage(), e);
response = new RestApiResponse<>(e.getCode(), e.getMessage());
} catch (Exception e) {
logger.error("查询经营效率系统异常 params:{}", JSONObject.toJSONString(dto), e);
response = new RestApiResponse<>(e);
}
return response;
}
}

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="/opt/app/spring/logs" />
<property name="log.path" value="./opt/app/spring/logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n%ex" />
<!-- 日志最大的历史 90天 -->

View File

@@ -4,7 +4,9 @@ import com.jsowell.common.core.page.PageResponse;
import com.jsowell.pile.dto.MerchantOrderReportDTO;
import com.jsowell.pile.dto.ParkingCouponRecordQueryDTO;
import com.jsowell.pile.dto.business.BusinessOperationAnalysisQueryDTO;
import com.jsowell.pile.dto.business.BusinessEfficiencyQueryDTO;
import com.jsowell.pile.dto.business.BusinessScaleQueryDTO;
import com.jsowell.pile.vo.uniapp.business.BusinessEfficiencyVO;
import com.jsowell.pile.vo.uniapp.business.BusinessGunEfficiencyAnalysisVO;
import com.jsowell.pile.vo.uniapp.business.BusinessOperationAnalysisVO;
import com.jsowell.pile.vo.uniapp.business.BusinessScaleVO;
@@ -45,4 +47,12 @@ public interface BusinessFinancialService {
* @return 经营规模数据(含指标卡片和曲线图)
*/
BusinessScaleVO getBusinessScale(BusinessScaleQueryDTO dto);
/**
* 查询经营效率
*
* @param dto 查询条件
* @return 经营效率数据(含指标卡片和曲线图)
*/
BusinessEfficiencyVO getBusinessEfficiency(BusinessEfficiencyQueryDTO dto);
}

View File

@@ -12,11 +12,13 @@ import com.jsowell.common.enums.ykc.ReturnCodeEnum;
import com.jsowell.common.exception.BusinessException;
import com.jsowell.pile.dto.MerchantOrderReportDTO;
import com.jsowell.pile.dto.ParkingCouponRecordQueryDTO;
import com.jsowell.pile.dto.business.BusinessEfficiencyQueryDTO;
import com.jsowell.pile.dto.business.BusinessOperationAnalysisQueryDTO;
import com.jsowell.pile.dto.business.BusinessOperationDateRangeDTO;
import com.jsowell.pile.dto.business.BusinessOperationSummaryDTO;
import com.jsowell.pile.dto.business.BusinessScaleQueryDTO;
import com.jsowell.pile.domain.PileConnectorInfo;
import com.jsowell.pile.domain.PileStationInfo;
import com.jsowell.pile.domain.SettleOrderReport;
import com.jsowell.pile.service.BusinessFinancialService;
import com.jsowell.pile.service.CarCouponRecordService;
@@ -25,7 +27,9 @@ import com.jsowell.pile.service.PileConnectorInfoService;
import com.jsowell.pile.service.PileStationInfoService;
import com.jsowell.pile.service.SettleOrderReportService;
import com.jsowell.pile.util.UserUtils;
import com.jsowell.pile.vo.base.ConnectorInfoVO;
import com.jsowell.pile.vo.uniapp.business.BusinessGunEfficiencyAnalysisVO;
import com.jsowell.pile.vo.uniapp.business.BusinessEfficiencyVO;
import com.jsowell.pile.vo.uniapp.business.BusinessOperationAnalysisVO;
import com.jsowell.pile.vo.uniapp.business.BusinessOperationDiagnosisItemVO;
import com.jsowell.pile.vo.uniapp.business.BusinessOperationMetricVO;
@@ -74,6 +78,11 @@ public class BusinessFinancialServiceImpl implements BusinessFinancialService {
private static final String SCALE_METRIC_SERVICE_AMOUNT = "SERVICE_AMOUNT";
private static final String SCALE_METRIC_USE_ELECTRICITY = "USE_ELECTRICITY";
private static final String EFFICIENCY_METRIC_AVG_SERVICE_FEE_PER_DEGREE = "AVG_SERVICE_FEE_PER_DEGREE";
private static final String EFFICIENCY_METRIC_TIME_UTILIZATION = "TIME_UTILIZATION";
private static final String EFFICIENCY_METRIC_GUN_AVG_ELECTRICITY = "GUN_AVG_ELECTRICITY";
private static final String EFFICIENCY_METRIC_POWER_UTILIZATION = "POWER_UTILIZATION";
/**
* 财务查询通用线程池(支持多方法并行查询)
* 核心线程数4覆盖经营规模(2路)、我的钱包(3路)、经营分析(2路)、枪均效率(2路)等场景
@@ -785,4 +794,323 @@ public class BusinessFinancialServiceImpl implements BusinessFinancialService {
return BigDecimalUtils.nullToZero(report.getUseElectricity());
}
}
/**
* 查询经营效率
* 优化:当前周期和上周期汇总并行查询,枪口数量与总额定功率并行查询
*
* @param dto 查询条件
* @return 经营效率数据
*/
@Override
public BusinessEfficiencyVO getBusinessEfficiency(BusinessEfficiencyQueryDTO dto) {
if (dto == null) {
return null;
}
if (StringUtils.isBlank(dto.getStartTime()) && StringUtils.isBlank(dto.getEndTime())) {
LocalDate endDate = LocalDate.now();
LocalDate startDate = endDate.minusDays(6);
dto.setStartTime(startDate.toString());
dto.setEndTime(endDate.toString());
}
BusinessOperationDateRangeDTO range = buildDateRangeFromEfficiency(dto);
List<String> stationIdList = resolveStationIds(dto.getStationIdList());
// 并行查询当前周期和上周期原始报表数据
CompletableFuture<List<SettleOrderReport>> currentReportFuture = CompletableFuture.supplyAsync(
() -> queryRawReports(stationIdList, range.getCurrentStart(), range.getCurrentEnd()),
FINANCIAL_THREAD_POOL);
CompletableFuture<List<SettleOrderReport>> previousReportFuture = CompletableFuture.supplyAsync(
() -> queryRawReports(stationIdList, range.getPreviousStart(), range.getPreviousEnd()),
FINANCIAL_THREAD_POOL);
// 等待报表查询完成
List<SettleOrderReport> currentReports = currentReportFuture.join();
List<SettleOrderReport> previousReports = previousReportFuture.join();
BusinessOperationSummaryDTO currentSummary = buildSummaryFromReports(currentReports);
BusinessOperationSummaryDTO previousSummary = buildSummaryFromReports(previousReports);
Map<String, SettleOrderReport> currentDailyMap = buildDailyMapFromReports(currentReports);
Map<String, SettleOrderReport> previousDailyMap = buildDailyMapFromReports(previousReports);
long dayCount = calculateInclusiveDays(range.getCurrentStart(), range.getCurrentEnd());
// 查询枪口数量和总额定功率
ConnectorStats connectorStats = buildConnectorStats(stationIdList);
int connectorCount = connectorStats.getConnectorCount();
BigDecimal totalRatedPower = connectorStats.getTotalRatedPower();
// 构建指标卡片
BigDecimal currentAvgServiceFeePerDegree = BigDecimalUtils.safeDivide(
currentSummary.getServiceAmount(), currentSummary.getUseElectricity(), 2);
BigDecimal previousAvgServiceFeePerDegree = BigDecimalUtils.safeDivide(
previousSummary.getServiceAmount(), previousSummary.getUseElectricity(), 2);
BigDecimal currentTimeUtilization = calculateTimeUtilization(
currentSummary.getChargeTime(), connectorCount, dayCount);
BigDecimal previousTimeUtilization = calculateTimeUtilization(
previousSummary.getChargeTime(), connectorCount, dayCount);
BigDecimal currentGunAvgElectricity = connectorCount > 0
? BigDecimalUtils.safeDivide(currentSummary.getUseElectricity(),
BigDecimal.valueOf(connectorCount), 2)
: BigDecimal.ZERO;
BigDecimal previousGunAvgElectricity = connectorCount > 0
? BigDecimalUtils.safeDivide(previousSummary.getUseElectricity(),
BigDecimal.valueOf(connectorCount), 2)
: BigDecimal.ZERO;
BigDecimal currentPowerUtilization = calculatePowerUtilization(
currentSummary.getUseElectricity(), currentSummary.getChargeTime(),
connectorCount, dayCount, totalRatedPower);
BigDecimal previousPowerUtilization = calculatePowerUtilization(
previousSummary.getUseElectricity(), previousSummary.getChargeTime(),
connectorCount, dayCount, totalRatedPower);
List<BusinessScaleMetricVO> metricList = new ArrayList<>();
metricList.add(buildScaleMetric(EFFICIENCY_METRIC_AVG_SERVICE_FEE_PER_DEGREE, "度均服务费", "",
currentAvgServiceFeePerDegree, previousAvgServiceFeePerDegree));
metricList.add(buildScaleMetric(EFFICIENCY_METRIC_TIME_UTILIZATION, "时间利用率", "%",
currentTimeUtilization, previousTimeUtilization));
metricList.add(buildScaleMetric(EFFICIENCY_METRIC_GUN_AVG_ELECTRICITY, "枪均电量", "",
currentGunAvgElectricity, previousGunAvgElectricity));
metricList.add(buildScaleMetric(EFFICIENCY_METRIC_POWER_UTILIZATION, "功率利用率", "%",
currentPowerUtilization, previousPowerUtilization));
// 构建选中指标的曲线图数据
String selectedMetricCode = StringUtils.isBlank(dto.getSelectedMetricCode())
? EFFICIENCY_METRIC_AVG_SERVICE_FEE_PER_DEGREE : dto.getSelectedMetricCode();
BusinessScaleChartVO chartData = buildEfficiencyChartFromMaps(
selectedMetricCode, range, currentDailyMap, previousDailyMap,
connectorCount, totalRatedPower);
return BusinessEfficiencyVO.builder()
.currentStartTime(range.getCurrentStart())
.currentEndTime(range.getCurrentEnd())
.previousStartTime(range.getPreviousStart())
.previousEndTime(range.getPreviousEnd())
.metricList(metricList)
.chartData(chartData)
.build();
}
/**
* 构建经营效率日期区间
*/
private BusinessOperationDateRangeDTO buildDateRangeFromEfficiency(BusinessEfficiencyQueryDTO dto) {
try {
LocalDate currentStart = LocalDate.parse(dto.getStartTime());
LocalDate currentEnd = LocalDate.parse(dto.getEndTime());
if (currentEnd.isBefore(currentStart)) {
throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR);
}
long days = ChronoUnit.DAYS.between(currentStart, currentEnd) + 1;
LocalDate previousEnd = currentStart.minusDays(1);
LocalDate previousStart = previousEnd.minusDays(days - 1);
return BusinessOperationDateRangeDTO.builder()
.currentStart(currentStart.toString())
.currentEnd(currentEnd.toString())
.previousStart(previousStart.toString())
.previousEnd(previousEnd.toString())
.build();
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR);
}
}
/**
* 构建枪口统计信息(枪口数量 + 总额定功率)
*/
private ConnectorStats buildConnectorStats(List<String> stationIdList) {
List<ConnectorInfoVO> connectors;
if (stationIdList == null) {
// 平台账号先查询所有站点ID再批量查询枪口信息
List<PileStationInfo> allStations = pileStationInfoService.selectPileStationInfoList(new PileStationInfo());
List<String> allStationIds = allStations.stream()
.map(s -> String.valueOf(s.getId()))
.collect(Collectors.toList());
if (allStationIds.isEmpty()) {
return new ConnectorStats(0, BigDecimal.ZERO);
}
connectors = pileConnectorInfoService.batchSelectConnectorList(allStationIds);
} else if (stationIdList.isEmpty()) {
return new ConnectorStats(0, BigDecimal.ZERO);
} else {
connectors = pileConnectorInfoService.batchSelectConnectorList(stationIdList);
}
if (CollectionUtils.isEmpty(connectors)) {
return new ConnectorStats(0, BigDecimal.ZERO);
}
BigDecimal totalRatedPower = BigDecimal.ZERO;
for (ConnectorInfoVO connector : connectors) {
totalRatedPower = totalRatedPower.add(BigDecimalUtils.parseOrZero(connector.getRatedPower()));
}
return new ConnectorStats(connectors.size(), totalRatedPower);
}
/**
* 计算时间利用率(%
* = 总充电时长(分钟) / (枪口数 × 天数 × 1440分钟) × 100
*/
private BigDecimal calculateTimeUtilization(BigDecimal chargeTime, int connectorCount, long dayCount) {
if (connectorCount == 0 || dayCount == 0) {
return BigDecimal.ZERO;
}
BigDecimal totalAvailableMinutes = BigDecimal.valueOf(connectorCount)
.multiply(BigDecimal.valueOf(dayCount))
.multiply(new BigDecimal("1440"));
BigDecimal utilization = BigDecimalUtils.safeDivide(
BigDecimalUtils.nullToZero(chargeTime), totalAvailableMinutes, 4);
return utilization.multiply(new BigDecimal("100")).setScale(2, RoundingMode.HALF_UP);
}
/**
* 计算功率利用率(%
* = 实际总能量 / 理论最大总能量 × 100
* = useElectricity / (总额定功率 × 天数 × 24) × 100
*/
private BigDecimal calculatePowerUtilization(BigDecimal useElectricity, BigDecimal chargeTime,
int connectorCount, long dayCount, BigDecimal totalRatedPower) {
if (totalRatedPower.compareTo(BigDecimal.ZERO) == 0 || dayCount == 0) {
return BigDecimal.ZERO;
}
BigDecimal theoreticalMaxEnergy = totalRatedPower
.multiply(BigDecimal.valueOf(dayCount))
.multiply(new BigDecimal("24"));
BigDecimal utilization = BigDecimalUtils.safeDivide(
BigDecimalUtils.nullToZero(useElectricity), theoreticalMaxEnergy, 4);
return utilization.multiply(new BigDecimal("100")).setScale(2, RoundingMode.HALF_UP);
}
/**
* 从dailyMap构建经营效率曲线图数据
* 每个指标按天计算(度均服务费=当天服务费/当天电量, 时间利用率=当天充电时长/可用时长, 等)
*/
private BusinessScaleChartVO buildEfficiencyChartFromMaps(String metricCode,
BusinessOperationDateRangeDTO range,
Map<String, SettleOrderReport> currentDailyMap,
Map<String, SettleOrderReport> previousDailyMap,
int connectorCount,
BigDecimal totalRatedPower) {
String metricName;
String unit;
switch (metricCode) {
case EFFICIENCY_METRIC_TIME_UTILIZATION:
metricName = "时间利用率";
unit = "%";
break;
case EFFICIENCY_METRIC_GUN_AVG_ELECTRICITY:
metricName = "枪均电量";
unit = "";
break;
case EFFICIENCY_METRIC_POWER_UTILIZATION:
metricName = "功率利用率";
unit = "%";
break;
default:
metricName = "度均服务费";
unit = "";
break;
}
LocalDate currentStart = LocalDate.parse(range.getCurrentStart());
LocalDate currentEnd = LocalDate.parse(range.getCurrentEnd());
LocalDate previousStart = LocalDate.parse(range.getPreviousStart());
long dayCount = ChronoUnit.DAYS.between(currentStart, currentEnd) + 1;
DateTimeFormatter displayFormatter = DateTimeFormatter.ofPattern("MM/dd");
List<BusinessScaleChartVO.ChartPoint> chartPoints = new ArrayList<>();
for (long i = 0; i < dayCount; i++) {
LocalDate currentDate = currentStart.plusDays(i);
LocalDate previousDate = previousStart.plusDays(i);
SettleOrderReport currentReport = currentDailyMap.get(currentDate.toString());
SettleOrderReport previousReport = previousDailyMap.get(previousDate.toString());
BigDecimal currentValue = calculateEfficiencyMetricValue(
metricCode, currentReport, connectorCount, 1, totalRatedPower);
BigDecimal previousValue = calculateEfficiencyMetricValue(
metricCode, previousReport, connectorCount, 1, totalRatedPower);
chartPoints.add(BusinessScaleChartVO.ChartPoint.builder()
.date(currentDate.format(displayFormatter))
.fullDate(currentDate.toString())
.value(BigDecimalUtils.normalize(currentValue))
.previousValue(BigDecimalUtils.normalize(previousValue))
.changeRate(BigDecimalUtils.calculateRate(
BigDecimalUtils.nullToZero(currentValue),
BigDecimalUtils.nullToZero(previousValue)))
.build());
}
return BusinessScaleChartVO.builder()
.metricCode(metricCode)
.metricName(metricName)
.unit(unit)
.chartData(chartPoints)
.build();
}
/**
* 根据指标编码计算单条报表记录对应的经营效率指标值
*/
private BigDecimal calculateEfficiencyMetricValue(String metricCode, SettleOrderReport report,
int connectorCount, long dayCount,
BigDecimal totalRatedPower) {
if (report == null) {
return BigDecimal.ZERO;
}
BigDecimal serviceAmount = BigDecimalUtils.nullToZero(report.getServiceAmount());
BigDecimal useElectricity = BigDecimalUtils.nullToZero(report.getUseElectricity());
BigDecimal chargeTime = BigDecimalUtils.parseOrZero(report.getChargeTime());
switch (metricCode) {
case EFFICIENCY_METRIC_AVG_SERVICE_FEE_PER_DEGREE:
// 度均服务费 = 服务费 / 用电量
return BigDecimalUtils.safeDivide(serviceAmount, useElectricity, 2);
case EFFICIENCY_METRIC_TIME_UTILIZATION:
// 时间利用率 = 充电时长 / (枪口数 × 1天 × 1440) × 100
return calculateTimeUtilization(chargeTime, connectorCount, dayCount);
case EFFICIENCY_METRIC_GUN_AVG_ELECTRICITY:
// 枪均电量 = 用电量 / 枪口数
return connectorCount > 0
? BigDecimalUtils.safeDivide(useElectricity, BigDecimal.valueOf(connectorCount), 2)
: BigDecimal.ZERO;
case EFFICIENCY_METRIC_POWER_UTILIZATION:
// 功率利用率 = 用电量 / (总额定功率 × 1天 × 24) × 100
return calculatePowerUtilization(useElectricity, chargeTime,
connectorCount, dayCount, totalRatedPower);
default:
return BigDecimal.ZERO;
}
}
/**
* 枪口统计信息内部类
*/
private static class ConnectorStats {
private final int connectorCount;
private final BigDecimal totalRatedPower;
ConnectorStats(int connectorCount, BigDecimal totalRatedPower) {
this.connectorCount = connectorCount;
this.totalRatedPower = totalRatedPower;
}
int getConnectorCount() {
return connectorCount;
}
BigDecimal getTotalRatedPower() {
return totalRatedPower;
}
}
}

View File

@@ -3497,7 +3497,7 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService {
logger.debug("redis中没有实时数据了去数据库查");
// redis中为空去查库
OrderMonitorData orderMonitorData = orderMonitorDataService.selectByTransactionCode(transactionCode);
if (orderMonitorData != null) {
if (orderMonitorData != null && StringUtils.isNotBlank(orderMonitorData.getMonitorData())) {
String monitorData = orderMonitorData.getMonitorData();
List<RealTimeMonitorData> dataList = JSON.parseArray(monitorData, RealTimeMonitorData.class);
resultList.addAll(dataList);