package com.ruoyi.other.service.impl;
|
|
import com.alibaba.fastjson.JSONArray;
|
import com.alibaba.fastjson2.JSONObject;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.google.common.collect.Lists;
|
import com.ruoyi.common.core.exception.ServiceException;
|
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.core.web.page.PageInfo;
|
import com.ruoyi.other.api.domain.TStationEarn;
|
import com.ruoyi.other.api.domain.TStationInfo;
|
import com.ruoyi.other.mapper.TStationEarnMapper;
|
import com.ruoyi.other.query.StationEarnQuery;
|
import com.ruoyi.other.query.StationEarnOverviewQuery;
|
import com.ruoyi.other.query.StationEarnStatisticsQuery;
|
import com.ruoyi.other.service.TStationEarnService;
|
import com.ruoyi.other.util.KsolarUtils;
|
import com.ruoyi.other.vo.StationEarnStatisticsItemVO;
|
import com.ruoyi.other.vo.StationEarnStatisticsVO;
|
import com.ruoyi.other.vo.StationEarnOverviewVO;
|
import org.apache.commons.collections4.CollectionUtils;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.Instant;
|
import java.time.ZoneId;
|
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeParseException;
|
import java.util.*;
|
|
/**
|
* 电站发电量统计服务实现。
|
*/
|
@Service
|
public class TStationEarnServiceImpl
|
extends ServiceImpl<TStationEarnMapper, TStationEarn>
|
implements TStationEarnService {
|
|
@Autowired
|
public TStationEarnMapper mapper;
|
|
private static final BigDecimal PHOTOVOLTAIC_CARBON_REDUCTION_FACTOR = new BigDecimal("0.0001564");
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public boolean addStationEarn(TStationEarn stationEarn) {
|
validate(stationEarn);
|
|
stationEarn.setId(null);
|
stationEarn.setCreateTime(LocalDateTime.now());
|
stationEarn.setUpdateTime(LocalDateTime.now());
|
return save(stationEarn);
|
}
|
|
/**
|
* 将第三方电站收益数据转换为实体并批量保存。
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public boolean saveStationEarnList(JSONArray stationEarnList) {
|
if (stationEarnList == null || stationEarnList.isEmpty()) {
|
return false;
|
}
|
|
List<TStationEarn> earnList = buildStationEarnSyncList(stationEarnList, getExistingEarnByKey());
|
|
if (CollectionUtils.isNotEmpty(earnList)) {
|
List<List<TStationEarn>> earnAllList = Lists.partition(earnList, 500);
|
earnAllList.forEach(this::saveOrUpdateBatch);
|
}
|
return true;
|
}
|
|
/**
|
* 将华为 FusionSolar 电站日指标转换为收益记录并批量保存。
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public boolean saveStationHuaWeiList(JSONArray stationDayList) {
|
if (stationDayList == null || stationDayList.isEmpty()) {
|
return false;
|
}
|
Map<String, TStationEarn> existingByKey = getExistingEarnByKey();
|
Map<String, TStationEarn> recordsByKey = new LinkedHashMap<>();
|
for (Object item : stationDayList) {
|
if (item == null) {
|
continue;
|
}
|
JSONObject json = JSONObject.parseObject(item.toString());
|
String stationId = json.getString("stationCode");
|
LocalDate saveTime = parseHuaweiCollectTime(json.getString("collectTime"));
|
if (StringUtils.isBlank(stationId) || saveTime == null) {
|
continue;
|
}
|
JSONObject dataItemMap = json.getJSONObject("dataItemMap");
|
if (dataItemMap == null) {
|
dataItemMap = new JSONObject();
|
}
|
LocalDateTime now = LocalDateTime.now();
|
TStationEarn earn = new TStationEarn();
|
earn.setPlatformId(1L);
|
earn.setStationId(stationId);
|
earn.setStationName(json.getString("stationName"));
|
earn.setSaveTime(saveTime);
|
earn.setElecTotal(firstDecimal(dataItemMap, "PVYield", "inverterYield", "inverter_power"));
|
earn.setEarnTotal(parseBigDecimal(dataItemMap.getString("power_profit")));
|
earn.setDataSource(2);
|
earn.setCreateBy("");
|
earn.setUpdateBy("");
|
earn.setCreateTime(now);
|
earn.setUpdateTime(now);
|
earn.setDelFlag("0");
|
TStationEarn existing = existingByKey.get(buildEarnKey(earn));
|
if (existing != null) {
|
earn.setId(existing.getId());
|
earn.setCreateTime(null);
|
}
|
recordsByKey.put(buildEarnKey(earn), earn);
|
}
|
if (recordsByKey.isEmpty()) {
|
return false;
|
}
|
List<List<TStationEarn>> partitions = Lists.partition(new ArrayList<>(recordsByKey.values()), 500);
|
partitions.forEach(this::saveOrUpdateBatch);
|
return true;
|
}
|
|
private static LocalDate parseHuaweiCollectTime(String value) {
|
if (StringUtils.isBlank(value)) {
|
return null;
|
}
|
try {
|
return Instant.ofEpochMilli(Long.parseLong(value)).atZone(ZoneId.systemDefault()).toLocalDate();
|
} catch (NumberFormatException exception) {
|
return null;
|
}
|
}
|
|
private static BigDecimal firstDecimal(JSONObject data, String... keys) {
|
for (String key : keys) {
|
if (data.get(key) != null && StringUtils.isNotBlank(data.getString(key))) {
|
return parseBigDecimal(data.getString(key));
|
}
|
}
|
return BigDecimal.ZERO;
|
}
|
|
/**
|
* 查询发电量统计记录中的最大统计日期。
|
*
|
* @return 最大统计日期,无记录时返回 null
|
*/
|
@Override
|
public LocalDate getMaxSaveTime() {
|
TStationEarn latest = lambdaQuery()
|
.select(TStationEarn::getSaveTime)
|
.isNotNull(TStationEarn::getSaveTime)
|
.orderByDesc(TStationEarn::getSaveTime)
|
.last("LIMIT 1")
|
.one();
|
return latest == null ? null : latest.getSaveTime();
|
}
|
|
@Override
|
public PageInfo<TStationEarn> pageList(StationEarnQuery query) {
|
if (query == null) query = new StationEarnQuery();
|
PageInfo<TStationEarn> page = new PageInfo<>(query.getPageCurr(), query.getPageSize());
|
String viewMode = StringUtils.isBlank(query.getViewMode()) ? "station" : query.getViewMode().trim();
|
if ("summary".equalsIgnoreCase(viewMode)) {
|
page(page, buildSummaryQueryWrapper(query));
|
} else if ("station".equalsIgnoreCase(viewMode)) {
|
page(page, buildQueryWrapper(query));
|
} else {
|
throw new ServiceException("查看方式仅支持station、summary");
|
}
|
return page;
|
}
|
|
@Override
|
public List<TStationEarn> exportList(StationEarnQuery query) {
|
return list(buildQueryWrapper(query));
|
}
|
|
@Override
|
public StationEarnStatisticsVO statistics(StationEarnStatisticsQuery query) {
|
if (query == null) {
|
query = new StationEarnStatisticsQuery();
|
}
|
String dimension = StringUtils.isBlank(query.getDimension()) ? "day" : query.getDimension().toLowerCase();
|
LocalDate today = LocalDate.now();
|
LocalDate startDate = null;
|
LocalDate endDate = null;
|
if ("day".equals(dimension)) {
|
startDate = query.getStartSaveTime();
|
endDate = query.getEndSaveTime();
|
if (startDate == null || endDate == null) {
|
throw new ServiceException("按天统计时开始日期和结束日期不能为空");
|
}
|
if (startDate.isAfter(endDate)) {
|
throw new ServiceException("开始日期不能晚于结束日期");
|
}
|
} else if ("month".equals(dimension)) {
|
startDate = today.withDayOfYear(1);
|
endDate = today;
|
} else if (!"year".equals(dimension)) {
|
throw new ServiceException("统计维度仅支持day、month、year");
|
}
|
|
LambdaQueryWrapper<TStationEarn> wrapper = new LambdaQueryWrapper<TStationEarn>()
|
.select(TStationEarn::getSaveTime, TStationEarn::getElecTotal, TStationEarn::getEarnTotal)
|
.eq(query.getPlatformId() != null, TStationEarn::getPlatformId, query.getPlatformId())
|
.eq(StringUtils.isNotBlank(query.getStationId()), TStationEarn::getStationId, query.getStationId())
|
.ge(startDate != null, TStationEarn::getSaveTime, startDate)
|
.le(endDate != null, TStationEarn::getSaveTime, endDate)
|
.isNotNull(TStationEarn::getSaveTime)
|
.orderByAsc(TStationEarn::getSaveTime);
|
List<TStationEarn> records = list(wrapper);
|
|
Map<String, BigDecimal[]> grouped = createStatisticsGroups(dimension, startDate, endDate);
|
for (TStationEarn record : records) {
|
String label = statisticsLabel(dimension, record.getSaveTime());
|
BigDecimal[] values = grouped.computeIfAbsent(label,
|
key -> new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO});
|
values[0] = values[0].add(defaultZero(record.getElecTotal()));
|
values[1] = values[1].add(defaultZero(record.getEarnTotal()));
|
}
|
|
BigDecimal totalGeneration = BigDecimal.ZERO;
|
BigDecimal totalEarn = BigDecimal.ZERO;
|
List<StationEarnStatisticsItemVO> items = new ArrayList<>(grouped.size());
|
for (Map.Entry<String, BigDecimal[]> entry : grouped.entrySet()) {
|
totalGeneration = totalGeneration.add(entry.getValue()[0]);
|
totalEarn = totalEarn.add(entry.getValue()[1]);
|
items.add(new StationEarnStatisticsItemVO(entry.getKey(), entry.getValue()[0], entry.getValue()[1]));
|
}
|
StationEarnStatisticsVO result = new StationEarnStatisticsVO();
|
result.setDimension(dimension);
|
result.setTotalGeneration(totalGeneration);
|
result.setTotalEarn(totalEarn);
|
result.setTotalCarbonReduction(getAllCo2());
|
result.setItems(items);
|
return result;
|
}
|
|
public BigDecimal getAllCo2() {
|
BigDecimal co2List = BigDecimal.ZERO;
|
List<TStationInfo> infos = mapper.selectStationInfoAll();
|
if(CollectionUtils.isNotEmpty(infos)) {
|
for (TStationInfo info : infos) {
|
JSONObject stationEarnObj = KsolarUtils.getStationEarnDetail(info.getStationId(), KsolarUtils.token());
|
if (Objects.nonNull(stationEarnObj)) {
|
co2List = co2List.add(parseBigDecimal(stationEarnObj.getString("co2")));
|
}
|
}
|
}
|
return co2List;
|
}
|
|
@Override
|
public StationEarnOverviewVO generationOverview(StationEarnOverviewQuery query) {
|
if (query == null) {
|
query = new StationEarnOverviewQuery();
|
}
|
LocalDate today = LocalDate.now();
|
LocalDate yearStart = today.withDayOfYear(1);
|
LocalDate monthStart = today.withDayOfMonth(1);
|
List<TStationEarn> records = list(new LambdaQueryWrapper<TStationEarn>()
|
.select(TStationEarn::getSaveTime, TStationEarn::getElecTotal)
|
.eq(query.getPlatformId() != null, TStationEarn::getPlatformId, query.getPlatformId())
|
.eq(StringUtils.isNotBlank(query.getStationId()), TStationEarn::getStationId, query.getStationId())
|
.between(TStationEarn::getSaveTime, yearStart, today));
|
|
BigDecimal todayGeneration = BigDecimal.ZERO;
|
BigDecimal monthGeneration = BigDecimal.ZERO;
|
BigDecimal yearGeneration = BigDecimal.ZERO;
|
for (TStationEarn record : records) {
|
BigDecimal generation = defaultZero(record.getElecTotal());
|
yearGeneration = yearGeneration.add(generation);
|
if (!record.getSaveTime().isBefore(monthStart)) {
|
monthGeneration = monthGeneration.add(generation);
|
}
|
if (today.equals(record.getSaveTime())) {
|
todayGeneration = todayGeneration.add(generation);
|
}
|
}
|
|
StationEarnOverviewVO result = new StationEarnOverviewVO();
|
result.setTodayGeneration(todayGeneration);
|
result.setMonthGeneration(monthGeneration);
|
result.setYearGeneration(yearGeneration.divide(new BigDecimal("10000"), 4, RoundingMode.HALF_UP)
|
.stripTrailingZeros());
|
return result;
|
}
|
|
private Map<String, BigDecimal[]> createStatisticsGroups(String dimension, LocalDate startDate, LocalDate endDate) {
|
Map<String, BigDecimal[]> groups = new LinkedHashMap<>();
|
if ("day".equals(dimension)) {
|
for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
|
groups.put(date.toString(), new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO});
|
}
|
} else if ("month".equals(dimension)) {
|
for (LocalDate date = startDate.withDayOfMonth(1); !date.isAfter(endDate); date = date.plusMonths(1)) {
|
groups.put(date.getMonthValue() + "月", new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO});
|
}
|
}
|
return groups;
|
}
|
|
private String statisticsLabel(String dimension, LocalDate saveTime) {
|
if ("day".equals(dimension)) {
|
return saveTime.toString();
|
}
|
if ("month".equals(dimension)) {
|
return saveTime.getMonthValue() + "月";
|
}
|
return String.valueOf(saveTime.getYear());
|
}
|
|
private LambdaQueryWrapper<TStationEarn> buildQueryWrapper(StationEarnQuery query) {
|
return new LambdaQueryWrapper<TStationEarn>()
|
.eq(query.getPlatformId() != null, TStationEarn::getPlatformId, query.getPlatformId())
|
.eq(StringUtils.isNotBlank(query.getStationId()), TStationEarn::getStationId, query.getStationId())
|
.ge(query.getStartSaveTime() != null, TStationEarn::getSaveTime, query.getStartSaveTime())
|
.le(query.getEndSaveTime() != null, TStationEarn::getSaveTime, query.getEndSaveTime())
|
.orderByDesc(TStationEarn::getSaveTime)
|
.orderByDesc(TStationEarn::getCreateTime);
|
}
|
|
private QueryWrapper<TStationEarn> buildSummaryQueryWrapper(StationEarnQuery query) {
|
return new QueryWrapper<TStationEarn>()
|
.select("save_time",
|
"COALESCE(SUM(elec_total), 0) AS elec_total",
|
"COALESCE(SUM(earn_total), 0) AS earn_total")
|
.eq(query.getPlatformId() != null, "platform_id", query.getPlatformId())
|
.eq(StringUtils.isNotBlank(query.getStationId()), "station_id", query.getStationId())
|
.ge(query.getStartSaveTime() != null, "save_time", query.getStartSaveTime())
|
.le(query.getEndSaveTime() != null, "save_time", query.getEndSaveTime())
|
.isNotNull("save_time")
|
.groupBy("save_time")
|
.orderByDesc("save_time");
|
}
|
|
|
static List<TStationEarn> buildStationEarnSyncList(JSONArray stationEarnList,
|
Map<String, TStationEarn> existingByKey) {
|
Map<String, TStationEarn> recordsByKey = new LinkedHashMap<>();
|
for (Object item : stationEarnList) {
|
TStationEarn stationEarn = buildStationEarn(JSONObject.parseObject(item.toString()));
|
if (stationEarn == null) {
|
continue;
|
}
|
TStationEarn existing = existingByKey.get(buildEarnKey(stationEarn));
|
if (existing != null) {
|
stationEarn.setId(existing.getId());
|
}
|
recordsByKey.put(buildEarnKey(stationEarn), stationEarn);
|
}
|
return new ArrayList<>(recordsByKey.values());
|
}
|
|
private static TStationEarn buildStationEarn(JSONObject jsonObject) {
|
String stationId = jsonObject.getString("stationId");
|
LocalDate saveTime = parseSaveTime(jsonObject.getString("saveTime"));
|
if (StringUtils.isBlank(stationId) || saveTime == null) {
|
return null;
|
}
|
|
LocalDateTime now = LocalDateTime.now();
|
TStationEarn stationEarn = new TStationEarn();
|
stationEarn.setPlatformId(0L);
|
stationEarn.setStationId(stationId);
|
stationEarn.setStationName(jsonObject.getString("stationName"));
|
stationEarn.setSaveTime(saveTime);
|
stationEarn.setEarnTotal(parseBigDecimal(jsonObject.getString("earnTotal")));
|
stationEarn.setElecTotal(parseBigDecimal(jsonObject.getString("elecTotal")));
|
stationEarn.setDataSource(2);
|
stationEarn.setCreateBy("");
|
stationEarn.setUpdateBy("");
|
stationEarn.setCreateTime(now);
|
stationEarn.setUpdateTime(now);
|
stationEarn.setDelFlag("0");
|
return stationEarn;
|
}
|
|
private Map<String, TStationEarn> getExistingEarnByKey() {
|
List<TStationEarn> existingList = lambdaQuery()
|
.select(TStationEarn::getId, TStationEarn::getPlatformId, TStationEarn::getStationId, TStationEarn::getSaveTime)
|
.list();
|
Map<String, TStationEarn> existingByKey = new LinkedHashMap<>();
|
for (TStationEarn stationEarn : existingList) {
|
if (StringUtils.isNotBlank(stationEarn.getStationId()) && stationEarn.getSaveTime() != null) {
|
existingByKey.put(buildEarnKey(stationEarn), stationEarn);
|
}
|
}
|
return existingByKey;
|
}
|
|
private static String buildEarnKey(TStationEarn stationEarn) {
|
return stationEarn.getPlatformId() + "|" + stationEarn.getStationId() + "|" + stationEarn.getSaveTime();
|
}
|
|
private void validate(TStationEarn stationEarn) {
|
if (stationEarn == null) {
|
throw new ServiceException("发电量统计参数不能为空");
|
}
|
if (stationEarn.getSaveTime() == null) {
|
throw new ServiceException("统计时间不能为空");
|
}
|
}
|
|
|
|
private BigDecimal defaultZero(BigDecimal value) {
|
return value == null ? BigDecimal.ZERO : value;
|
}
|
|
static BigDecimal calculateCarbonReduction(BigDecimal generation) {
|
return generation.multiply(PHOTOVOLTAIC_CARBON_REDUCTION_FACTOR)
|
.setScale(2, RoundingMode.DOWN);
|
}
|
|
private static BigDecimal parseBigDecimal(String value) {
|
if (StringUtils.isBlank(value)) {
|
return BigDecimal.ZERO;
|
}
|
try {
|
return new BigDecimal(value);
|
} catch (NumberFormatException exception) {
|
return BigDecimal.ZERO;
|
}
|
}
|
|
private static LocalDate parseSaveTime(String value) {
|
if (StringUtils.isBlank(value)) {
|
return null;
|
}
|
DateTimeFormatter[] formatters = {
|
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
|
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
|
};
|
for (DateTimeFormatter formatter : formatters) {
|
try {
|
return LocalDateTime.parse(value, formatter).toLocalDate();
|
} catch (DateTimeParseException ignored) {
|
// 尝试下一个时间格式
|
}
|
}
|
try {
|
return LocalDate.parse(value, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
} catch (DateTimeParseException exception) {
|
return null;
|
}
|
}
|
}
|