|
@@ -5,13 +5,13 @@ import cn.afterturn.easypoi.excel.entity.ExportParams;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
+import com.bizmatics.common.core.bean.CommonPage;
|
|
|
import com.bizmatics.common.core.exception.BusinessException;
|
|
|
import com.bizmatics.common.core.util.*;
|
|
|
import com.bizmatics.common.mvc.base.AbstractCrudService;
|
|
|
import com.bizmatics.common.spring.util.GlobalUtils;
|
|
|
import com.bizmatics.common.spring.util.JsonUtils;
|
|
|
import com.bizmatics.model.*;
|
|
|
-import com.bizmatics.model.utils.TimeRangeParams;
|
|
|
import com.bizmatics.model.utils.TimeRangeUtils;
|
|
|
import com.bizmatics.model.vo.*;
|
|
|
import com.bizmatics.persistence.mapper.*;
|
|
@@ -22,25 +22,34 @@ import com.bizmatics.service.util.SecurityUtils;
|
|
|
import com.bizmatics.service.util.SiteFeeCacheService;
|
|
|
import com.bizmatics.service.vo.*;
|
|
|
import com.fasterxml.jackson.core.type.TypeReference;
|
|
|
+import com.github.pagehelper.PageHelper;
|
|
|
+import com.github.pagehelper.PageInfo;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
-import org.apache.poi.ss.usermodel.Workbook;
|
|
|
-import org.checkerframework.checker.units.qual.A;
|
|
|
+import org.apache.poi.ss.usermodel.*;
|
|
|
+import org.apache.poi.ss.util.CellRangeAddress;
|
|
|
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
import java.io.File;
|
|
|
import java.io.FileOutputStream;
|
|
|
import java.io.IOException;
|
|
|
+import java.io.OutputStream;
|
|
|
import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.text.DateFormat;
|
|
|
import java.text.DecimalFormat;
|
|
|
import java.text.SimpleDateFormat;
|
|
|
import java.time.*;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
import java.time.temporal.ChronoUnit;
|
|
|
import java.time.temporal.TemporalAdjusters;
|
|
|
import java.util.*;
|
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
+import java.util.function.Function;
|
|
|
import java.util.stream.Collectors;
|
|
|
import java.util.stream.Stream;
|
|
|
|
|
@@ -88,6 +97,8 @@ public class HtAnalogDataServiceImpl extends AbstractCrudService<HtAnalogDataMap
|
|
|
|
|
|
private static final BigDecimal ZERO = BigDecimal.ZERO;
|
|
|
|
|
|
+ private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
+
|
|
|
@Override
|
|
|
public HadCountVO selectCount() {
|
|
|
HadCountVO hadCountVO = new HadCountVO();
|
|
@@ -3028,5 +3039,587 @@ public class HtAnalogDataServiceImpl extends AbstractCrudService<HtAnalogDataMap
|
|
|
.reduce(ZERO, BigDecimal::add);
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public CommonPage<TimeSharingElectricityResponseVO> timeSharingElectricity(TimeSharingElectricityRequestVO request) {
|
|
|
+ /* ---------- 1. 参数兜底 ---------- */
|
|
|
+ Integer pageNum = request.getPageNum() == null ? 1 : request.getPageNum();
|
|
|
+ Integer pageSize = request.getPageSize() == null ? 20 : request.getPageSize();
|
|
|
+ if (request.getSiteId() == null) {
|
|
|
+ throw new BusinessException("站点ID不能为空");
|
|
|
+ }
|
|
|
+ if (CollectionUtils.isEmpty(request.getDeviceCodes())) {
|
|
|
+ throw new BusinessException("设备代码列表不能为空");
|
|
|
+ }
|
|
|
+ if (request.getStartTime() == null || request.getEndTime() == null) {
|
|
|
+ throw new BusinessException("起止时间不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ---------- 2. 分页查询 ---------- */
|
|
|
+ PageHelper.startPage(pageNum, pageSize);
|
|
|
+ LambdaQueryWrapper<SiteElectricityRecord> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(SiteElectricityRecord::getSiteId, request.getSiteId())
|
|
|
+ .in(SiteElectricityRecord::getDeviceCode, request.getDeviceCodes())
|
|
|
+ .between(SiteElectricityRecord::getDate, request.getStartTime(), request.getEndTime())
|
|
|
+ .orderByAsc(SiteElectricityRecord::getDate, SiteElectricityRecord::getDeviceCode);
|
|
|
+
|
|
|
+ List<SiteElectricityRecord> records = siteElectricityRecordMapper.selectList(wrapper);
|
|
|
+
|
|
|
+ // 3. 按 deviceCode 分组汇总
|
|
|
+ List<TimeSharingElectricityResponseVO> list = records.stream()
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ SiteElectricityRecord::getDeviceCode,
|
|
|
+ LinkedHashMap::new,
|
|
|
+ Collectors.reducing((a, b) -> {
|
|
|
+ // 累加电量
|
|
|
+ a.setSharpPeak(a.getSharpPeak().add(b.getSharpPeak()));
|
|
|
+ a.setPeak(a.getPeak().add(b.getPeak()));
|
|
|
+ a.setFlat(a.getFlat().add(b.getFlat()));
|
|
|
+ a.setValley(a.getValley().add(b.getValley()));
|
|
|
+ a.setTotalElectricity(a.getTotalElectricity().add(b.getTotalElectricity()));
|
|
|
+
|
|
|
+ // 累加电费
|
|
|
+ a.setSharpPeakCost(a.getSharpPeakCost().add(b.getSharpPeakCost()));
|
|
|
+ a.setPeakCost(a.getPeakCost().add(b.getPeakCost()));
|
|
|
+ a.setFlatCost(a.getFlatCost().add(b.getFlatCost()));
|
|
|
+ a.setValleyCost(a.getValleyCost().add(b.getValleyCost()));
|
|
|
+ a.setTotalCost(a.getTotalCost().add(b.getTotalCost()));
|
|
|
+ return a;
|
|
|
+ })
|
|
|
+ ))
|
|
|
+ .values().stream()
|
|
|
+ .map(opt -> opt.orElseThrow(() -> new RuntimeException("汇总数据不能为空")))
|
|
|
+ .map(r -> {
|
|
|
+ TimeSharingElectricityResponseVO vo = new TimeSharingElectricityResponseVO();
|
|
|
+ vo.setDeviceName(r.getDeviceCode());
|
|
|
+ vo.setSharpNum(r.getSharpPeak());
|
|
|
+ vo.setPeakNum(r.getPeak());
|
|
|
+ vo.setFlatNum(r.getFlat());
|
|
|
+ vo.setValleyNum(r.getValley());
|
|
|
+ vo.setTotalNum(r.getTotalElectricity());
|
|
|
+ vo.setSharpPrice(r.getSharpPeakCost());
|
|
|
+ vo.setPeakPrice(r.getPeakCost());
|
|
|
+ vo.setFlatPrice(r.getFlatCost());
|
|
|
+ vo.setValleyPrice(r.getValleyCost());
|
|
|
+ vo.setTotalPrice(r.getTotalCost());
|
|
|
+ vo.setTime(request.getStartTime().toString() +
|
|
|
+ " - " + request.getEndTime().toString());
|
|
|
+ return vo;
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ /* ---------- 4. 封装分页结果 ---------- */
|
|
|
+ PageInfo<TimeSharingElectricityResponseVO> pageInfo = new PageInfo<>(list);
|
|
|
+ return new CommonPage<>(list, pageInfo.getTotal(), pageNum, pageSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<ElectricityTrendResponseVO> trend(ElectricityTrendRequestVO request) {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (request.getSiteId() == null) {
|
|
|
+ throw new BusinessException("站点ID不能为空");
|
|
|
+ }
|
|
|
+ if (CollectionUtils.isEmpty(request.getDeviceCodes())) {
|
|
|
+ throw new BusinessException("设备代码列表不能为空");
|
|
|
+ }
|
|
|
+ if (request.getStartTime() == null || request.getEndTime() == null || request.getStartTime().isAfter(request.getEndTime())) {
|
|
|
+ throw new BusinessException("起止时间异常");
|
|
|
+ }
|
|
|
+ if (request.getType() == null || !Arrays.asList(1, 2).contains(request.getType())) {
|
|
|
+ throw new BusinessException("查询类型必须为1(电量)或2(电费)");
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDate startDate = request.getStartTime();
|
|
|
+ LocalDate endDate = request.getEndTime();
|
|
|
+
|
|
|
+ // 2. 查询数据
|
|
|
+ LambdaQueryWrapper<SiteElectricityRecord> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(SiteElectricityRecord::getSiteId, request.getSiteId())
|
|
|
+ .in(SiteElectricityRecord::getDeviceCode, request.getDeviceCodes())
|
|
|
+ .between(SiteElectricityRecord::getDate, startDate, endDate)
|
|
|
+ .orderByAsc(SiteElectricityRecord::getDeviceCode, SiteElectricityRecord::getDate);
|
|
|
+
|
|
|
+ List<SiteElectricityRecord> records = siteElectricityRecordMapper.selectList(wrapper);
|
|
|
+
|
|
|
+ // 3. 判断维度
|
|
|
+ long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
|
|
|
+ boolean monthMode = days > 31;
|
|
|
+
|
|
|
+ // 4. 选择字段函数(电量 or 电费)
|
|
|
+ boolean isElectricity = request.getType() == 1;
|
|
|
+ Function<SiteElectricityRecord, BigDecimal> sharpFn = isElectricity ?
|
|
|
+ SiteElectricityRecord::getSharpPeak : SiteElectricityRecord::getSharpPeakCost;
|
|
|
+ Function<SiteElectricityRecord, BigDecimal> peakFn = isElectricity ?
|
|
|
+ SiteElectricityRecord::getPeak : SiteElectricityRecord::getPeakCost;
|
|
|
+ Function<SiteElectricityRecord, BigDecimal> flatFn = isElectricity ?
|
|
|
+ SiteElectricityRecord::getFlat : SiteElectricityRecord::getFlatCost;
|
|
|
+ Function<SiteElectricityRecord, BigDecimal> valleyFn = isElectricity ?
|
|
|
+ SiteElectricityRecord::getValley : SiteElectricityRecord::getValleyCost;
|
|
|
+
|
|
|
+ records.forEach(r -> log.info("id={}, date={}, toString={}", r.getId(), r.getDate(), r.getDate().toString()));
|
|
|
+
|
|
|
+ // 5. 分组汇总
|
|
|
+ Map<String, Map<String, List<SiteElectricityRecord>>> group =
|
|
|
+ records.stream()
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ SiteElectricityRecord::getDeviceCode,
|
|
|
+ LinkedHashMap::new,
|
|
|
+ Collectors.groupingBy(r -> monthMode ?
|
|
|
+ YearMonth.from(r.getDate()).toString() : r.getDate().toString(),
|
|
|
+ LinkedHashMap::new,
|
|
|
+ Collectors.toList())
|
|
|
+ ));
|
|
|
+
|
|
|
+
|
|
|
+ // 6. 转 VO
|
|
|
+ List<ElectricityTrendResponseVO> result = new ArrayList<>();
|
|
|
+ group.forEach((deviceCode, timeMap) -> {
|
|
|
+ ElectricityTrendResponseVO dev = new ElectricityTrendResponseVO();
|
|
|
+ dev.setDeviceCode(deviceCode);
|
|
|
+
|
|
|
+ List<ElectricityTrendVO> trendList = new ArrayList<>();
|
|
|
+ timeMap.forEach((timeKey, list) -> {
|
|
|
+
|
|
|
+ if (timeKey.contains("031") || timeKey.contains("-0031")) {
|
|
|
+ throw new IllegalArgumentException("脏 timeKey 出现:" + timeKey);
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal sharp = list.stream().map(sharpFn).reduce(ZERO, BigDecimal::add);
|
|
|
+ BigDecimal peak = list.stream().map(peakFn).reduce(ZERO, BigDecimal::add);
|
|
|
+ BigDecimal flat = list.stream().map(flatFn).reduce(ZERO, BigDecimal::add);
|
|
|
+ BigDecimal valley = list.stream().map(valleyFn).reduce(ZERO, BigDecimal::add);
|
|
|
+
|
|
|
+ ElectricityTrendVO vo = new ElectricityTrendVO();
|
|
|
+ vo.setTime(timeKey);
|
|
|
+ vo.setSharp(sharp);
|
|
|
+ vo.setPeak(peak);
|
|
|
+ vo.setFlat(flat);
|
|
|
+ vo.setValley(valley);
|
|
|
+ trendList.add(vo);
|
|
|
+ });
|
|
|
+ dev.setData(trendList);
|
|
|
+ result.add(dev);
|
|
|
+ });
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void export(TimeSharingElectricityRequestVO request, HttpServletResponse response) throws IOException {
|
|
|
+ // 参数校验
|
|
|
+ if (request.getSiteId() == null) {
|
|
|
+ throw new BusinessException("站点ID不能为空");
|
|
|
+ }
|
|
|
+ if (CollectionUtils.isEmpty(request.getDeviceCodes())) {
|
|
|
+ throw new BusinessException("设备代码列表不能为空");
|
|
|
+ }
|
|
|
+ if (request.getStartTime() == null || request.getEndTime() == null) {
|
|
|
+ throw new BusinessException("起止时间不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 格式化时间(解决之前的时间格式问题)
|
|
|
+ String start = formatter.format(request.getStartTime());
|
|
|
+ String end = formatter.format(request.getEndTime());
|
|
|
+
|
|
|
+ // 查询数据
|
|
|
+ LambdaQueryWrapper<SiteElectricityRecord> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(SiteElectricityRecord::getSiteId, request.getSiteId())
|
|
|
+ .in(SiteElectricityRecord::getDeviceCode, request.getDeviceCodes())
|
|
|
+ .between(SiteElectricityRecord::getDate, request.getStartTime(), request.getEndTime())
|
|
|
+ .orderByAsc(SiteElectricityRecord::getDate, SiteElectricityRecord::getDeviceCode);
|
|
|
+
|
|
|
+ List<SiteElectricityRecord> records = siteElectricityRecordMapper.selectList(wrapper);
|
|
|
+ log.info("查询到的记录数: {}", records.size());
|
|
|
+ if (records.isEmpty()) {
|
|
|
+ throw new BusinessException("没有找到符合条件的数据");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按设备分组汇总数据
|
|
|
+ List<TimeSharingElectricityResponseVO> dataList = aggregateData(records, start, end);
|
|
|
+
|
|
|
+ // 处理分页
|
|
|
+ List<TimeSharingElectricityResponseVO> pageList = handlePagination(dataList, request);
|
|
|
+
|
|
|
+ // 准备导出信息
|
|
|
+ String deviceTypePrefix = request.getDeviceType() == 1 ? "支路" : "分项";
|
|
|
+ String sheetName = deviceTypePrefix + "分时用电";
|
|
|
+ String fileName = String.format("%s分时用电(%s-%s).xlsx", deviceTypePrefix, start, end);
|
|
|
+ String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
|
|
|
+
|
|
|
+ // 导出Excel
|
|
|
+ try (Workbook workbook = new XSSFWorkbook()) {
|
|
|
+ Sheet sheet = workbook.createSheet(sheetName);
|
|
|
+
|
|
|
+ // 创建样式
|
|
|
+ CellStyle titleStyle = createTitleStyle(workbook); // 新增标题样式
|
|
|
+ CellStyle headerStyle = createHeaderStyle(workbook);
|
|
|
+ CellStyle dataStyle = createDataStyle(workbook);
|
|
|
+ CellStyle percentageStyle = createPercentageStyle(workbook);
|
|
|
+
|
|
|
+ // 构建表格结构
|
|
|
+ int currentRow = 0;
|
|
|
+ currentRow = createTitleRow(sheet, titleStyle, sheetName, currentRow); // 使用标题样式
|
|
|
+ currentRow = createHeaderRows(sheet, headerStyle, currentRow, deviceTypePrefix);
|
|
|
+
|
|
|
+ // 设置列宽
|
|
|
+ setColumnWidths(sheet);
|
|
|
+
|
|
|
+ // 插入数据
|
|
|
+ currentRow = insertDataIntoSheet(sheet, pageList, dataStyle, percentageStyle, currentRow);
|
|
|
+
|
|
|
+ // 响应处理
|
|
|
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
|
+ response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
|
|
|
+
|
|
|
+ try (OutputStream outputStream = response.getOutputStream()) {
|
|
|
+ workbook.write(outputStream);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("导出文件失败", e);
|
|
|
+ throw new BusinessException("导出文件失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建标题行
|
|
|
+ */
|
|
|
+ private int createTitleRow(Sheet sheet, CellStyle titleStyle, String title, int rowIndex) {
|
|
|
+ Row titleRow = sheet.createRow(rowIndex++);
|
|
|
+ Cell titleCell = titleRow.createCell(0);
|
|
|
+ titleCell.setCellValue(title);
|
|
|
+ titleCell.setCellStyle(titleStyle);
|
|
|
+ // 合并标题行(0行0列到0行11列)
|
|
|
+ sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), 0, 11));
|
|
|
+ return rowIndex;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建表头行(包含单元格合并逻辑)
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 创建表头行(包含单元格合并逻辑,确保所有单元格应用带边框的样式)
|
|
|
+ */
|
|
|
+ private int createHeaderRows(Sheet sheet, CellStyle headerStyle, int rowIndex, String deviceTypePrefix) {
|
|
|
+ // 第一级表头(rowIndex行)
|
|
|
+ Row headerRow = sheet.createRow(rowIndex);
|
|
|
+
|
|
|
+ // 1. 支路名称 - 合并两行(rowIndex和rowIndex+1行,0列)
|
|
|
+ sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex + 1, 0, 0));
|
|
|
+ // 为合并区域的两个行(rowIndex 和 rowIndex+1)的第0列单元格设置样式
|
|
|
+ for (int r = rowIndex; r <= rowIndex + 1; r++) {
|
|
|
+ Row row = sheet.getRow(r);
|
|
|
+ if (row == null) {
|
|
|
+ row = sheet.createRow(r);
|
|
|
+ }
|
|
|
+ Cell cell = row.getCell(0);
|
|
|
+ if (cell == null) {
|
|
|
+ cell = row.createCell(0);
|
|
|
+ }
|
|
|
+ cell.setCellValue(deviceTypePrefix + "名称");
|
|
|
+ cell.setCellStyle(headerStyle);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 尖峰 - 合并两列(rowIndex行,1-2列)
|
|
|
+ sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex, 1, 2));
|
|
|
+ // 为合并区域的两个列(1 和 2)的单元格设置样式
|
|
|
+ for (int c = 1; c <= 2; c++) {
|
|
|
+ Cell cell = headerRow.getCell(c);
|
|
|
+ if (cell == null) {
|
|
|
+ cell = headerRow.createCell(c);
|
|
|
+ }
|
|
|
+ cell.setCellValue("尖峰");
|
|
|
+ cell.setCellStyle(headerStyle);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 峰 - 合并两列(rowIndex行,3-4列)
|
|
|
+ sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex, 3, 4));
|
|
|
+ for (int c = 3; c <= 4; c++) {
|
|
|
+ Cell cell = headerRow.getCell(c);
|
|
|
+ if (cell == null) {
|
|
|
+ cell = headerRow.createCell(c);
|
|
|
+ }
|
|
|
+ cell.setCellValue("峰");
|
|
|
+ cell.setCellStyle(headerStyle);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 平 - 合并两列(rowIndex行,5-6列)
|
|
|
+ sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex, 5, 6));
|
|
|
+ for (int c = 5; c <= 6; c++) {
|
|
|
+ Cell cell = headerRow.getCell(c);
|
|
|
+ if (cell == null) {
|
|
|
+ cell = headerRow.createCell(c);
|
|
|
+ }
|
|
|
+ cell.setCellValue("平");
|
|
|
+ cell.setCellStyle(headerStyle);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 谷 - 合并两列(rowIndex行,7-8列)
|
|
|
+ sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex, 7, 8));
|
|
|
+ for (int c = 7; c <= 8; c++) {
|
|
|
+ Cell cell = headerRow.getCell(c);
|
|
|
+ if (cell == null) {
|
|
|
+ cell = headerRow.createCell(c);
|
|
|
+ }
|
|
|
+ cell.setCellValue("谷");
|
|
|
+ cell.setCellStyle(headerStyle);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 总 - 合并两列(rowIndex行,9-10列)
|
|
|
+ sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex, 9, 10));
|
|
|
+ for (int c = 9; c <= 10; c++) {
|
|
|
+ Cell cell = headerRow.getCell(c);
|
|
|
+ if (cell == null) {
|
|
|
+ cell = headerRow.createCell(c);
|
|
|
+ }
|
|
|
+ cell.setCellValue("总");
|
|
|
+ cell.setCellStyle(headerStyle);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 谷电量占比 - 合并两行(rowIndex和rowIndex+1行,11列)
|
|
|
+ sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex + 1, 11, 11));
|
|
|
+ for (int r = rowIndex; r <= rowIndex + 1; r++) {
|
|
|
+ Row row = sheet.getRow(r);
|
|
|
+ if (row == null) {
|
|
|
+ row = sheet.createRow(r);
|
|
|
+ }
|
|
|
+ Cell cell = row.getCell(11);
|
|
|
+ if (cell == null) {
|
|
|
+ cell = row.createCell(11);
|
|
|
+ }
|
|
|
+ cell.setCellValue("谷电量占比");
|
|
|
+ cell.setCellStyle(headerStyle);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 第二级表头(rowIndex+1行)
|
|
|
+ rowIndex++;
|
|
|
+ Row subHeaderRow = sheet.createRow(rowIndex);
|
|
|
+ String[] subHeaders = {"", "电量(kWh)", "电费(元)", "电量(kWh)", "电费(元)",
|
|
|
+ "电量(kWh)", "电费(元)", "电量(kWh)", "电费(元)",
|
|
|
+ "电量(kWh)", "电费(元)", ""};
|
|
|
+ for (int i = 0; i < subHeaders.length; i++) {
|
|
|
+ Cell cell = subHeaderRow.createCell(i);
|
|
|
+ cell.setCellValue(subHeaders[i]);
|
|
|
+ cell.setCellStyle(headerStyle);
|
|
|
+ }
|
|
|
+
|
|
|
+ return rowIndex + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置列宽(增加标题行宽度)
|
|
|
+ */
|
|
|
+ private void setColumnWidths(Sheet sheet) {
|
|
|
+ // 为不同列设置更合理的宽度,第0列(支路名称)宽度增加
|
|
|
+ int[] columnWidths = {25, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 18};
|
|
|
+ for (int i = 0; i < columnWidths.length; i++) {
|
|
|
+ sheet.setColumnWidth(i, columnWidths[i] * 256);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建标题样式(带边框、宋体,24号)
|
|
|
+ private CellStyle createTitleStyle(Workbook workbook) {
|
|
|
+ CellStyle style = workbook.createCellStyle();
|
|
|
+ // 设置边框
|
|
|
+ style.setBorderTop(BorderStyle.THIN);
|
|
|
+ style.setBorderBottom(BorderStyle.THIN);
|
|
|
+ style.setBorderLeft(BorderStyle.THIN);
|
|
|
+ style.setBorderRight(BorderStyle.THIN);
|
|
|
+ style.setAlignment(HorizontalAlignment.CENTER);
|
|
|
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+
|
|
|
+ Font font = workbook.createFont();
|
|
|
+ font.setFontName("宋体");
|
|
|
+ font.setFontHeightInPoints((short) 24);
|
|
|
+ font.setBold(true);
|
|
|
+ style.setFont(font);
|
|
|
+ return style;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建表头样式(带边框、宋体,12号,加粗)
|
|
|
+ private CellStyle createHeaderStyle(Workbook workbook) {
|
|
|
+ CellStyle style = workbook.createCellStyle();
|
|
|
+ // 设置边框
|
|
|
+ style.setBorderTop(BorderStyle.THIN);
|
|
|
+ style.setBorderBottom(BorderStyle.THIN);
|
|
|
+ style.setBorderLeft(BorderStyle.THIN);
|
|
|
+ style.setBorderRight(BorderStyle.THIN);
|
|
|
+ style.setAlignment(HorizontalAlignment.CENTER);
|
|
|
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+
|
|
|
+ Font font = workbook.createFont();
|
|
|
+ font.setFontName("宋体");
|
|
|
+ font.setFontHeightInPoints((short) 12);
|
|
|
+ font.setBold(true);
|
|
|
+ style.setFont(font);
|
|
|
+ return style;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建数据行样式(带边框、宋体,12号)
|
|
|
+ private CellStyle createDataStyle(Workbook workbook) {
|
|
|
+ CellStyle style = workbook.createCellStyle();
|
|
|
+ // 设置边框
|
|
|
+ style.setBorderTop(BorderStyle.THIN);
|
|
|
+ style.setBorderBottom(BorderStyle.THIN);
|
|
|
+ style.setBorderLeft(BorderStyle.THIN);
|
|
|
+ style.setBorderRight(BorderStyle.THIN);
|
|
|
+ style.setAlignment(HorizontalAlignment.CENTER);
|
|
|
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+
|
|
|
+ Font font = workbook.createFont();
|
|
|
+ font.setFontName("宋体");
|
|
|
+ font.setFontHeightInPoints((short) 12);
|
|
|
+ style.setFont(font);
|
|
|
+ return style;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建百分比样式(带边框、宋体,12号,百分比格式)
|
|
|
+ private CellStyle createPercentageStyle(Workbook workbook) {
|
|
|
+ CellStyle style = createDataStyle(workbook);
|
|
|
+ DataFormat format = workbook.createDataFormat();
|
|
|
+ style.setDataFormat(format.getFormat("0.00%"));
|
|
|
+ return style;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 汇总数据
|
|
|
+ */
|
|
|
+ private List<TimeSharingElectricityResponseVO> aggregateData(List<SiteElectricityRecord> records, String start, String end) {
|
|
|
+ return records.stream()
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ SiteElectricityRecord::getDeviceCode,
|
|
|
+ LinkedHashMap::new,
|
|
|
+ Collectors.reducing((a, b) -> {
|
|
|
+ // 累加电量
|
|
|
+ a.setSharpPeak(a.getSharpPeak().add(b.getSharpPeak()));
|
|
|
+ a.setPeak(a.getPeak().add(b.getPeak()));
|
|
|
+ a.setFlat(a.getFlat().add(b.getFlat()));
|
|
|
+ a.setValley(a.getValley().add(b.getValley()));
|
|
|
+ a.setTotalElectricity(a.getTotalElectricity().add(b.getTotalElectricity()));
|
|
|
+
|
|
|
+ // 累加电费
|
|
|
+ a.setSharpPeakCost(a.getSharpPeakCost().add(b.getSharpPeakCost()));
|
|
|
+ a.setPeakCost(a.getPeakCost().add(b.getPeakCost()));
|
|
|
+ a.setFlatCost(a.getFlatCost().add(b.getFlatCost()));
|
|
|
+ a.setValleyCost(a.getValleyCost().add(b.getValleyCost()));
|
|
|
+ a.setTotalCost(a.getTotalCost().add(b.getTotalCost()));
|
|
|
+ return a;
|
|
|
+ })
|
|
|
+ ))
|
|
|
+ .values().stream()
|
|
|
+ .map(opt -> opt.orElseThrow(() -> new RuntimeException("汇总数据不能为空")))
|
|
|
+ .map(r -> {
|
|
|
+ TimeSharingElectricityResponseVO vo = new TimeSharingElectricityResponseVO();
|
|
|
+ vo.setDeviceName(r.getDeviceCode());
|
|
|
+ vo.setSharpNum(r.getSharpPeak());
|
|
|
+ vo.setPeakNum(r.getPeak());
|
|
|
+ vo.setFlatNum(r.getFlat());
|
|
|
+ vo.setValleyNum(r.getValley());
|
|
|
+ vo.setTotalNum(r.getTotalElectricity());
|
|
|
+ vo.setSharpPrice(r.getSharpPeakCost());
|
|
|
+ vo.setPeakPrice(r.getPeakCost());
|
|
|
+ vo.setFlatPrice(r.getFlatCost());
|
|
|
+ vo.setValleyPrice(r.getValleyCost());
|
|
|
+ vo.setTotalPrice(r.getTotalCost());
|
|
|
+ vo.setTime(start + " - " + end);
|
|
|
+ return vo;
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理分页
|
|
|
+ */
|
|
|
+ private List<TimeSharingElectricityResponseVO> handlePagination(List<TimeSharingElectricityResponseVO> dataList,
|
|
|
+ TimeSharingElectricityRequestVO request) {
|
|
|
+ int total = dataList.size();
|
|
|
+ int startIndex = (request.getPageNum() - 1) * request.getPageSize();
|
|
|
+ int endIndex = Math.min(startIndex + request.getPageSize(), total);
|
|
|
+
|
|
|
+ log.info("总记录数: {}, 起始索引: {}, 结束索引: {}", total, startIndex, endIndex);
|
|
|
+
|
|
|
+ if (startIndex > total) {
|
|
|
+ throw new BusinessException("起始索引无效");
|
|
|
+ }
|
|
|
+
|
|
|
+ return dataList.subList(startIndex, endIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 插入数据到工作表
|
|
|
+ */
|
|
|
+ private int insertDataIntoSheet(Sheet sheet, List<TimeSharingElectricityResponseVO> dataList,
|
|
|
+ CellStyle dataStyle, CellStyle percentageStyle, int startRow) {
|
|
|
+ int currentRow = startRow;
|
|
|
+
|
|
|
+ for (TimeSharingElectricityResponseVO vo : dataList) {
|
|
|
+ Row row = sheet.createRow(currentRow++);
|
|
|
+ int cellIndex = 0;
|
|
|
+
|
|
|
+ // 支路名称
|
|
|
+ createCell(row, cellIndex++, vo.getDeviceName(), dataStyle);
|
|
|
+
|
|
|
+ // 尖峰数据
|
|
|
+ createCell(row, cellIndex++, vo.getSharpNum(), dataStyle);
|
|
|
+ createCell(row, cellIndex++, vo.getSharpPrice(), dataStyle);
|
|
|
+
|
|
|
+ // 峰数据
|
|
|
+ createCell(row, cellIndex++, vo.getPeakNum(), dataStyle);
|
|
|
+ createCell(row, cellIndex++, vo.getPeakPrice(), dataStyle);
|
|
|
+
|
|
|
+ // 平数据
|
|
|
+ createCell(row, cellIndex++, vo.getFlatNum(), dataStyle);
|
|
|
+ createCell(row, cellIndex++, vo.getFlatPrice(), dataStyle);
|
|
|
+
|
|
|
+ // 谷数据
|
|
|
+ createCell(row, cellIndex++, vo.getValleyNum(), dataStyle);
|
|
|
+ createCell(row, cellIndex++, vo.getValleyPrice(), dataStyle);
|
|
|
+
|
|
|
+ // 总数据
|
|
|
+ createCell(row, cellIndex++, vo.getTotalNum(), dataStyle);
|
|
|
+ createCell(row, cellIndex++, vo.getTotalPrice(), dataStyle);
|
|
|
+
|
|
|
+ // 谷电量占比(处理除零情况)
|
|
|
+ BigDecimal valleyRatio = calculateValleyRatio(vo);
|
|
|
+ createCell(row, cellIndex++, valleyRatio, percentageStyle);
|
|
|
+ }
|
|
|
+
|
|
|
+ return currentRow;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建单元格并设置值和样式
|
|
|
+ */
|
|
|
+ private void createCell(Row row, int cellIndex, Object value, CellStyle style) {
|
|
|
+ Cell cell = row.createCell(cellIndex);
|
|
|
+ cell.setCellStyle(style);
|
|
|
+
|
|
|
+ if (value == null) {
|
|
|
+ cell.setCellValue("");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据值类型设置单元格内容
|
|
|
+ if (value instanceof BigDecimal) {
|
|
|
+ cell.setCellValue(((BigDecimal) value).doubleValue());
|
|
|
+ } else if (value instanceof Number) {
|
|
|
+ cell.setCellValue(((Number) value).doubleValue());
|
|
|
+ } else {
|
|
|
+ cell.setCellValue(value.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算谷电量占比(处理除零情况)
|
|
|
+ */
|
|
|
+ private BigDecimal calculateValleyRatio(TimeSharingElectricityResponseVO vo) {
|
|
|
+ try {
|
|
|
+ if (vo.getTotalNum() == null || vo.getTotalNum().compareTo(BigDecimal.ZERO) == 0) {
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ return vo.getValleyNum().divide(vo.getTotalNum(), 4, RoundingMode.HALF_UP);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("计算谷电量占比失败", e);
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
}
|