| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.config; |
| | | |
| | | import lombok.Data; |
| | | import org.springframework.boot.context.properties.ConfigurationProperties; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** |
| | | * 天å¯çµè¡¨å¹³å°é
ç½® |
| | | */ |
| | | @Data |
| | | @Component |
| | | @ConfigurationProperties(prefix = "tqdianbiao") |
| | | public class TqdianbiaoConfig { |
| | | |
| | | /** API åºç¡å°å */ |
| | | private String baseUrl = "https://168.tqdianbiao.com"; |
| | | |
| | | /** è®¤è¯ token */ |
| | | private String auth = "b6229401590539d5def7f2bf897b33de"; |
| | | |
| | | /** æ¯å¦å¿½ç¥äºæå¨åæ¯ï¼0-å¦ï¼1-æ¯ */ |
| | | private Integer ignoreRadio = 1; |
| | | |
| | | /** 忥é
ç½® */ |
| | | private Sync sync = new Sync(); |
| | | |
| | | @Data |
| | | public static class Sync { |
| | | /** æ¯å¦å¯ç¨å®æ¶åæ¥ */ |
| | | private Boolean enabled = true; |
| | | /** å°æ¶åæ¥åççªå£ï¼å®æ´å°æ¶æ°ï¼ä¸å«å½åå°æ¶ï¼ */ |
| | | private Integer hourWindow = 1; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.controller; |
| | | |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.controller.BaseController; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import com.ruoyi.http.service.StatisticEleService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | @RestController |
| | | @Tag(name = "è½èçµè¡¨ç»è®¡") |
| | | @RequestMapping("/statisticEle") |
| | | @RequiredArgsConstructor |
| | | public class StatisticEleController extends BaseController { |
| | | |
| | | private final StatisticEleService statisticEleService; |
| | | |
| | | @GetMapping("/list") |
| | | @Operation(summary = "è½èæ°æ®-å表æ¥è¯¢(æ¬å°åº)") |
| | | @Log(title = "è½èæ°æ®-å表æ¥è¯¢", businessType = BusinessType.OTHER) |
| | | public AjaxResult list( |
| | | @RequestParam(defaultValue = "hour") String dimension, |
| | | @RequestParam String startTime, |
| | | @RequestParam String endTime, |
| | | @RequestParam(defaultValue = "1") Integer ignoreRadio) { |
| | | return AjaxResult.success(statisticEleService.listRecords(dimension, startTime, endTime, ignoreRadio)); |
| | | } |
| | | |
| | | @GetMapping("/summary") |
| | | @Operation(summary = "è½èæ°æ®-æ±æ»ç»è®¡(æ¬å°åº)") |
| | | @Log(title = "è½èæ°æ®-æ±æ»ç»è®¡", businessType = BusinessType.OTHER) |
| | | public AjaxResult summary( |
| | | @RequestParam(defaultValue = "hour") String dimension, |
| | | @RequestParam String startTime, |
| | | @RequestParam String endTime) { |
| | | return AjaxResult.success(statisticEleService.getSummary(dimension, startTime, endTime)); |
| | | } |
| | | |
| | | @GetMapping("/analytics") |
| | | @Operation(summary = "è½èæ°æ®-综ååæ") |
| | | public AjaxResult analytics( |
| | | @RequestParam(defaultValue = "day") String dimension, |
| | | @RequestParam String startTime, |
| | | @RequestParam String endTime, |
| | | @RequestParam(required = false) String trendGranularity) { |
| | | return AjaxResult.success(statisticEleService.getAnalytics(dimension, startTime, endTime, trendGranularity)); |
| | | } |
| | | |
| | | @GetMapping("/yesterday") |
| | | @Operation(summary = "æ¨æ¥ç¨çµéæ±æ»") |
| | | public AjaxResult yesterday() { |
| | | return AjaxResult.success(statisticEleService.getYesterdaySummary()); |
| | | } |
| | | |
| | | @GetMapping("/syncStatus") |
| | | @Operation(summary = "è½èæ°æ®-åæ¥ç¶æ") |
| | | public AjaxResult syncStatus() { |
| | | return AjaxResult.success(statisticEleService.getSyncStatus()); |
| | | } |
| | | |
| | | @GetMapping("/raw") |
| | | @Operation(summary = "è½èæ°æ®-åå§æ¥å£(è°è¯ç¨ï¼æ
ç¨)") |
| | | @Log(title = "è½èæ°æ®-åå§æ¥å£", businessType = BusinessType.OTHER) |
| | | public AjaxResult raw( |
| | | @RequestParam(defaultValue = "hour") String dimension, |
| | | @RequestParam String startTime, |
| | | @RequestParam String endTime) { |
| | | return AjaxResult.success(statisticEleService.fetchRawData(dimension, startTime, endTime)); |
| | | } |
| | | |
| | | @PostMapping("/export") |
| | | @Operation(summary = "è½èæ°æ®-导åº") |
| | | @Log(title = "è½èæ°æ®-导åº", businessType = BusinessType.EXPORT) |
| | | public void export( |
| | | @RequestParam(defaultValue = "hour") String dimension, |
| | | @RequestParam String startTime, |
| | | @RequestParam String endTime, |
| | | HttpServletResponse response) { |
| | | statisticEleService.exportRecords(dimension, startTime, endTime, response); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.controller; |
| | | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.controller.BaseController; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import com.ruoyi.http.pojo.TqdianbiaoCollector; |
| | | import com.ruoyi.http.service.TqdianbiaoCollectorManageService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.DeleteMapping; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.util.List; |
| | | |
| | | @RestController |
| | | @Tag(name = "éé卿¡£æ¡") |
| | | @RequestMapping("/tqdianbiao/collector") |
| | | @RequiredArgsConstructor |
| | | public class TqdianbiaoCollectorController extends BaseController { |
| | | |
| | | private final TqdianbiaoCollectorManageService collectorManageService; |
| | | |
| | | @GetMapping("/listPage") |
| | | @Operation(summary = "éé卿¡£æ¡-å页æ¥è¯¢") |
| | | public AjaxResult listPage(Page page, TqdianbiaoCollector query) { |
| | | return AjaxResult.success(collectorManageService.listPage(page, query)); |
| | | } |
| | | |
| | | @GetMapping("/listAll") |
| | | @Operation(summary = "éé卿¡£æ¡-å
¨é¨å表") |
| | | public AjaxResult listAll() { |
| | | return AjaxResult.success(collectorManageService.list()); |
| | | } |
| | | |
| | | @PostMapping("/add") |
| | | @Log(title = "éé卿¡£æ¡-æ°å¢", businessType = BusinessType.INSERT) |
| | | public AjaxResult add(@RequestBody TqdianbiaoCollector collector) { |
| | | return collectorManageService.addCollector(collector) ? AjaxResult.success() : AjaxResult.error(); |
| | | } |
| | | |
| | | @PostMapping("/update") |
| | | @Log(title = "éé卿¡£æ¡-ä¿®æ¹", businessType = BusinessType.UPDATE) |
| | | public AjaxResult update(@RequestBody TqdianbiaoCollector collector) { |
| | | return collectorManageService.updateCollector(collector) ? AjaxResult.success() : AjaxResult.error(); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Log(title = "éé卿¡£æ¡-å é¤", businessType = BusinessType.DELETE) |
| | | public AjaxResult delete(@RequestBody List<Long> ids) { |
| | | return collectorManageService.deleteByIds(ids) ? AjaxResult.success() : AjaxResult.error(); |
| | | } |
| | | |
| | | @PostMapping("/sync") |
| | | @Log(title = "éé卿¡£æ¡-è¿ç¨åæ¥", businessType = BusinessType.OTHER) |
| | | public AjaxResult sync() { |
| | | int count = collectorManageService.syncFromRemote(); |
| | | return AjaxResult.success("忥æåï¼å
±åæ¥ " + count + " æ¡"); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.controller; |
| | | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.controller.BaseController; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import com.ruoyi.http.pojo.TqdianbiaoEleRecord; |
| | | import com.ruoyi.http.service.TqdianbiaoEleRecordManageService; |
| | | import com.ruoyi.http.service.TqdianbiaoEleSyncService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.DeleteMapping; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @RestController |
| | | @Tag(name = "çµéè®°å½ç®¡ç") |
| | | @RequestMapping("/tqdianbiao/eleRecord") |
| | | @RequiredArgsConstructor |
| | | public class TqdianbiaoEleRecordController extends BaseController { |
| | | |
| | | private static final DateTimeFormatter HOUR_FMT = DateTimeFormatter.ofPattern("yyyyMMddHH"); |
| | | |
| | | private final TqdianbiaoEleRecordManageService eleRecordManageService; |
| | | private final TqdianbiaoEleSyncService eleSyncService; |
| | | |
| | | @GetMapping("/listPage") |
| | | @Operation(summary = "çµéè®°å½-å页æ¥è¯¢") |
| | | public AjaxResult listPage(Page page, TqdianbiaoEleRecord query) { |
| | | return AjaxResult.success(eleRecordManageService.listPage(page, query)); |
| | | } |
| | | |
| | | @PostMapping("/add") |
| | | @Log(title = "çµéè®°å½-æ°å¢", businessType = BusinessType.INSERT) |
| | | public AjaxResult add(@RequestBody TqdianbiaoEleRecord record) { |
| | | return eleRecordManageService.addRecord(record) ? AjaxResult.success() : AjaxResult.error(); |
| | | } |
| | | |
| | | @PostMapping("/update") |
| | | @Log(title = "çµéè®°å½-ä¿®æ¹", businessType = BusinessType.UPDATE) |
| | | public AjaxResult update(@RequestBody TqdianbiaoEleRecord record) { |
| | | return eleRecordManageService.updateRecord(record) ? AjaxResult.success() : AjaxResult.error(); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Log(title = "çµéè®°å½-å é¤", businessType = BusinessType.DELETE) |
| | | public AjaxResult delete(@RequestBody List<Long> ids) { |
| | | return eleRecordManageService.deleteByIds(ids) ? AjaxResult.success() : AjaxResult.error(); |
| | | } |
| | | |
| | | @GetMapping("/prevReading") |
| | | @Operation(summary = "è·å䏿¬¡çµé读æ°") |
| | | public AjaxResult prevReading(Long meterId, String timeKey) { |
| | | return AjaxResult.success(eleRecordManageService.getPrevReading(meterId, timeKey)); |
| | | } |
| | | |
| | | @PostMapping("/syncRecentOnce") |
| | | @Log(title = "çµéè®°å½-è¿ä¸å¤©è¡¥æ°", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "忥åä¸å¤©è³å½åå°æ¶çµéï¼ä¸æ¬¡æ§è¡¥æ°ï¼") |
| | | public AjaxResult syncRecentOnce() { |
| | | LocalDateTime start = LocalDate.now().minusDays(3).atStartOfDay(); |
| | | LocalDateTime end = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0); |
| | | int count = eleSyncService.syncLast3DaysHourData(); |
| | | Map<String, Object> data = new HashMap<>(); |
| | | data.put("count", count); |
| | | data.put("startTime", start.format(HOUR_FMT)); |
| | | data.put("endTime", end.format(HOUR_FMT)); |
| | | return AjaxResult.success("忥宿ï¼" + data.get("startTime") + " ~ " + data.get("endTime") + " å
± " + count + " æ¡", data); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.controller; |
| | | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.controller.BaseController; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import com.ruoyi.http.pojo.TqdianbiaoMeter; |
| | | import com.ruoyi.http.service.TqdianbiaoMeterManageService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.DeleteMapping; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.util.List; |
| | | |
| | | @RestController |
| | | @Tag(name = "çµè¡¨æ¡£æ¡") |
| | | @RequestMapping("/tqdianbiao/meter") |
| | | @RequiredArgsConstructor |
| | | public class TqdianbiaoMeterController extends BaseController { |
| | | |
| | | private final TqdianbiaoMeterManageService meterManageService; |
| | | |
| | | @GetMapping("/listPage") |
| | | @Operation(summary = "çµè¡¨æ¡£æ¡-å页æ¥è¯¢") |
| | | public AjaxResult listPage(Page page, TqdianbiaoMeter query) { |
| | | return AjaxResult.success(meterManageService.listPage(page, query)); |
| | | } |
| | | |
| | | @GetMapping("/listAll") |
| | | @Operation(summary = "çµè¡¨æ¡£æ¡-å
¨é¨å表") |
| | | public AjaxResult listAll() { |
| | | return AjaxResult.success(meterManageService.list()); |
| | | } |
| | | |
| | | @PostMapping("/add") |
| | | @Log(title = "çµè¡¨æ¡£æ¡-æ°å¢", businessType = BusinessType.INSERT) |
| | | public AjaxResult add(@RequestBody TqdianbiaoMeter meter) { |
| | | return meterManageService.addMeter(meter) ? AjaxResult.success() : AjaxResult.error(); |
| | | } |
| | | |
| | | @PostMapping("/update") |
| | | @Log(title = "çµè¡¨æ¡£æ¡-ä¿®æ¹", businessType = BusinessType.UPDATE) |
| | | public AjaxResult update(@RequestBody TqdianbiaoMeter meter) { |
| | | return meterManageService.updateMeter(meter) ? AjaxResult.success() : AjaxResult.error(); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Log(title = "çµè¡¨æ¡£æ¡-å é¤", businessType = BusinessType.DELETE) |
| | | public AjaxResult delete(@RequestBody List<Long> ids) { |
| | | return meterManageService.deleteByIds(ids) ? AjaxResult.success() : AjaxResult.error(); |
| | | } |
| | | |
| | | @PostMapping("/sync") |
| | | @Log(title = "çµè¡¨æ¡£æ¡-è¿ç¨åæ¥", businessType = BusinessType.OTHER) |
| | | public AjaxResult sync() { |
| | | int count = meterManageService.syncFromRemote(); |
| | | return AjaxResult.success("忥æåï¼å
±åæ¥ " + count + " æ¡"); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.http.pojo.TqdianbiaoCollector; |
| | | |
| | | public interface TqdianbiaoCollectorMapper extends BaseMapper<TqdianbiaoCollector> { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.http.pojo.TqdianbiaoEleRecord; |
| | | import com.ruoyi.http.vo.StatisticEleRecordVo; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | import java.util.List; |
| | | |
| | | public interface TqdianbiaoEleRecordMapper extends BaseMapper<TqdianbiaoEleRecord> { |
| | | |
| | | List<StatisticEleRecordVo> selectRecordList( |
| | | @Param("dimensions") List<String> dimensions, |
| | | @Param("startTime") String startTime, |
| | | @Param("endTime") String endTime); |
| | | |
| | | TqdianbiaoEleRecord selectPrevReading( |
| | | @Param("meterId") Long meterId, |
| | | @Param("timeKey") String timeKey); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.http.pojo.TqdianbiaoMeter; |
| | | |
| | | public interface TqdianbiaoMeterMapper extends BaseMapper<TqdianbiaoMeter> { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.http.pojo.TqdianbiaoSyncLog; |
| | | |
| | | public interface TqdianbiaoSyncLogMapper extends BaseMapper<TqdianbiaoSyncLog> { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.pojo; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @TableName("tqdianbiao_collector") |
| | | public class TqdianbiaoCollector { |
| | | |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | private String collectorId; |
| | | |
| | | private String collectorNo; |
| | | |
| | | private Boolean online; |
| | | |
| | | private Integer csq; |
| | | |
| | | private String connectTime; |
| | | |
| | | private String disconnectTime; |
| | | |
| | | private String description; |
| | | |
| | | private LocalDateTime syncTime; |
| | | |
| | | private LocalDateTime createTime; |
| | | |
| | | private LocalDateTime updateTime; |
| | | |
| | | @TableField(exist = false) |
| | | private String keyword; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.pojo; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @TableName("tqdianbiao_ele_record") |
| | | public class TqdianbiaoEleRecord { |
| | | |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | private Long meterId; |
| | | |
| | | /** çµè¡¨åç§°(åä½) */ |
| | | private String meterName; |
| | | |
| | | private String dimension; |
| | | |
| | | /** æè¡¨æ¹å¼ sync/manual */ |
| | | private String readingMethod; |
| | | |
| | | private String timeKey; |
| | | |
| | | private String startTime; |
| | | |
| | | private String endTime; |
| | | |
| | | private BigDecimal totalConsumption; |
| | | |
| | | private BigDecimal sharpConsumption; |
| | | |
| | | private BigDecimal peakConsumption; |
| | | |
| | | private BigDecimal flatConsumption; |
| | | |
| | | private BigDecimal valleyConsumption; |
| | | |
| | | private BigDecimal deepValleyConsumption; |
| | | |
| | | private String startReading; |
| | | |
| | | private String endReading; |
| | | |
| | | /** 䏿¬¡çµé(æ»è¯»æ°) */ |
| | | private BigDecimal prevReading; |
| | | |
| | | /** æ¬æ¬¡çµé(æ»è¯»æ°) */ |
| | | private BigDecimal currReading; |
| | | |
| | | private Integer ratio; |
| | | |
| | | private LocalDateTime syncTime; |
| | | |
| | | private LocalDateTime createTime; |
| | | |
| | | private LocalDateTime updateTime; |
| | | |
| | | @TableField(exist = false) |
| | | private String startTimeKey; |
| | | |
| | | @TableField(exist = false) |
| | | private String endTimeKey; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.pojo; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @TableName("tqdianbiao_meter") |
| | | public class TqdianbiaoMeter { |
| | | |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | private Long meterId; |
| | | |
| | | /** çµè¡¨åç§° */ |
| | | private String meterName; |
| | | |
| | | private String collectorId; |
| | | |
| | | private String collectorNo; |
| | | |
| | | private String address; |
| | | |
| | | private Integer rate; |
| | | |
| | | private String relayState; |
| | | |
| | | private String meterType; |
| | | |
| | | private Integer csq; |
| | | |
| | | private String description; |
| | | |
| | | /** æ¥æº sync-å¹³å°åæ¥ manual-æå¨æ·»å */ |
| | | private String source; |
| | | |
| | | private LocalDateTime syncTime; |
| | | |
| | | private LocalDateTime createTime; |
| | | |
| | | private LocalDateTime updateTime; |
| | | |
| | | @TableField(exist = false) |
| | | private String keyword; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.pojo; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @TableName("tqdianbiao_sync_log") |
| | | public class TqdianbiaoSyncLog { |
| | | |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | private String syncType; |
| | | |
| | | private String windowStart; |
| | | |
| | | private String windowEnd; |
| | | |
| | | private String status; |
| | | |
| | | private Integer recordCount; |
| | | |
| | | private Integer apiCallCount; |
| | | |
| | | private String errorMsg; |
| | | |
| | | private LocalDateTime createTime; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service; |
| | | |
| | | import com.ruoyi.http.vo.StatisticEleAnalyticsVo; |
| | | import com.ruoyi.http.vo.StatisticEleRecordVo; |
| | | import com.ruoyi.http.vo.StatisticEleSummaryVo; |
| | | import com.ruoyi.http.vo.StatisticEleSyncStatusVo; |
| | | |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 天å¯çµè¡¨ç»è®¡æ¥å£ |
| | | */ |
| | | public interface StatisticEleService { |
| | | |
| | | String fetchRawData(String dimension, String startTime, String endTime); |
| | | |
| | | List<StatisticEleRecordVo> listRecords(String dimension, String startTime, String endTime, Integer ignoreRadio); |
| | | |
| | | StatisticEleSummaryVo getSummary(String dimension, String startTime, String endTime); |
| | | |
| | | StatisticEleAnalyticsVo getAnalytics(String dimension, String startTime, String endTime, String trendGranularity); |
| | | |
| | | StatisticEleSummaryVo getYesterdaySummary(); |
| | | |
| | | void exportRecords(String dimension, String startTime, String endTime, HttpServletResponse response); |
| | | |
| | | StatisticEleSyncStatusVo getSyncStatus(); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.http.pojo.TqdianbiaoCollector; |
| | | |
| | | import java.util.List; |
| | | |
| | | public interface TqdianbiaoCollectorManageService extends IService<TqdianbiaoCollector> { |
| | | |
| | | IPage<TqdianbiaoCollector> listPage(Page page, TqdianbiaoCollector query); |
| | | |
| | | boolean addCollector(TqdianbiaoCollector collector); |
| | | |
| | | boolean updateCollector(TqdianbiaoCollector collector); |
| | | |
| | | boolean deleteByIds(List<Long> ids); |
| | | |
| | | int syncFromRemote(); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service; |
| | | |
| | | public interface TqdianbiaoCollectorSyncService { |
| | | |
| | | int syncCollectors(); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.http.pojo.TqdianbiaoEleRecord; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | |
| | | public interface TqdianbiaoEleRecordManageService extends IService<TqdianbiaoEleRecord> { |
| | | |
| | | IPage<TqdianbiaoEleRecord> listPage(Page page, TqdianbiaoEleRecord query); |
| | | |
| | | boolean addRecord(TqdianbiaoEleRecord record); |
| | | |
| | | boolean updateRecord(TqdianbiaoEleRecord record); |
| | | |
| | | boolean deleteByIds(List<Long> ids); |
| | | |
| | | BigDecimal getPrevReading(Long meterId, String timeKey); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service; |
| | | |
| | | public interface TqdianbiaoEleSyncService { |
| | | |
| | | int syncHourData(); |
| | | |
| | | /** 䏿¬¡æ§è¡¥æ°ï¼åæ¥åä¸å¤© 00:00 è³å½åå°æ¶ï¼ç¨å®å¯å */ |
| | | int syncLast3DaysHourData(); |
| | | |
| | | int syncDayData(); |
| | | |
| | | int syncMonthData(); |
| | | |
| | | int syncYearData(); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.http.pojo.TqdianbiaoMeter; |
| | | |
| | | import java.util.List; |
| | | |
| | | public interface TqdianbiaoMeterManageService extends IService<TqdianbiaoMeter> { |
| | | |
| | | IPage<TqdianbiaoMeter> listPage(Page page, TqdianbiaoMeter query); |
| | | |
| | | boolean addMeter(TqdianbiaoMeter meter); |
| | | |
| | | boolean updateMeter(TqdianbiaoMeter meter); |
| | | |
| | | boolean deleteByIds(List<Long> ids); |
| | | |
| | | int syncFromRemote(); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service; |
| | | |
| | | public interface TqdianbiaoMeterSyncService { |
| | | |
| | | int syncMeters(); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service; |
| | | |
| | | public interface TqdianbiaoSyncLogService { |
| | | |
| | | void logSuccess(String syncType, String windowStart, String windowEnd, int recordCount); |
| | | |
| | | void logFailure(String syncType, String windowStart, String windowEnd, String errorMsg); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.http.HttpUtils; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import com.ruoyi.http.config.TqdianbiaoConfig; |
| | | import com.ruoyi.http.mapper.TqdianbiaoCollectorMapper; |
| | | import com.ruoyi.http.mapper.TqdianbiaoEleRecordMapper; |
| | | import com.ruoyi.http.mapper.TqdianbiaoMeterMapper; |
| | | import com.ruoyi.http.mapper.TqdianbiaoSyncLogMapper; |
| | | import com.ruoyi.http.pojo.TqdianbiaoCollector; |
| | | import com.ruoyi.http.pojo.TqdianbiaoEleRecord; |
| | | import com.ruoyi.http.pojo.TqdianbiaoSyncLog; |
| | | import com.ruoyi.http.service.StatisticEleService; |
| | | import com.ruoyi.http.util.StatisticEleAggregateUtil; |
| | | import com.ruoyi.http.util.StatisticEleAggregateUtil.HourRange; |
| | | import com.ruoyi.http.util.StatisticEleAnalyticsUtil; |
| | | import com.ruoyi.http.util.StatisticEleAnalyticsUtil.DateBounds; |
| | | import com.ruoyi.http.vo.StatisticEleAnalyticsVo; |
| | | import com.ruoyi.http.vo.StatisticEleRecordVo; |
| | | import com.ruoyi.http.vo.StatisticEleSummaryVo; |
| | | import com.ruoyi.http.vo.StatisticEleSyncStatusVo; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.function.Function; |
| | | |
| | | @Service |
| | | @Slf4j |
| | | @RequiredArgsConstructor |
| | | public class StatisticEleServiceImpl implements StatisticEleService { |
| | | |
| | | private static final Set<String> STAT_DIMENSIONS = Set.of("day", "week", "month", "quarter", "year"); |
| | | private static final List<String> DATA_DIMENSIONS = List.of("hour", "manual"); |
| | | private static final DateTimeFormatter LOG_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); |
| | | |
| | | private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyyMMdd"); |
| | | |
| | | private final TqdianbiaoConfig config; |
| | | private final TqdianbiaoEleRecordMapper eleRecordMapper; |
| | | private final TqdianbiaoMeterMapper meterMapper; |
| | | private final TqdianbiaoCollectorMapper collectorMapper; |
| | | private final TqdianbiaoSyncLogMapper syncLogMapper; |
| | | |
| | | @Override |
| | | public String fetchRawData(String dimension, String startTime, String endTime) { |
| | | if (!"hour".equals(dimension)) { |
| | | throw new ServiceException("ä»
æ¯ææåå°æ¶åå§æ°æ®"); |
| | | } |
| | | String url = config.getBaseUrl() + "/Api/StatisticEle/hour"; |
| | | String param = String.format( |
| | | "auth=%s&start_time=%s&end_time=%s&ignore_radio=%d", |
| | | config.getAuth(), startTime, endTime, config.getIgnoreRadio() |
| | | ); |
| | | log.warn("è°ç¨è¿ç¨çµè¡¨æ¥å£(è°è¯): {}?{}", url, param); |
| | | return HttpUtils.sendGet(url, param); |
| | | } |
| | | |
| | | @Override |
| | | public List<StatisticEleRecordVo> listRecords(String dimension, String startTime, String endTime, Integer ignoreRadio) { |
| | | if ("hour".equals(dimension) || "collection".equals(dimension)) { |
| | | return queryHourRecords(startTime, endTime); |
| | | } |
| | | return aggregateFromHour(dimension, startTime, endTime, true); |
| | | } |
| | | |
| | | @Override |
| | | public StatisticEleSummaryVo getSummary(String dimension, String startTime, String endTime) { |
| | | if (!StringUtils.hasText(startTime) || !StringUtils.hasText(endTime)) { |
| | | throw new ServiceException("å¼å§æ¶é´åç»ææ¶é´ä¸è½ä¸ºç©º"); |
| | | } |
| | | if ("hour".equals(dimension)) { |
| | | List<StatisticEleRecordVo> hourRecords = queryHourRecords(startTime, endTime); |
| | | List<StatisticEleRecordVo> chartRecords = StatisticEleAggregateUtil.aggregateHourToBuckets( |
| | | hourRecords, StatisticEleAggregateUtil.HOUR_TO_HOUR); |
| | | return buildSummary(hourRecords, chartRecords, hourRecords); |
| | | } |
| | | if (!STAT_DIMENSIONS.contains(dimension)) { |
| | | throw new ServiceException("ç»è®¡ç»´åº¦æ æï¼æ¯æ hour/day/week/month/quarter/year"); |
| | | } |
| | | |
| | | if ("day".equals(dimension)) { |
| | | return getDayDimensionSummary(startTime, endTime); |
| | | } |
| | | |
| | | HourRange range = StatisticEleAggregateUtil.toHourQueryRange(dimension, startTime, endTime); |
| | | List<StatisticEleRecordVo> hourRecords = queryHourRecords(range.startTime(), range.endTime()); |
| | | List<StatisticEleRecordVo> detailRecords = aggregateFromHour(dimension, startTime, endTime, true); |
| | | List<StatisticEleRecordVo> chartRecords = aggregateFromHour(dimension, startTime, endTime, false); |
| | | return buildSummary(detailRecords, chartRecords, hourRecords); |
| | | } |
| | | |
| | | @Override |
| | | public StatisticEleAnalyticsVo getAnalytics(String dimension, String startTime, String endTime, String trendGranularity) { |
| | | if (!StringUtils.hasText(startTime) || !StringUtils.hasText(endTime)) { |
| | | throw new ServiceException("å¼å§æ¶é´åç»ææ¶é´ä¸è½ä¸ºç©º"); |
| | | } |
| | | StatisticEleSummaryVo summary = getSummary(dimension, startTime, endTime); |
| | | HourRange range = StatisticEleAggregateUtil.toHourQueryRange( |
| | | normalizeAnalyticsDimension(dimension), startTime, endTime); |
| | | List<StatisticEleRecordVo> hourRecords = queryHourRecords(range.startTime(), range.endTime()); |
| | | List<StatisticEleRecordVo> hourlyMerged = StatisticEleAggregateUtil.aggregateHourToBuckets( |
| | | hourRecords, StatisticEleAggregateUtil.HOUR_TO_HOUR); |
| | | |
| | | boolean singleDay = "day".equals(dimension) && startTime.equals(endTime); |
| | | String trend = StringUtils.hasText(trendGranularity) |
| | | ? trendGranularity |
| | | : StatisticEleAnalyticsUtil.defaultTrendGranularity(dimension, singleDay); |
| | | |
| | | StatisticEleAnalyticsVo analytics = copyToAnalytics(summary); |
| | | analytics.setLoadRate(StatisticEleAnalyticsUtil.calcLoadRate( |
| | | summary.getAvgConsumption(), summary.getMaxConsumption())); |
| | | analytics.setPeriodSplits(StatisticEleAnalyticsUtil.calcPeriodSplits(hourlyMerged)); |
| | | analytics.setShiftSplits(StatisticEleAnalyticsUtil.calcShiftSplits(hourlyMerged)); |
| | | analytics.setDayTypeSplits(StatisticEleAnalyticsUtil.calcDayTypeSplits(hourlyMerged)); |
| | | analytics.setTrendGranularity(trend); |
| | | analytics.setTrendRecords(StatisticEleAggregateUtil.aggregateHourToBuckets( |
| | | hourRecords, StatisticEleAnalyticsUtil.trendBucketFn(trend))); |
| | | |
| | | DateBounds bounds = StatisticEleAnalyticsUtil.resolveBounds(dimension, startTime, endTime); |
| | | Double chainTotal = queryTotalByDayBounds(StatisticEleAnalyticsUtil.shiftChain(bounds)); |
| | | Double yoyTotal = queryTotalByDayBounds(StatisticEleAnalyticsUtil.shiftYoy(bounds)); |
| | | analytics.setChainComparison(StatisticEleAnalyticsUtil.buildComparison( |
| | | "chain", "ç¯æ¯ä¸æ", summary.getTotalConsumption(), chainTotal)); |
| | | analytics.setYoyComparison(StatisticEleAnalyticsUtil.buildComparison( |
| | | "yoy", "忝å»å¹´åæ", summary.getTotalConsumption(), yoyTotal)); |
| | | return analytics; |
| | | } |
| | | |
| | | private String normalizeAnalyticsDimension(String dimension) { |
| | | return STAT_DIMENSIONS.contains(dimension) ? dimension : "day"; |
| | | } |
| | | |
| | | private Double queryTotalByDayBounds(DateBounds bounds) { |
| | | String start = bounds.start().format(DAY_FMT); |
| | | String end = bounds.end().format(DAY_FMT); |
| | | HourRange range = StatisticEleAggregateUtil.toHourQueryRange("day", start, end); |
| | | List<StatisticEleRecordVo> hourRecords = queryHourRecords(range.startTime(), range.endTime()); |
| | | if (hourRecords.isEmpty()) { |
| | | return 0.0; |
| | | } |
| | | return StatisticEleAnalyticsUtil.calcCombinedMetrics( |
| | | hourRecords, |
| | | StatisticEleAggregateUtil.aggregateHourToBuckets(hourRecords, StatisticEleAggregateUtil.HOUR_TO_DAY) |
| | | ).getTotalConsumption(); |
| | | } |
| | | |
| | | private StatisticEleAnalyticsVo copyToAnalytics(StatisticEleSummaryVo summary) { |
| | | StatisticEleAnalyticsVo analytics = new StatisticEleAnalyticsVo(); |
| | | analytics.setTotalConsumption(summary.getTotalConsumption()); |
| | | analytics.setAvgConsumption(summary.getAvgConsumption()); |
| | | analytics.setMaxConsumption(summary.getMaxConsumption()); |
| | | analytics.setMinConsumption(summary.getMinConsumption()); |
| | | analytics.setRecordCount(summary.getRecordCount()); |
| | | analytics.setChartRecords(summary.getChartRecords()); |
| | | analytics.setRecords(summary.getRecords()); |
| | | return analytics; |
| | | } |
| | | |
| | | /** |
| | | * å¤©ç»´åº¦æ±æ»ï¼åæ¥ç¨å°æ¶çº§æ°æ®è®¡ç®åå¼/æå¼å¹¶å±ç¤º 24 å°æ¶è¶å¿ï¼å¤æ¥ææ¥æ¡¶å¯¹æ¯ã |
| | | */ |
| | | private StatisticEleSummaryVo getDayDimensionSummary(String startTime, String endTime) { |
| | | HourRange range = StatisticEleAggregateUtil.toHourQueryRange("day", startTime, endTime); |
| | | List<StatisticEleRecordVo> hourRecords = queryHourRecords(range.startTime(), range.endTime()); |
| | | List<StatisticEleRecordVo> detailRecords = StatisticEleAggregateUtil.aggregateHourPerMeter( |
| | | hourRecords, StatisticEleAggregateUtil.HOUR_TO_DAY); |
| | | |
| | | boolean singleDay = startTime.equals(endTime); |
| | | List<StatisticEleRecordVo> chartRecords = singleDay |
| | | ? StatisticEleAggregateUtil.aggregateHourToBuckets(hourRecords, StatisticEleAggregateUtil.HOUR_TO_HOUR) |
| | | : StatisticEleAggregateUtil.aggregateHourToBuckets(hourRecords, StatisticEleAggregateUtil.HOUR_TO_DAY); |
| | | |
| | | return buildSummary(detailRecords, chartRecords, hourRecords); |
| | | } |
| | | |
| | | private StatisticEleSummaryVo buildSummary( |
| | | List<StatisticEleRecordVo> detailRecords, |
| | | List<StatisticEleRecordVo> chartRecords, |
| | | List<StatisticEleRecordVo> hourRecords) { |
| | | StatisticEleAggregateUtil.StatisticEleSummaryMetrics metrics = |
| | | StatisticEleAnalyticsUtil.calcCombinedMetrics(hourRecords, chartRecords); |
| | | StatisticEleSummaryVo summary = new StatisticEleSummaryVo(); |
| | | summary.setRecords(detailRecords); |
| | | summary.setChartRecords(chartRecords); |
| | | summary.setRecordCount(metrics.getRecordCount()); |
| | | summary.setTotalConsumption(metrics.getTotalConsumption()); |
| | | summary.setAvgConsumption(metrics.getAvgConsumption()); |
| | | summary.setMaxConsumption(metrics.getMaxConsumption()); |
| | | summary.setMinConsumption(metrics.getMinConsumption()); |
| | | return summary; |
| | | } |
| | | |
| | | @Override |
| | | public StatisticEleSummaryVo getYesterdaySummary() { |
| | | String day = LocalDate.now().minusDays(1).format(DateTimeFormatter.ofPattern("yyyyMMdd")); |
| | | return getDayDimensionSummary(day, day); |
| | | } |
| | | |
| | | @Override |
| | | public void exportRecords(String dimension, String startTime, String endTime, HttpServletResponse response) { |
| | | List<StatisticEleRecordVo> records; |
| | | if ("hour".equals(dimension)) { |
| | | records = queryHourRecords(startTime, endTime); |
| | | } else { |
| | | records = aggregateFromHour(dimension, startTime, endTime, true); |
| | | } |
| | | ExcelUtil<StatisticEleRecordVo> util = new ExcelUtil<>(StatisticEleRecordVo.class); |
| | | util.exportExcel(response, records, "è½èç»è®¡æ°æ®"); |
| | | } |
| | | |
| | | @Override |
| | | public StatisticEleSyncStatusVo getSyncStatus() { |
| | | StatisticEleSyncStatusVo status = new StatisticEleSyncStatusVo(); |
| | | status.setMeterCount(Math.toIntExact(meterMapper.selectCount(null))); |
| | | status.setCollectorCount(Math.toIntExact(collectorMapper.selectCount(null))); |
| | | status.setOnlineCollectorCount(Math.toIntExact(collectorMapper.selectCount( |
| | | Wrappers.<TqdianbiaoCollector>lambdaQuery().eq(TqdianbiaoCollector::getOnline, true)))); |
| | | |
| | | Map<String, Long> recordCountByDimension = new HashMap<>(); |
| | | recordCountByDimension.put("hour", eleRecordMapper.selectCount( |
| | | Wrappers.<TqdianbiaoEleRecord>lambdaQuery().eq(TqdianbiaoEleRecord::getDimension, "hour"))); |
| | | status.setRecordCountByDimension(recordCountByDimension); |
| | | |
| | | Map<String, String> lastSyncTimeByType = new HashMap<>(); |
| | | for (String syncType : List.of("collector", "meter", "hour")) { |
| | | TqdianbiaoSyncLog latest = syncLogMapper.selectOne( |
| | | Wrappers.<TqdianbiaoSyncLog>lambdaQuery() |
| | | .eq(TqdianbiaoSyncLog::getSyncType, syncType) |
| | | .eq(TqdianbiaoSyncLog::getStatus, "success") |
| | | .orderByDesc(TqdianbiaoSyncLog::getCreateTime) |
| | | .last("LIMIT 1")); |
| | | if (latest != null && latest.getCreateTime() != null) { |
| | | lastSyncTimeByType.put(syncType, latest.getCreateTime().format(LOG_TIME_FMT)); |
| | | } |
| | | } |
| | | status.setLastSyncTimeByType(lastSyncTimeByType); |
| | | return status; |
| | | } |
| | | |
| | | private List<StatisticEleRecordVo> queryHourRecords(String startTime, String endTime) { |
| | | String normalizedStart = StatisticEleAggregateUtil.normalizeQueryStartTimeKey(startTime); |
| | | String normalizedEnd = StatisticEleAggregateUtil.normalizeQueryEndTimeKey(endTime); |
| | | List<StatisticEleRecordVo> records = eleRecordMapper.selectRecordList(DATA_DIMENSIONS, normalizedStart, normalizedEnd); |
| | | StatisticEleAggregateUtil.normalizeConsumptions(records); |
| | | return records; |
| | | } |
| | | |
| | | private List<StatisticEleRecordVo> aggregateFromHour( |
| | | String dimension, String startTime, String endTime, boolean perMeter) { |
| | | HourRange range = StatisticEleAggregateUtil.toHourQueryRange(dimension, startTime, endTime); |
| | | List<StatisticEleRecordVo> hourRecords = queryHourRecords(range.startTime(), range.endTime()); |
| | | Function<String, String> bucketFn = StatisticEleAggregateUtil.bucketFn(dimension); |
| | | if (perMeter) { |
| | | return StatisticEleAggregateUtil.aggregateHourPerMeter(hourRecords, bucketFn); |
| | | } |
| | | return StatisticEleAggregateUtil.aggregateHourToBuckets(hourRecords, bucketFn); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.http.mapper.TqdianbiaoCollectorMapper; |
| | | import com.ruoyi.http.pojo.TqdianbiaoCollector; |
| | | import com.ruoyi.http.service.TqdianbiaoCollectorManageService; |
| | | import com.ruoyi.http.service.TqdianbiaoCollectorSyncService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.CollectionUtils; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class TqdianbiaoCollectorManageServiceImpl |
| | | extends ServiceImpl<TqdianbiaoCollectorMapper, TqdianbiaoCollector> |
| | | implements TqdianbiaoCollectorManageService { |
| | | |
| | | private final TqdianbiaoCollectorSyncService collectorSyncService; |
| | | |
| | | @Override |
| | | public IPage<TqdianbiaoCollector> listPage(Page page, TqdianbiaoCollector query) { |
| | | return page(page, Wrappers.<TqdianbiaoCollector>lambdaQuery() |
| | | .and(StringUtils.isNotEmpty(query.getKeyword()), w -> w |
| | | .like(TqdianbiaoCollector::getCollectorNo, query.getKeyword()) |
| | | .or() |
| | | .like(TqdianbiaoCollector::getCollectorId, query.getKeyword()) |
| | | .or() |
| | | .like(TqdianbiaoCollector::getDescription, query.getKeyword())) |
| | | .eq(StringUtils.isNotEmpty(query.getCollectorId()), TqdianbiaoCollector::getCollectorId, query.getCollectorId()) |
| | | .orderByDesc(TqdianbiaoCollector::getUpdateTime)); |
| | | } |
| | | |
| | | @Override |
| | | public boolean addCollector(TqdianbiaoCollector collector) { |
| | | validateCollector(collector); |
| | | if (existsCollectorId(collector.getCollectorId(), null)) { |
| | | throw new ServiceException("éé卿¡£æ¡IDå·²åå¨"); |
| | | } |
| | | collector.setSyncTime(LocalDateTime.now()); |
| | | return save(collector); |
| | | } |
| | | |
| | | @Override |
| | | public boolean updateCollector(TqdianbiaoCollector collector) { |
| | | if (collector.getId() == null) { |
| | | throw new ServiceException("IDä¸è½ä¸ºç©º"); |
| | | } |
| | | validateCollector(collector); |
| | | if (existsCollectorId(collector.getCollectorId(), collector.getId())) { |
| | | throw new ServiceException("éé卿¡£æ¡IDå·²åå¨"); |
| | | } |
| | | collector.setSyncTime(LocalDateTime.now()); |
| | | return updateById(collector); |
| | | } |
| | | |
| | | @Override |
| | | public boolean deleteByIds(List<Long> ids) { |
| | | if (CollectionUtils.isEmpty(ids)) { |
| | | throw new ServiceException("è¯·éæ©è³å°ä¸æ¡æ°æ®"); |
| | | } |
| | | return removeBatchByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public int syncFromRemote() { |
| | | return collectorSyncService.syncCollectors(); |
| | | } |
| | | |
| | | private void validateCollector(TqdianbiaoCollector collector) { |
| | | if (StringUtils.isEmpty(collector.getCollectorId())) { |
| | | throw new ServiceException("éé卿¡£æ¡IDä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(collector.getCollectorNo())) { |
| | | throw new ServiceException("ééå¨å·ä¸è½ä¸ºç©º"); |
| | | } |
| | | } |
| | | |
| | | private boolean existsCollectorId(String collectorId, Long excludeId) { |
| | | return count(Wrappers.<TqdianbiaoCollector>lambdaQuery() |
| | | .eq(TqdianbiaoCollector::getCollectorId, collectorId) |
| | | .ne(excludeId != null, TqdianbiaoCollector::getId, excludeId)) > 0; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service.impl; |
| | | |
| | | import com.alibaba.fastjson2.JSON; |
| | | import com.alibaba.fastjson2.JSONArray; |
| | | import com.alibaba.fastjson2.JSONObject; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.ruoyi.common.utils.http.HttpUtils; |
| | | import com.ruoyi.http.config.TqdianbiaoConfig; |
| | | import com.ruoyi.http.mapper.TqdianbiaoCollectorMapper; |
| | | import com.ruoyi.http.pojo.TqdianbiaoCollector; |
| | | import com.ruoyi.http.service.TqdianbiaoCollectorSyncService; |
| | | import com.ruoyi.http.service.TqdianbiaoSyncLogService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Service |
| | | @Slf4j |
| | | @RequiredArgsConstructor |
| | | public class TqdianbiaoCollectorSyncServiceImpl implements TqdianbiaoCollectorSyncService { |
| | | |
| | | private final TqdianbiaoConfig config; |
| | | private final TqdianbiaoCollectorMapper collectorMapper; |
| | | private final TqdianbiaoSyncLogService syncLogService; |
| | | |
| | | @Override |
| | | public int syncCollectors() { |
| | | String syncType = "collector"; |
| | | try { |
| | | String url = config.getBaseUrl() + "/Api/Collector"; |
| | | String param = "auth=" + config.getAuth(); |
| | | String raw = HttpUtils.sendGet(url, param); |
| | | JSONArray list = parseList(raw); |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | int count = 0; |
| | | for (int i = 0; i < list.size(); i++) { |
| | | JSONObject item = list.getJSONObject(i); |
| | | String collectorId = item.getString("id"); |
| | | if (collectorId == null) { |
| | | continue; |
| | | } |
| | | TqdianbiaoCollector entity = collectorMapper.selectOne( |
| | | Wrappers.<TqdianbiaoCollector>lambdaQuery() |
| | | .eq(TqdianbiaoCollector::getCollectorId, collectorId) |
| | | .last("LIMIT 1")); |
| | | if (entity == null) { |
| | | entity = new TqdianbiaoCollector(); |
| | | entity.setCollectorId(collectorId); |
| | | } |
| | | entity.setCollectorNo(item.getString("collectorid")); |
| | | entity.setOnline(item.getBoolean("online")); |
| | | entity.setCsq(item.getInteger("csq")); |
| | | entity.setConnectTime(item.getString("connect_time")); |
| | | entity.setDisconnectTime(item.getString("disconnect_time")); |
| | | entity.setDescription(item.getString("description")); |
| | | entity.setSyncTime(now); |
| | | if (entity.getId() == null) { |
| | | collectorMapper.insert(entity); |
| | | } else { |
| | | collectorMapper.updateById(entity); |
| | | } |
| | | count++; |
| | | } |
| | | syncLogService.logSuccess(syncType, null, null, count); |
| | | return count; |
| | | } catch (Exception e) { |
| | | log.error("ééå¨åæ¥å¤±è´¥", e); |
| | | syncLogService.logFailure(syncType, null, null, e.getMessage()); |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | private JSONArray parseList(String raw) { |
| | | Object parsed = JSON.parse(raw); |
| | | if (parsed instanceof JSONArray) { |
| | | return (JSONArray) parsed; |
| | | } |
| | | JSONObject root = (JSONObject) parsed; |
| | | if (root.getIntValue("status") != 1) { |
| | | throw new IllegalStateException("éé卿¥å£è¿åå¼å¸¸: " + raw); |
| | | } |
| | | Object data = root.get("data"); |
| | | if (data instanceof JSONArray) { |
| | | return (JSONArray) data; |
| | | } |
| | | return new JSONArray(); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.http.mapper.TqdianbiaoEleRecordMapper; |
| | | import com.ruoyi.http.mapper.TqdianbiaoMeterMapper; |
| | | import com.ruoyi.http.pojo.TqdianbiaoEleRecord; |
| | | import com.ruoyi.http.pojo.TqdianbiaoMeter; |
| | | import com.ruoyi.http.service.TqdianbiaoEleRecordManageService; |
| | | import com.ruoyi.http.util.StatisticEleReadingUtil; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.CollectionUtils; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | |
| | | @Service |
| | | public class TqdianbiaoEleRecordManageServiceImpl |
| | | extends ServiceImpl<TqdianbiaoEleRecordMapper, TqdianbiaoEleRecord> |
| | | implements TqdianbiaoEleRecordManageService { |
| | | |
| | | private static final Set<String> MANUAL_DIMENSIONS = Set.of("manual"); |
| | | |
| | | private final TqdianbiaoMeterMapper meterMapper; |
| | | |
| | | public TqdianbiaoEleRecordManageServiceImpl(TqdianbiaoMeterMapper meterMapper) { |
| | | this.meterMapper = meterMapper; |
| | | } |
| | | |
| | | @Override |
| | | public IPage<TqdianbiaoEleRecord> listPage(Page page, TqdianbiaoEleRecord query) { |
| | | return page(page, Wrappers.<TqdianbiaoEleRecord>lambdaQuery() |
| | | .eq(StringUtils.isNotEmpty(query.getDimension()), TqdianbiaoEleRecord::getDimension, query.getDimension()) |
| | | .eq(query.getMeterId() != null, TqdianbiaoEleRecord::getMeterId, query.getMeterId()) |
| | | .ge(StringUtils.isNotEmpty(query.getStartTimeKey()), TqdianbiaoEleRecord::getTimeKey, query.getStartTimeKey()) |
| | | .le(StringUtils.isNotEmpty(query.getEndTimeKey()), TqdianbiaoEleRecord::getTimeKey, query.getEndTimeKey()) |
| | | .orderByDesc(TqdianbiaoEleRecord::getTimeKey)); |
| | | } |
| | | |
| | | @Override |
| | | public boolean addRecord(TqdianbiaoEleRecord record) { |
| | | validateRecord(record); |
| | | enrichFromMeter(record); |
| | | if (existsUnique(record, null)) { |
| | | throw new ServiceException("该çµè¡¨å¨è¯¥æ¶é´ç¹å·²åå¨è®°å½"); |
| | | } |
| | | record.setSyncTime(LocalDateTime.now()); |
| | | return save(record); |
| | | } |
| | | |
| | | @Override |
| | | public boolean updateRecord(TqdianbiaoEleRecord record) { |
| | | if (record.getId() == null) { |
| | | throw new ServiceException("IDä¸è½ä¸ºç©º"); |
| | | } |
| | | validateRecord(record); |
| | | enrichFromMeter(record); |
| | | if (existsUnique(record, record.getId())) { |
| | | throw new ServiceException("该çµè¡¨å¨è¯¥æ¶é´ç¹å·²åå¨è®°å½"); |
| | | } |
| | | record.setSyncTime(LocalDateTime.now()); |
| | | return updateById(record); |
| | | } |
| | | |
| | | @Override |
| | | public boolean deleteByIds(List<Long> ids) { |
| | | if (CollectionUtils.isEmpty(ids)) { |
| | | throw new ServiceException("è¯·éæ©è³å°ä¸æ¡æ°æ®"); |
| | | } |
| | | return removeBatchByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public BigDecimal getPrevReading(Long meterId, String timeKey) { |
| | | if (meterId == null || StringUtils.isEmpty(timeKey)) { |
| | | return null; |
| | | } |
| | | TqdianbiaoEleRecord prev = baseMapper.selectPrevReading(meterId, timeKey); |
| | | if (prev == null) { |
| | | return null; |
| | | } |
| | | if (prev.getCurrReading() != null) { |
| | | return prev.getCurrReading(); |
| | | } |
| | | return StatisticEleReadingUtil.parseFirstReading(prev.getEndReading()); |
| | | } |
| | | |
| | | private void enrichFromMeter(TqdianbiaoEleRecord record) { |
| | | TqdianbiaoMeter meter = meterMapper.selectOne( |
| | | Wrappers.<TqdianbiaoMeter>lambdaQuery() |
| | | .eq(TqdianbiaoMeter::getMeterId, record.getMeterId()) |
| | | .last("LIMIT 1")); |
| | | if (meter != null) { |
| | | record.setMeterName(meter.getMeterName()); |
| | | if (record.getRatio() == null && meter.getRate() != null) { |
| | | record.setRatio(meter.getRate()); |
| | | } |
| | | } |
| | | if (record.getPrevReading() != null && record.getCurrReading() != null) { |
| | | record.setTotalConsumption( |
| | | StatisticEleReadingUtil.calcConsumption(record.getPrevReading(), record.getCurrReading(), record.getRatio())); |
| | | } |
| | | } |
| | | |
| | | private void validateRecord(TqdianbiaoEleRecord record) { |
| | | if (record.getMeterId() == null) { |
| | | throw new ServiceException("çµè¡¨ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(record.getDimension()) || !MANUAL_DIMENSIONS.contains(record.getDimension())) { |
| | | throw new ServiceException("ä»
æ¯ææå¨å½å
¥(manual)ç»´åº¦æ°æ®"); |
| | | } |
| | | if (StringUtils.isEmpty(record.getTimeKey())) { |
| | | throw new ServiceException("æ¶é´æ è¯ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (record.getTimeKey().length() != 12) { |
| | | throw new ServiceException("æå¨å½å
¥æ¶é´æ è¯æ ¼å¼åºä¸º YYYYMMDDHHmm"); |
| | | } |
| | | if (record.getCurrReading() == null) { |
| | | throw new ServiceException("æ¬æ¬¡çµéä¸è½ä¸ºç©º"); |
| | | } |
| | | if (record.getPrevReading() == null) { |
| | | throw new ServiceException("䏿¬¡çµéä¸è½ä¸ºç©º"); |
| | | } |
| | | record.setReadingMethod("manual"); |
| | | } |
| | | |
| | | private boolean existsUnique(TqdianbiaoEleRecord record, Long excludeId) { |
| | | return count(Wrappers.<TqdianbiaoEleRecord>lambdaQuery() |
| | | .eq(TqdianbiaoEleRecord::getMeterId, record.getMeterId()) |
| | | .eq(TqdianbiaoEleRecord::getDimension, record.getDimension()) |
| | | .eq(TqdianbiaoEleRecord::getTimeKey, record.getTimeKey()) |
| | | .ne(excludeId != null, TqdianbiaoEleRecord::getId, excludeId)) > 0; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.ruoyi.common.utils.http.HttpUtils; |
| | | import com.ruoyi.http.config.TqdianbiaoConfig; |
| | | import com.ruoyi.http.mapper.TqdianbiaoEleRecordMapper; |
| | | import com.ruoyi.http.mapper.TqdianbiaoMeterMapper; |
| | | import com.ruoyi.http.pojo.TqdianbiaoEleRecord; |
| | | import com.ruoyi.http.pojo.TqdianbiaoMeter; |
| | | import com.ruoyi.http.service.TqdianbiaoEleSyncService; |
| | | import com.ruoyi.http.service.TqdianbiaoSyncLogService; |
| | | import com.ruoyi.http.util.StatisticEleParseUtil; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.List; |
| | | |
| | | @Service |
| | | @Slf4j |
| | | @RequiredArgsConstructor |
| | | public class TqdianbiaoEleSyncServiceImpl implements TqdianbiaoEleSyncService { |
| | | |
| | | private static final DateTimeFormatter HOUR_FMT = DateTimeFormatter.ofPattern("yyyyMMddHH"); |
| | | private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyyMMdd"); |
| | | private static final DateTimeFormatter MONTH_FMT = DateTimeFormatter.ofPattern("yyyyMM"); |
| | | private static final DateTimeFormatter YEAR_FMT = DateTimeFormatter.ofPattern("yyyy"); |
| | | |
| | | private final TqdianbiaoConfig config; |
| | | private final TqdianbiaoEleRecordMapper eleRecordMapper; |
| | | private final TqdianbiaoMeterMapper meterMapper; |
| | | private final TqdianbiaoSyncLogService syncLogService; |
| | | |
| | | @Override |
| | | public int syncHourData() { |
| | | int window = config.getSync().getHourWindow() != null ? config.getSync().getHourWindow() : 1; |
| | | if (window < 1) { |
| | | window = 1; |
| | | } |
| | | // ç»æäºä¸ä¸å®æ´å°æ¶ï¼ä¸å
å«å½åæªç»æçå°æ¶ |
| | | LocalDateTime currentHourStart = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0); |
| | | LocalDateTime end = currentHourStart.minusHours(1); |
| | | LocalDateTime start = end.minusHours(window - 1L); |
| | | String startTime = start.format(HOUR_FMT); |
| | | String endTime = end.format(HOUR_FMT); |
| | | return syncDimension("hour", startTime, endTime); |
| | | } |
| | | |
| | | @Override |
| | | public int syncLast3DaysHourData() { |
| | | LocalDateTime start = LocalDate.now().minusDays(3).atStartOfDay(); |
| | | LocalDateTime end = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0); |
| | | return syncHourRangeInChunks(start, end); |
| | | } |
| | | |
| | | /** ææå¤ 24 å°æ¶ä¸æ®µåæ¹æåï¼å¹³å°æ¥å£éå¶ï¼ */ |
| | | private int syncHourRangeInChunks(LocalDateTime start, LocalDateTime end) { |
| | | int total = 0; |
| | | LocalDateTime windowStart = start; |
| | | while (!windowStart.isAfter(end)) { |
| | | LocalDateTime windowEnd = windowStart.plusHours(23); |
| | | if (windowEnd.isAfter(end)) { |
| | | windowEnd = end; |
| | | } |
| | | total += syncDimension("hour", windowStart.format(HOUR_FMT), windowEnd.format(HOUR_FMT)); |
| | | windowStart = windowEnd.plusHours(1); |
| | | } |
| | | return total; |
| | | } |
| | | |
| | | @Override |
| | | public int syncDayData() { |
| | | LocalDate yesterday = LocalDate.now().minusDays(1); |
| | | String day = yesterday.format(DAY_FMT); |
| | | return syncDimension("day", day, day); |
| | | } |
| | | |
| | | @Override |
| | | public int syncMonthData() { |
| | | LocalDate now = LocalDate.now(); |
| | | String month = now.format(MONTH_FMT); |
| | | return syncDimension("month", month, month); |
| | | } |
| | | |
| | | @Override |
| | | public int syncYearData() { |
| | | LocalDate now = LocalDate.now(); |
| | | String year = now.format(YEAR_FMT); |
| | | return syncDimension("year", year, year); |
| | | } |
| | | |
| | | private int syncDimension(String dimension, String startTime, String endTime) { |
| | | return syncDimension(dimension, startTime, endTime, config.getIgnoreRadio()); |
| | | } |
| | | |
| | | private int syncDimension(String dimension, String startTime, String endTime, int ignoreRadio) { |
| | | try { |
| | | String url = config.getBaseUrl() + "/Api/StatisticEle/" + dimension; |
| | | String param = String.format( |
| | | "auth=%s&start_time=%s&end_time=%s&ignore_radio=%d", |
| | | config.getAuth(), startTime, endTime, ignoreRadio |
| | | ); |
| | | log.info("忥çµéæ°æ®: {} {}-{} ignore_radio={}", dimension, startTime, endTime, ignoreRadio); |
| | | String raw = HttpUtils.sendGet(url, param); |
| | | List<TqdianbiaoEleRecord> records = StatisticEleParseUtil.parseToEntities(raw, dimension); |
| | | enrichMeterInfo(records); |
| | | int count = upsertRecords(records); |
| | | syncLogService.logSuccess(dimension, startTime, endTime, count); |
| | | return count; |
| | | } catch (Exception e) { |
| | | log.error("çµéåæ¥å¤±è´¥ dimension={} {}-{}", dimension, startTime, endTime, e); |
| | | syncLogService.logFailure(dimension, startTime, endTime, e.getMessage()); |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | private void enrichMeterInfo(List<TqdianbiaoEleRecord> records) { |
| | | for (TqdianbiaoEleRecord record : records) { |
| | | if (record.getMeterId() == null) { |
| | | continue; |
| | | } |
| | | TqdianbiaoMeter meter = meterMapper.selectOne( |
| | | Wrappers.<TqdianbiaoMeter>lambdaQuery() |
| | | .eq(TqdianbiaoMeter::getMeterId, record.getMeterId()) |
| | | .last("LIMIT 1")); |
| | | if (meter != null && StringUtils.hasText(meter.getMeterName())) { |
| | | record.setMeterName(meter.getMeterName()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private int upsertRecords(List<TqdianbiaoEleRecord> records) { |
| | | int count = 0; |
| | | for (TqdianbiaoEleRecord record : records) { |
| | | TqdianbiaoEleRecord existing = eleRecordMapper.selectOne( |
| | | Wrappers.<TqdianbiaoEleRecord>lambdaQuery() |
| | | .eq(TqdianbiaoEleRecord::getMeterId, record.getMeterId()) |
| | | .eq(TqdianbiaoEleRecord::getDimension, record.getDimension()) |
| | | .eq(TqdianbiaoEleRecord::getTimeKey, record.getTimeKey()) |
| | | .last("LIMIT 1")); |
| | | if (existing == null) { |
| | | eleRecordMapper.insert(record); |
| | | } else { |
| | | record.setId(existing.getId()); |
| | | eleRecordMapper.updateById(record); |
| | | } |
| | | count++; |
| | | } |
| | | return count; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.http.mapper.TqdianbiaoMeterMapper; |
| | | import com.ruoyi.http.pojo.TqdianbiaoMeter; |
| | | import com.ruoyi.http.service.TqdianbiaoMeterManageService; |
| | | import com.ruoyi.http.service.TqdianbiaoMeterSyncService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.CollectionUtils; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class TqdianbiaoMeterManageServiceImpl |
| | | extends ServiceImpl<TqdianbiaoMeterMapper, TqdianbiaoMeter> |
| | | implements TqdianbiaoMeterManageService { |
| | | |
| | | private static final long MANUAL_METER_ID_BASE = 900_000_000L; |
| | | |
| | | private final TqdianbiaoMeterSyncService meterSyncService; |
| | | |
| | | @Override |
| | | public IPage<TqdianbiaoMeter> listPage(Page page, TqdianbiaoMeter query) { |
| | | return page(page, Wrappers.<TqdianbiaoMeter>lambdaQuery() |
| | | .and(StringUtils.isNotEmpty(query.getKeyword()), w -> w |
| | | .like(TqdianbiaoMeter::getMeterName, query.getKeyword()) |
| | | .or() |
| | | .like(TqdianbiaoMeter::getAddress, query.getKeyword()) |
| | | .or() |
| | | .like(TqdianbiaoMeter::getDescription, query.getKeyword())) |
| | | .eq(query.getMeterId() != null, TqdianbiaoMeter::getMeterId, query.getMeterId()) |
| | | .eq(StringUtils.isNotEmpty(query.getSource()), TqdianbiaoMeter::getSource, query.getSource()) |
| | | .orderByDesc(TqdianbiaoMeter::getUpdateTime)); |
| | | } |
| | | |
| | | @Override |
| | | public boolean addMeter(TqdianbiaoMeter meter) { |
| | | validateManualMeter(meter); |
| | | meter.setSource("manual"); |
| | | meter.setMeterId(generateManualMeterId()); |
| | | if (meter.getRate() == null) { |
| | | meter.setRate(1); |
| | | } |
| | | if (StringUtils.isEmpty(meter.getRelayState())) { |
| | | meter.setRelayState("1"); |
| | | } |
| | | meter.setSyncTime(LocalDateTime.now()); |
| | | return save(meter); |
| | | } |
| | | |
| | | @Override |
| | | public boolean updateMeter(TqdianbiaoMeter meter) { |
| | | if (meter.getId() == null) { |
| | | throw new ServiceException("IDä¸è½ä¸ºç©º"); |
| | | } |
| | | TqdianbiaoMeter existing = getById(meter.getId()); |
| | | if (existing == null) { |
| | | throw new ServiceException("çµè¡¨ä¸åå¨"); |
| | | } |
| | | if (StringUtils.isEmpty(meter.getAddress())) { |
| | | throw new ServiceException("表å°åä¸è½ä¸ºç©º"); |
| | | } |
| | | String meterName = StringUtils.isNotEmpty(meter.getMeterName()) ? meter.getMeterName() : meter.getAddress(); |
| | | existing.setMeterName(meterName); |
| | | existing.setAddress(meter.getAddress()); |
| | | existing.setDescription(meter.getDescription()); |
| | | existing.setRelayState(meter.getRelayState()); |
| | | if (existing.getSource() == null) { |
| | | existing.setSource("sync"); |
| | | } |
| | | if ("manual".equals(existing.getSource())) { |
| | | existing.setRate(meter.getRate()); |
| | | } |
| | | existing.setSyncTime(LocalDateTime.now()); |
| | | return updateById(existing); |
| | | } |
| | | |
| | | @Override |
| | | public boolean deleteByIds(List<Long> ids) { |
| | | if (CollectionUtils.isEmpty(ids)) { |
| | | throw new ServiceException("è¯·éæ©è³å°ä¸æ¡æ°æ®"); |
| | | } |
| | | return removeBatchByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public int syncFromRemote() { |
| | | return meterSyncService.syncMeters(); |
| | | } |
| | | |
| | | private void validateManualMeter(TqdianbiaoMeter meter) { |
| | | if (StringUtils.isEmpty(meter.getMeterName())) { |
| | | throw new ServiceException("çµè¡¨åç§°ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(meter.getAddress())) { |
| | | throw new ServiceException("表å°åä¸è½ä¸ºç©º"); |
| | | } |
| | | } |
| | | |
| | | private Long generateManualMeterId() { |
| | | TqdianbiaoMeter maxManual = getOne( |
| | | Wrappers.<TqdianbiaoMeter>lambdaQuery() |
| | | .eq(TqdianbiaoMeter::getSource, "manual") |
| | | .orderByDesc(TqdianbiaoMeter::getMeterId) |
| | | .last("LIMIT 1"), |
| | | false); |
| | | if (maxManual == null || maxManual.getMeterId() == null) { |
| | | return MANUAL_METER_ID_BASE + 1; |
| | | } |
| | | return maxManual.getMeterId() + 1; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service.impl; |
| | | |
| | | import com.alibaba.fastjson2.JSON; |
| | | import com.alibaba.fastjson2.JSONArray; |
| | | import com.alibaba.fastjson2.JSONObject; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.ruoyi.common.utils.http.HttpUtils; |
| | | import com.ruoyi.http.config.TqdianbiaoConfig; |
| | | import com.ruoyi.http.mapper.TqdianbiaoMeterMapper; |
| | | import com.ruoyi.http.pojo.TqdianbiaoMeter; |
| | | import com.ruoyi.http.service.TqdianbiaoMeterSyncService; |
| | | import com.ruoyi.http.service.TqdianbiaoSyncLogService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Service |
| | | @Slf4j |
| | | @RequiredArgsConstructor |
| | | public class TqdianbiaoMeterSyncServiceImpl implements TqdianbiaoMeterSyncService { |
| | | |
| | | private final TqdianbiaoConfig config; |
| | | private final TqdianbiaoMeterMapper meterMapper; |
| | | private final TqdianbiaoSyncLogService syncLogService; |
| | | |
| | | @Override |
| | | public int syncMeters() { |
| | | String syncType = "meter"; |
| | | try { |
| | | String url = config.getBaseUrl() + "/Api/Meter"; |
| | | String param = "auth=" + config.getAuth(); |
| | | String raw = HttpUtils.sendGet(url, param); |
| | | JSONArray list = parseList(raw); |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | int count = 0; |
| | | for (int i = 0; i < list.size(); i++) { |
| | | JSONObject item = list.getJSONObject(i); |
| | | if (!"0".equals(String.valueOf(item.get("device_type")))) { |
| | | continue; |
| | | } |
| | | Long meterId = item.getLong("id"); |
| | | if (meterId == null) { |
| | | continue; |
| | | } |
| | | TqdianbiaoMeter entity = meterMapper.selectOne( |
| | | Wrappers.<TqdianbiaoMeter>lambdaQuery() |
| | | .eq(TqdianbiaoMeter::getMeterId, meterId) |
| | | .last("LIMIT 1")); |
| | | boolean isNew = entity == null; |
| | | if (isNew) { |
| | | entity = new TqdianbiaoMeter(); |
| | | entity.setMeterId(meterId); |
| | | entity.setSource("sync"); |
| | | } |
| | | String savedName = entity.getMeterName(); |
| | | String savedAddress = entity.getAddress(); |
| | | String savedDesc = entity.getDescription(); |
| | | String savedRelay = entity.getRelayState(); |
| | | |
| | | entity.setCollectorId(item.getString("cid")); |
| | | entity.setCollectorNo(item.getString("collectorid")); |
| | | entity.setMeterType(item.getString("type")); |
| | | entity.setCsq(item.getInteger("csq")); |
| | | entity.setRate(item.getInteger("rate")); |
| | | entity.setSyncTime(now); |
| | | |
| | | if (isNew) { |
| | | entity.setAddress(item.getString("address")); |
| | | entity.setRelayState(item.getString("relay_state")); |
| | | entity.setDescription(item.getString("description")); |
| | | entity.setMeterName(StringUtils.hasText(savedName) ? savedName : item.getString("address")); |
| | | } else { |
| | | entity.setMeterName(savedName); |
| | | entity.setAddress(savedAddress); |
| | | entity.setDescription(savedDesc); |
| | | entity.setRelayState(savedRelay); |
| | | } |
| | | if (entity.getId() == null) { |
| | | meterMapper.insert(entity); |
| | | } else { |
| | | meterMapper.updateById(entity); |
| | | } |
| | | count++; |
| | | } |
| | | syncLogService.logSuccess(syncType, null, null, count); |
| | | return count; |
| | | } catch (Exception e) { |
| | | log.error("çµè¡¨åæ¥å¤±è´¥", e); |
| | | syncLogService.logFailure(syncType, null, null, e.getMessage()); |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | private JSONArray parseList(String raw) { |
| | | Object parsed = JSON.parse(raw); |
| | | if (parsed instanceof JSONArray) { |
| | | return (JSONArray) parsed; |
| | | } |
| | | JSONObject root = (JSONObject) parsed; |
| | | if (root.getIntValue("status") != 1) { |
| | | throw new IllegalStateException("çµè¡¨æ¥å£è¿åå¼å¸¸: " + raw); |
| | | } |
| | | Object data = root.get("data"); |
| | | if (data instanceof JSONArray) { |
| | | return (JSONArray) data; |
| | | } |
| | | return new JSONArray(); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.service.impl; |
| | | |
| | | import com.ruoyi.http.mapper.TqdianbiaoSyncLogMapper; |
| | | import com.ruoyi.http.pojo.TqdianbiaoSyncLog; |
| | | import com.ruoyi.http.service.TqdianbiaoSyncLogService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class TqdianbiaoSyncLogServiceImpl implements TqdianbiaoSyncLogService { |
| | | |
| | | private final TqdianbiaoSyncLogMapper syncLogMapper; |
| | | |
| | | @Override |
| | | public void logSuccess(String syncType, String windowStart, String windowEnd, int recordCount) { |
| | | TqdianbiaoSyncLog log = new TqdianbiaoSyncLog(); |
| | | log.setSyncType(syncType); |
| | | log.setWindowStart(windowStart); |
| | | log.setWindowEnd(windowEnd); |
| | | log.setStatus("success"); |
| | | log.setRecordCount(recordCount); |
| | | log.setApiCallCount(1); |
| | | syncLogMapper.insert(log); |
| | | } |
| | | |
| | | @Override |
| | | public void logFailure(String syncType, String windowStart, String windowEnd, String errorMsg) { |
| | | TqdianbiaoSyncLog log = new TqdianbiaoSyncLog(); |
| | | log.setSyncType(syncType); |
| | | log.setWindowStart(windowStart); |
| | | log.setWindowEnd(windowEnd); |
| | | log.setStatus("fail"); |
| | | log.setRecordCount(0); |
| | | log.setApiCallCount(1); |
| | | log.setErrorMsg(errorMsg != null && errorMsg.length() > 500 ? errorMsg.substring(0, 500) : errorMsg); |
| | | syncLogMapper.insert(log); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.task; |
| | | |
| | | import com.ruoyi.http.config.TqdianbiaoConfig; |
| | | import com.ruoyi.http.service.TqdianbiaoCollectorSyncService; |
| | | import com.ruoyi.http.service.TqdianbiaoEleSyncService; |
| | | import com.ruoyi.http.service.TqdianbiaoMeterSyncService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** |
| | | * 天å¯çµè¡¨å®æ¶åæ¥ï¼ä»
忥ééå¨ãçµè¡¨ãå°æ¶çµéï¼ç»è®¡ç±å°æ¶ç´¯ç§¯è®¡ç®ï¼ |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | | @RequiredArgsConstructor |
| | | public class TqdianbiaoSyncTask { |
| | | |
| | | private final TqdianbiaoConfig config; |
| | | private final TqdianbiaoCollectorSyncService collectorSyncService; |
| | | private final TqdianbiaoMeterSyncService meterSyncService; |
| | | private final TqdianbiaoEleSyncService eleSyncService; |
| | | |
| | | //@Scheduled(cron = "30 5 * * * ?") |
| | | public void syncCollectors() { |
| | | if (!isEnabled()) return; |
| | | try { |
| | | collectorSyncService.syncCollectors(); |
| | | } catch (Exception e) { |
| | | log.error("ééå¨å®æ¶åæ¥å¼å¸¸", e); |
| | | } |
| | | } |
| | | |
| | | @Scheduled(cron = "30 7 * * * ?") |
| | | public void syncMeters() { |
| | | if (!isEnabled()) return; |
| | | try { |
| | | meterSyncService.syncMeters(); |
| | | } catch (Exception e) { |
| | | log.error("çµè¡¨å®æ¶åæ¥å¼å¸¸", e); |
| | | } |
| | | } |
| | | |
| | | @Scheduled(cron = "30 10 * * * ?") |
| | | public void syncHourEle() { |
| | | if (!isEnabled()) return; |
| | | try { |
| | | eleSyncService.syncHourData(); |
| | | } catch (Exception e) { |
| | | log.error("å°æ¶çµé宿¶åæ¥å¼å¸¸", e); |
| | | } |
| | | } |
| | | |
| | | private boolean isEnabled() { |
| | | return config.getSync() != null && Boolean.TRUE.equals(config.getSync().getEnabled()); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.util; |
| | | |
| | | import com.ruoyi.http.vo.StatisticEleRecordVo; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDate; |
| | | import java.time.YearMonth; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.Comparator; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.function.Function; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * çµéç»è®¡èåå·¥å
·ï¼åºäºå°æ¶æ°æ®å䏿±æ»ï¼ |
| | | */ |
| | | public final class StatisticEleAggregateUtil { |
| | | |
| | | private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyyMMdd"); |
| | | private static final int CONSUMPTION_SCALE = StatisticEleReadingUtil.CONSUMPTION_SCALE; |
| | | private static final int SUMMARY_SCALE = 2; |
| | | |
| | | private StatisticEleAggregateUtil() { |
| | | } |
| | | |
| | | public static final Function<String, String> HOUR_TO_DAY = tk -> tk != null && tk.length() >= 8 ? tk.substring(0, 8) : null; |
| | | public static final Function<String, String> HOUR_TO_HOUR = tk -> normalizeHourKey(tk); |
| | | public static final Function<String, String> HOUR_TO_MONTH = tk -> tk != null && tk.length() >= 6 ? tk.substring(0, 6) : null; |
| | | public static final Function<String, String> HOUR_TO_YEAR = tk -> tk != null && tk.length() >= 4 ? tk.substring(0, 4) : null; |
| | | public static final Function<String, String> HOUR_TO_QUARTER = tk -> { |
| | | String monthKey = HOUR_TO_MONTH.apply(tk); |
| | | return monthKey != null ? toQuarterKey(monthKey) : null; |
| | | }; |
| | | public static final Function<String, String> HOUR_TO_WEEK = StatisticEleAnalyticsUtil.HOUR_TO_WEEK; |
| | | |
| | | /** |
| | | * ææ¶é´æ¡¶æ±æ»ï¼å¤çµè¡¨åå¹¶ï¼ç¨äºå¾è¡¨ï¼ |
| | | */ |
| | | public static List<StatisticEleRecordVo> aggregateHourToBuckets( |
| | | List<StatisticEleRecordVo> hourRecords, Function<String, String> bucketFn) { |
| | | Map<String, StatisticEleRecordVo> map = new LinkedHashMap<>(); |
| | | for (StatisticEleRecordVo record : hourRecords) { |
| | | String bucket = bucketFn.apply(record.getTimeKey()); |
| | | if (bucket == null) { |
| | | continue; |
| | | } |
| | | mergeInto(map, bucket, null, record); |
| | | } |
| | | return sorted(map); |
| | | } |
| | | |
| | | /** |
| | | * ææ¶é´æ¡¶+çµè¡¨æ±æ»ï¼ç¨äºæç»ï¼ |
| | | */ |
| | | public static List<StatisticEleRecordVo> aggregateHourPerMeter( |
| | | List<StatisticEleRecordVo> hourRecords, Function<String, String> bucketFn) { |
| | | Map<String, StatisticEleRecordVo> map = new LinkedHashMap<>(); |
| | | for (StatisticEleRecordVo record : hourRecords) { |
| | | String bucket = bucketFn.apply(record.getTimeKey()); |
| | | if (bucket == null || record.getMeterId() == null) { |
| | | continue; |
| | | } |
| | | String key = bucket + "_" + record.getMeterId(); |
| | | mergeInto(map, key, bucket, record); |
| | | StatisticEleRecordVo agg = map.get(key); |
| | | agg.setMeterId(record.getMeterId()); |
| | | mergeMeterInfo(agg, record); |
| | | } |
| | | return sorted(map); |
| | | } |
| | | |
| | | private static void mergeMeterInfo(StatisticEleRecordVo agg, StatisticEleRecordVo record) { |
| | | if (hasText(record.getMeterName())) { |
| | | agg.setMeterName(record.getMeterName()); |
| | | } |
| | | if (hasText(record.getAddress())) { |
| | | agg.setAddress(record.getAddress()); |
| | | } |
| | | mergeTimeRange(agg, record); |
| | | } |
| | | |
| | | private static void mergeTimeRange(StatisticEleRecordVo agg, StatisticEleRecordVo record) { |
| | | if (hasText(record.getStartTime())) { |
| | | if (!hasText(agg.getStartTime()) || record.getStartTime().compareTo(agg.getStartTime()) < 0) { |
| | | agg.setStartTime(record.getStartTime()); |
| | | } |
| | | } |
| | | if (hasText(record.getEndTime())) { |
| | | if (!hasText(agg.getEndTime()) || record.getEndTime().compareTo(agg.getEndTime()) > 0) { |
| | | agg.setEndTime(record.getEndTime()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** æ±æ»åè¥èµ·æ¢æ¶é´ä¸ºç©ºï¼æ timeKey æ¨å¯¼ */ |
| | | private static void fillTimeRangeIfEmpty(StatisticEleRecordVo vo) { |
| | | if (hasText(vo.getStartTime()) && hasText(vo.getEndTime())) { |
| | | return; |
| | | } |
| | | String timeKey = vo.getTimeKey(); |
| | | if (!hasText(timeKey)) { |
| | | return; |
| | | } |
| | | if (timeKey.contains("Q")) { |
| | | String[] parts = timeKey.split("Q"); |
| | | if (parts.length != 2) { |
| | | return; |
| | | } |
| | | int year = Integer.parseInt(parts[0]); |
| | | int quarter = Integer.parseInt(parts[1]); |
| | | int startMonth = (quarter - 1) * 3 + 1; |
| | | int endMonth = startMonth + 2; |
| | | YearMonth endYm = YearMonth.of(year, endMonth); |
| | | vo.setStartTime(String.format("%04d-%02d-01 00:00:00", year, startMonth)); |
| | | vo.setEndTime(String.format("%04d-%02d-%02d 23:59:59", year, endMonth, endYm.lengthOfMonth())); |
| | | return; |
| | | } |
| | | if (timeKey.length() == 4) { |
| | | vo.setStartTime(timeKey + "-01-01 00:00:00"); |
| | | vo.setEndTime(timeKey + "-12-31 23:59:59"); |
| | | return; |
| | | } |
| | | if (timeKey.length() == 6) { |
| | | YearMonth ym = YearMonth.parse(timeKey, DateTimeFormatter.ofPattern("yyyyMM")); |
| | | vo.setStartTime(String.format("%04d-%02d-01 00:00:00", ym.getYear(), ym.getMonthValue())); |
| | | vo.setEndTime(String.format("%04d-%02d-%02d 23:59:59", |
| | | ym.getYear(), ym.getMonthValue(), ym.lengthOfMonth())); |
| | | return; |
| | | } |
| | | if (timeKey.length() >= 8) { |
| | | String day = timeKey.substring(0, 8); |
| | | vo.setStartTime(toDateTime(day, "00:00:00")); |
| | | vo.setEndTime(toDateTime(day, "23:59:59")); |
| | | } |
| | | } |
| | | |
| | | private static String toDateTime(String yyyyMMdd, String time) { |
| | | return yyyyMMdd.substring(0, 4) + "-" |
| | | + yyyyMMdd.substring(4, 6) + "-" |
| | | + yyyyMMdd.substring(6, 8) + " " + time; |
| | | } |
| | | |
| | | private static boolean hasText(String value) { |
| | | return value != null && !value.isBlank(); |
| | | } |
| | | |
| | | /** |
| | | * å
¼å®¹ï¼æ timeKey ç´æ¥æ±æ» |
| | | */ |
| | | public static List<StatisticEleRecordVo> aggregateByTimeKey(List<StatisticEleRecordVo> records) { |
| | | Map<String, StatisticEleRecordVo> map = new LinkedHashMap<>(); |
| | | for (StatisticEleRecordVo record : records) { |
| | | String key = record.getTimeKey(); |
| | | if (key == null) { |
| | | continue; |
| | | } |
| | | mergeInto(map, key, key, record); |
| | | } |
| | | return sorted(map); |
| | | } |
| | | |
| | | public static String toQuarterKey(String monthOrDayKey) { |
| | | if (monthOrDayKey == null || monthOrDayKey.length() < 6) { |
| | | return null; |
| | | } |
| | | int year = Integer.parseInt(monthOrDayKey.substring(0, 4)); |
| | | int month = Integer.parseInt(monthOrDayKey.substring(4, 6)); |
| | | int quarter = (month - 1) / 3 + 1; |
| | | return year + "Q" + quarter; |
| | | } |
| | | |
| | | /** |
| | | * ç»è®¡ç»´åº¦ -> å°æ¶ time_key æ¥è¯¢èå´ |
| | | */ |
| | | public static HourRange toHourQueryRange(String dimension, String startTime, String endTime) { |
| | | return switch (dimension) { |
| | | case "hour" -> new HourRange(startTime, endTime); |
| | | case "day" -> new HourRange(startTime + "00", endTime + "23"); |
| | | case "week" -> new HourRange(startTime + "00", endTime + "23"); |
| | | case "month" -> new HourRange(startTime + "0100", endTime + lastDayOfMonth(endTime) + "23"); |
| | | case "year" -> new HourRange(startTime + "010100", endTime + "123123"); |
| | | case "quarter" -> new HourRange( |
| | | startTime.length() >= 8 ? startTime.substring(0, 8) + "00" : startTime + "00", |
| | | endTime.length() >= 8 ? endTime.substring(0, 8) + "23" : endTime + "23"); |
| | | default -> throw new IllegalArgumentException("䏿¯æç维度: " + dimension); |
| | | }; |
| | | } |
| | | |
| | | public static HourRange yesterdayHourRange() { |
| | | String day = LocalDate.now().minusDays(1).format(DAY_FMT); |
| | | return new HourRange(normalizeQueryStartTimeKey(day + "00"), normalizeQueryEndTimeKey(day + "23")); |
| | | } |
| | | |
| | | /** æ¥è¯¢èµ·å§ time_keyï¼ç»ä¸ 12 ä½ï¼å
¼å®¹ 10 ä½å°æ¶é®ï¼ */ |
| | | public static String normalizeQueryStartTimeKey(String timeKey) { |
| | | if (timeKey == null || timeKey.isBlank()) { |
| | | return timeKey; |
| | | } |
| | | if (timeKey.length() == 8) { |
| | | return timeKey + "0000"; |
| | | } |
| | | if (timeKey.length() == 10) { |
| | | return timeKey + "00"; |
| | | } |
| | | return timeKey.length() > 12 ? timeKey.substring(0, 12) : timeKey; |
| | | } |
| | | |
| | | /** æ¥è¯¢ç»æ time_keyï¼ç»ä¸ 12 ä½ï¼å
¼å®¹ 10 ä½å°æ¶é®ï¼ */ |
| | | public static String normalizeQueryEndTimeKey(String timeKey) { |
| | | if (timeKey == null || timeKey.isBlank()) { |
| | | return timeKey; |
| | | } |
| | | if (timeKey.length() == 8) { |
| | | return timeKey + "2359"; |
| | | } |
| | | if (timeKey.length() == 10) { |
| | | return timeKey + "59"; |
| | | } |
| | | return timeKey.length() > 12 ? timeKey.substring(0, 12) : timeKey; |
| | | } |
| | | |
| | | /** æç»è®°å½æ»ç¨çµéï¼ä¸æ°æ®éé页æ±åæ¹å¼ä¸è´ï¼ */ |
| | | public static double sumRecordsTotal(List<StatisticEleRecordVo> records) { |
| | | BigDecimal total = records.stream() |
| | | .map(StatisticEleRecordVo::getTotalConsumption) |
| | | .filter(v -> v != null) |
| | | .map(BigDecimal::valueOf) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | return roundSummary(total); |
| | | } |
| | | |
| | | public static Function<String, String> bucketFn(String dimension) { |
| | | return switch (dimension) { |
| | | case "hour" -> HOUR_TO_HOUR; |
| | | case "day" -> HOUR_TO_DAY; |
| | | case "week" -> HOUR_TO_WEEK; |
| | | case "month" -> HOUR_TO_MONTH; |
| | | case "quarter" -> HOUR_TO_QUARTER; |
| | | case "year" -> HOUR_TO_YEAR; |
| | | default -> StatisticEleAggregateUtil::normalizeHourKey; |
| | | }; |
| | | } |
| | | |
| | | public static String normalizeHourKey(String timeKey) { |
| | | if (timeKey == null) { |
| | | return null; |
| | | } |
| | | return timeKey.length() >= 10 ? timeKey.substring(0, 10) : timeKey; |
| | | } |
| | | |
| | | public static StatisticEleSummaryMetrics calcMetrics(List<StatisticEleRecordVo> buckets) { |
| | | StatisticEleSummaryMetrics metrics = new StatisticEleSummaryMetrics(); |
| | | metrics.setRecordCount(buckets.size()); |
| | | if (buckets.isEmpty()) { |
| | | metrics.setTotalConsumption(0.0); |
| | | metrics.setAvgConsumption(0.0); |
| | | metrics.setMaxConsumption(0.0); |
| | | metrics.setMinConsumption(0.0); |
| | | return metrics; |
| | | } |
| | | List<BigDecimal> values = buckets.stream() |
| | | .map(StatisticEleRecordVo::getTotalConsumption) |
| | | .filter(v -> v != null) |
| | | .map(BigDecimal::valueOf) |
| | | .collect(Collectors.toList()); |
| | | BigDecimal total = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | metrics.setTotalConsumption(roundSummary(total)); |
| | | metrics.setAvgConsumption(roundSummary(total.divide( |
| | | BigDecimal.valueOf(values.size()), CONSUMPTION_SCALE, RoundingMode.HALF_UP))); |
| | | metrics.setMaxConsumption(roundSummary(values.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO))); |
| | | metrics.setMinConsumption(roundSummary(values.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO))); |
| | | return metrics; |
| | | } |
| | | |
| | | private static void mergeInto(Map<String, StatisticEleRecordVo> map, String mapKey, |
| | | String timeKey, StatisticEleRecordVo record) { |
| | | StatisticEleRecordVo agg = map.computeIfAbsent(mapKey, k -> { |
| | | StatisticEleRecordVo vo = new StatisticEleRecordVo(); |
| | | vo.setTimeKey(timeKey != null ? timeKey : k); |
| | | vo.setTotalConsumption(0.0); |
| | | vo.setSharpConsumption(0.0); |
| | | vo.setPeakConsumption(0.0); |
| | | vo.setFlatConsumption(0.0); |
| | | vo.setValleyConsumption(0.0); |
| | | return vo; |
| | | }); |
| | | agg.setTotalConsumption(add(agg.getTotalConsumption(), record.getTotalConsumption())); |
| | | agg.setSharpConsumption(add(agg.getSharpConsumption(), record.getSharpConsumption())); |
| | | agg.setPeakConsumption(add(agg.getPeakConsumption(), record.getPeakConsumption())); |
| | | agg.setFlatConsumption(add(agg.getFlatConsumption(), record.getFlatConsumption())); |
| | | agg.setValleyConsumption(add(agg.getValleyConsumption(), record.getValleyConsumption())); |
| | | } |
| | | |
| | | private static List<StatisticEleRecordVo> sorted(Map<String, StatisticEleRecordVo> map) { |
| | | return map.values().stream() |
| | | .peek(vo -> { |
| | | normalizeConsumptions(vo); |
| | | fillTimeRangeIfEmpty(vo); |
| | | }) |
| | | .sorted(Comparator.comparing(StatisticEleRecordVo::getTimeKey)) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | private static String lastDayOfMonth(String yyyyMM) { |
| | | YearMonth ym = YearMonth.parse(yyyyMM, DateTimeFormatter.ofPattern("yyyyMM")); |
| | | return yyyyMM + String.format("%02d", ym.lengthOfMonth()); |
| | | } |
| | | |
| | | private static Double add(Double a, Double b) { |
| | | BigDecimal sum = BigDecimal.valueOf(a == null ? 0.0 : a) |
| | | .add(BigDecimal.valueOf(b == null ? 0.0 : b)); |
| | | return roundConsumption(sum); |
| | | } |
| | | |
| | | /** ç»ä¸çµéåæ®µç²¾åº¦ï¼æç»å±ç¤ºï¼ */ |
| | | public static void normalizeConsumptions(StatisticEleRecordVo vo) { |
| | | if (vo == null) { |
| | | return; |
| | | } |
| | | vo.setTotalConsumption(roundConsumption(vo.getTotalConsumption())); |
| | | vo.setSharpConsumption(roundConsumption(vo.getSharpConsumption())); |
| | | vo.setPeakConsumption(roundConsumption(vo.getPeakConsumption())); |
| | | vo.setFlatConsumption(roundConsumption(vo.getFlatConsumption())); |
| | | vo.setValleyConsumption(roundConsumption(vo.getValleyConsumption())); |
| | | } |
| | | |
| | | public static void normalizeConsumptions(List<StatisticEleRecordVo> records) { |
| | | if (records == null) { |
| | | return; |
| | | } |
| | | records.forEach(vo -> normalizeConsumptions(vo)); |
| | | } |
| | | |
| | | private static Double roundConsumption(Double value) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | return roundConsumption(BigDecimal.valueOf(value)); |
| | | } |
| | | |
| | | private static Double roundConsumption(BigDecimal value) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | return value.setScale(CONSUMPTION_SCALE, RoundingMode.HALF_UP).doubleValue(); |
| | | } |
| | | |
| | | private static double roundSummary(BigDecimal value) { |
| | | return value.setScale(SUMMARY_SCALE, RoundingMode.HALF_UP).doubleValue(); |
| | | } |
| | | |
| | | public record HourRange(String startTime, String endTime) {} |
| | | |
| | | public static class StatisticEleSummaryMetrics { |
| | | private Double totalConsumption; |
| | | private Double avgConsumption; |
| | | private Double maxConsumption; |
| | | private Double minConsumption; |
| | | private Integer recordCount; |
| | | |
| | | public Double getTotalConsumption() { return totalConsumption; } |
| | | public void setTotalConsumption(Double v) { this.totalConsumption = v; } |
| | | public Double getAvgConsumption() { return avgConsumption; } |
| | | public void setAvgConsumption(Double v) { this.avgConsumption = v; } |
| | | public Double getMaxConsumption() { return maxConsumption; } |
| | | public void setMaxConsumption(Double v) { this.maxConsumption = v; } |
| | | public Double getMinConsumption() { return minConsumption; } |
| | | public void setMinConsumption(Double v) { this.minConsumption = v; } |
| | | public Integer getRecordCount() { return recordCount; } |
| | | public void setRecordCount(Integer v) { this.recordCount = v; } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.util; |
| | | |
| | | import com.ruoyi.http.vo.StatisticEleComparisonVo; |
| | | import com.ruoyi.http.vo.StatisticEleRecordVo; |
| | | import com.ruoyi.http.vo.StatisticEleSplitItemVo; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.DayOfWeek; |
| | | import java.time.LocalDate; |
| | | import java.time.YearMonth; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.time.temporal.WeekFields; |
| | | import java.util.ArrayList; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | import java.util.function.Function; |
| | | |
| | | /** |
| | | * è½è综ååæè®¡ç® |
| | | */ |
| | | public final class StatisticEleAnalyticsUtil { |
| | | |
| | | private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyyMMdd"); |
| | | private static final int SUMMARY_SCALE = 2; |
| | | /** ç½ç 08:00-19:59 */ |
| | | private static final int DAY_SHIFT_START = 8; |
| | | private static final int DAY_SHIFT_END = 19; |
| | | |
| | | private StatisticEleAnalyticsUtil() { |
| | | } |
| | | |
| | | public static final Function<String, String> HOUR_TO_WEEK = tk -> { |
| | | if (tk == null || tk.length() < 8) { |
| | | return null; |
| | | } |
| | | LocalDate date = LocalDate.parse(tk.substring(0, 8), DAY_FMT); |
| | | WeekFields wf = WeekFields.of(Locale.CHINA); |
| | | int week = date.get(wf.weekOfWeekBasedYear()); |
| | | int year = date.get(wf.weekBasedYear()); |
| | | return String.format("%04dW%02d", year, week); |
| | | }; |
| | | |
| | | public static Function<String, String> trendBucketFn(String granularity) { |
| | | return switch (granularity) { |
| | | case "hour" -> StatisticEleAggregateUtil.HOUR_TO_HOUR; |
| | | case "week" -> HOUR_TO_WEEK; |
| | | case "month" -> StatisticEleAggregateUtil.HOUR_TO_MONTH; |
| | | case "year" -> StatisticEleAggregateUtil.HOUR_TO_YEAR; |
| | | default -> StatisticEleAggregateUtil.HOUR_TO_DAY; |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * å¨æç´¯è®¡æ¥èªæ±æ»æ¡¶ï¼æå¼/åå¼å§ç»æ¥èªå°æ¶æ¡¶ã |
| | | */ |
| | | public static StatisticEleAggregateUtil.StatisticEleSummaryMetrics calcCombinedMetrics( |
| | | List<StatisticEleRecordVo> hourRecords, |
| | | List<StatisticEleRecordVo> periodChartRecords) { |
| | | List<StatisticEleRecordVo> hourlyBuckets = StatisticEleAggregateUtil.aggregateHourToBuckets( |
| | | hourRecords, StatisticEleAggregateUtil.HOUR_TO_HOUR); |
| | | StatisticEleAggregateUtil.StatisticEleSummaryMetrics hourMetrics = |
| | | StatisticEleAggregateUtil.calcMetrics(hourlyBuckets); |
| | | StatisticEleAggregateUtil.StatisticEleSummaryMetrics periodMetrics = |
| | | StatisticEleAggregateUtil.calcMetrics(periodChartRecords); |
| | | |
| | | StatisticEleAggregateUtil.StatisticEleSummaryMetrics result = |
| | | new StatisticEleAggregateUtil.StatisticEleSummaryMetrics(); |
| | | result.setRecordCount(periodMetrics.getRecordCount()); |
| | | result.setTotalConsumption(hourMetrics.getTotalConsumption()); |
| | | result.setAvgConsumption(hourMetrics.getAvgConsumption()); |
| | | result.setMaxConsumption(hourMetrics.getMaxConsumption()); |
| | | result.setMinConsumption(hourMetrics.getMinConsumption()); |
| | | return result; |
| | | } |
| | | |
| | | public static double calcLoadRate(Double avgHourly, Double maxHourly) { |
| | | if (avgHourly == null || maxHourly == null || maxHourly <= 0) { |
| | | return 0.0; |
| | | } |
| | | return roundSummary(BigDecimal.valueOf(avgHourly) |
| | | .divide(BigDecimal.valueOf(maxHourly), SUMMARY_SCALE + 2, RoundingMode.HALF_UP) |
| | | .multiply(BigDecimal.valueOf(100))); |
| | | } |
| | | |
| | | public static List<StatisticEleSplitItemVo> calcPeriodSplits(List<StatisticEleRecordVo> hourRecords) { |
| | | double sharp = sumField(hourRecords, StatisticEleRecordVo::getSharpConsumption); |
| | | double peak = sumField(hourRecords, StatisticEleRecordVo::getPeakConsumption); |
| | | double flat = sumField(hourRecords, StatisticEleRecordVo::getFlatConsumption); |
| | | double valley = sumField(hourRecords, StatisticEleRecordVo::getValleyConsumption); |
| | | double total = sharp + peak + flat + valley; |
| | | if (total <= 0) { |
| | | total = sumField(hourRecords, StatisticEleRecordVo::getTotalConsumption); |
| | | } |
| | | List<StatisticEleSplitItemVo> items = new ArrayList<>(); |
| | | addSplitItem(items, "å°", sharp, total); |
| | | addSplitItem(items, "å³°", peak, total); |
| | | addSplitItem(items, "å¹³", flat, total); |
| | | addSplitItem(items, "è°·", valley, total); |
| | | return items.stream().filter(i -> i.getConsumption() != null && i.getConsumption() > 0).toList(); |
| | | } |
| | | |
| | | public static List<StatisticEleSplitItemVo> calcShiftSplits(List<StatisticEleRecordVo> hourRecords) { |
| | | Map<String, Double> map = new LinkedHashMap<>(); |
| | | map.put("ç½ç", 0.0); |
| | | map.put("å¤ç", 0.0); |
| | | for (StatisticEleRecordVo record : hourRecords) { |
| | | int hour = parseHour(record.getTimeKey()); |
| | | if (hour < 0) { |
| | | continue; |
| | | } |
| | | double val = safe(record.getTotalConsumption()); |
| | | if (hour >= DAY_SHIFT_START && hour <= DAY_SHIFT_END) { |
| | | map.merge("ç½ç", val, Double::sum); |
| | | } else { |
| | | map.merge("å¤ç", val, Double::sum); |
| | | } |
| | | } |
| | | return toSplitItems(map); |
| | | } |
| | | |
| | | public static List<StatisticEleSplitItemVo> calcDayTypeSplits(List<StatisticEleRecordVo> hourRecords) { |
| | | Map<String, Double> map = new LinkedHashMap<>(); |
| | | map.put("工使¥", 0.0); |
| | | map.put("伿¯æ¥", 0.0); |
| | | for (StatisticEleRecordVo record : hourRecords) { |
| | | LocalDate date = parseDate(record.getTimeKey()); |
| | | if (date == null) { |
| | | continue; |
| | | } |
| | | double val = safe(record.getTotalConsumption()); |
| | | DayOfWeek dow = date.getDayOfWeek(); |
| | | if (dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY) { |
| | | map.merge("伿¯æ¥", val, Double::sum); |
| | | } else { |
| | | map.merge("工使¥", val, Double::sum); |
| | | } |
| | | } |
| | | return toSplitItems(map); |
| | | } |
| | | |
| | | public static StatisticEleComparisonVo buildComparison( |
| | | String type, String label, Double currentTotal, Double compareTotal) { |
| | | StatisticEleComparisonVo vo = new StatisticEleComparisonVo(); |
| | | vo.setType(type); |
| | | vo.setLabel(label); |
| | | vo.setCurrentTotal(round(currentTotal)); |
| | | vo.setCompareTotal(round(compareTotal)); |
| | | double delta = round(safe(currentTotal) - safe(compareTotal)); |
| | | vo.setDelta(delta); |
| | | if (compareTotal != null && compareTotal > 0) { |
| | | vo.setChangeRate(roundSummary(BigDecimal.valueOf(delta) |
| | | .divide(BigDecimal.valueOf(compareTotal), SUMMARY_SCALE + 2, RoundingMode.HALF_UP) |
| | | .multiply(BigDecimal.valueOf(100)))); |
| | | } else { |
| | | vo.setChangeRate(currentTotal != null && currentTotal > 0 ? 100.0 : 0.0); |
| | | } |
| | | return vo; |
| | | } |
| | | |
| | | public record DateBounds(LocalDate start, LocalDate end) {} |
| | | |
| | | public static DateBounds resolveBounds(String dimension, String startTime, String endTime) { |
| | | LocalDate start = resolveStartDate(dimension, startTime); |
| | | LocalDate end = resolveEndDate(dimension, endTime); |
| | | if (start.isAfter(end)) { |
| | | LocalDate tmp = start; |
| | | start = end; |
| | | end = tmp; |
| | | } |
| | | return new DateBounds(start, end); |
| | | } |
| | | |
| | | public static DateBounds shiftChain(DateBounds bounds) { |
| | | long days = ChronoUnit.DAYS.between(bounds.start(), bounds.end()) + 1; |
| | | return new DateBounds(bounds.start().minusDays(days), bounds.start().minusDays(1)); |
| | | } |
| | | |
| | | public static DateBounds shiftYoy(DateBounds bounds) { |
| | | return new DateBounds(bounds.start().minusYears(1), bounds.end().minusYears(1)); |
| | | } |
| | | |
| | | public static String defaultTrendGranularity(String dimension, boolean singleDay) { |
| | | if (singleDay) { |
| | | return "hour"; |
| | | } |
| | | return switch (dimension) { |
| | | case "week", "day" -> "day"; |
| | | case "month", "quarter" -> "week"; |
| | | case "year" -> "month"; |
| | | default -> "day"; |
| | | }; |
| | | } |
| | | |
| | | private static LocalDate resolveStartDate(String dimension, String startTime) { |
| | | return switch (dimension) { |
| | | case "year" -> LocalDate.of(Integer.parseInt(startTime), 1, 1); |
| | | case "month" -> YearMonth.parse(startTime, DateTimeFormatter.ofPattern("yyyyMM")).atDay(1); |
| | | default -> LocalDate.parse(normalizeDayKey(startTime), DAY_FMT); |
| | | }; |
| | | } |
| | | |
| | | private static LocalDate resolveEndDate(String dimension, String endTime) { |
| | | return switch (dimension) { |
| | | case "year" -> LocalDate.of(Integer.parseInt(endTime), 12, 31); |
| | | case "month" -> YearMonth.parse(endTime, DateTimeFormatter.ofPattern("yyyyMM")).atEndOfMonth(); |
| | | default -> LocalDate.parse(normalizeDayKey(endTime), DAY_FMT); |
| | | }; |
| | | } |
| | | |
| | | private static String normalizeDayKey(String timeKey) { |
| | | if (timeKey == null) { |
| | | return LocalDate.now().format(DAY_FMT); |
| | | } |
| | | if (timeKey.length() == 4) { |
| | | return timeKey + "0101"; |
| | | } |
| | | if (timeKey.length() == 6) { |
| | | return timeKey + "01"; |
| | | } |
| | | return timeKey.length() >= 8 ? timeKey.substring(0, 8) : timeKey; |
| | | } |
| | | |
| | | private static List<StatisticEleSplitItemVo> toSplitItems(Map<String, Double> map) { |
| | | double total = map.values().stream().mapToDouble(v -> v).sum(); |
| | | List<StatisticEleSplitItemVo> items = new ArrayList<>(); |
| | | map.forEach((name, val) -> addSplitItem(items, name, val, total)); |
| | | return items; |
| | | } |
| | | |
| | | private static void addSplitItem(List<StatisticEleSplitItemVo> items, String name, double val, double total) { |
| | | if (val <= 0) { |
| | | return; |
| | | } |
| | | StatisticEleSplitItemVo item = new StatisticEleSplitItemVo(); |
| | | item.setName(name); |
| | | item.setConsumption(round(val)); |
| | | item.setRatio(total > 0 ? roundSummary(BigDecimal.valueOf(val) |
| | | .divide(BigDecimal.valueOf(total), SUMMARY_SCALE + 2, RoundingMode.HALF_UP) |
| | | .multiply(BigDecimal.valueOf(100))) : 0.0); |
| | | items.add(item); |
| | | } |
| | | |
| | | private static double sumField(List<StatisticEleRecordVo> records, |
| | | Function<StatisticEleRecordVo, Double> getter) { |
| | | return records.stream().map(getter).mapToDouble(StatisticEleAnalyticsUtil::safe).sum(); |
| | | } |
| | | |
| | | private static int parseHour(String timeKey) { |
| | | if (timeKey == null || timeKey.length() < 10) { |
| | | return -1; |
| | | } |
| | | try { |
| | | return Integer.parseInt(timeKey.substring(8, 10)); |
| | | } catch (NumberFormatException e) { |
| | | return -1; |
| | | } |
| | | } |
| | | |
| | | private static LocalDate parseDate(String timeKey) { |
| | | if (timeKey == null || timeKey.length() < 8) { |
| | | return null; |
| | | } |
| | | try { |
| | | return LocalDate.parse(timeKey.substring(0, 8), DAY_FMT); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private static double safe(Double v) { |
| | | return v == null ? 0.0 : v; |
| | | } |
| | | |
| | | private static double round(Double v) { |
| | | if (v == null) { |
| | | return 0.0; |
| | | } |
| | | return roundSummary(BigDecimal.valueOf(v)); |
| | | } |
| | | |
| | | private static double roundSummary(BigDecimal value) { |
| | | return value.setScale(SUMMARY_SCALE, RoundingMode.HALF_UP).doubleValue(); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.util; |
| | | |
| | | import com.alibaba.fastjson2.JSON; |
| | | import com.alibaba.fastjson2.JSONArray; |
| | | import com.alibaba.fastjson2.JSONObject; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.http.pojo.TqdianbiaoEleRecord; |
| | | import com.ruoyi.http.vo.StatisticEleRecordVo; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDateTime; |
| | | import java.util.ArrayList; |
| | | import java.util.Comparator; |
| | | import java.util.List; |
| | | |
| | | public final class StatisticEleParseUtil { |
| | | |
| | | private StatisticEleParseUtil() { |
| | | } |
| | | |
| | | public static List<TqdianbiaoEleRecord> parseToEntities(String raw, String dimension) { |
| | | JSONObject root = JSON.parseObject(raw); |
| | | if (root == null) { |
| | | throw new ServiceException("çµè¡¨æ¥å£è¿å为空"); |
| | | } |
| | | if (root.getIntValue("status") != 1) { |
| | | String msg = root.getString("msg"); |
| | | throw new ServiceException("çµè¡¨æ¥å£è¿åå¼å¸¸: " + (msg != null ? msg : raw)); |
| | | } |
| | | JSONObject data = root.getJSONObject("data"); |
| | | if (data == null || data.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | List<TqdianbiaoEleRecord> list = new ArrayList<>(); |
| | | for (String timeKey : data.keySet()) { |
| | | JSONArray records = data.getJSONArray(timeKey); |
| | | if (records == null) { |
| | | continue; |
| | | } |
| | | for (int i = 0; i < records.size(); i++) { |
| | | JSONObject rec = records.getJSONObject(i); |
| | | TqdianbiaoEleRecord entity = new TqdianbiaoEleRecord(); |
| | | entity.setMeterId(rec.getLong("mid")); |
| | | entity.setDimension(dimension); |
| | | entity.setTimeKey(timeKey); |
| | | entity.setReadingMethod("sync"); |
| | | Integer ratio = rec.getInteger("r"); |
| | | entity.setRatio(ratio); |
| | | entity.setStartTime(rec.getString("st")); |
| | | entity.setEndTime(rec.getString("et")); |
| | | |
| | | JSONArray sArr = rec.getJSONArray("s"); |
| | | JSONArray eArr = rec.getJSONArray("e"); |
| | | JSONArray dArr = rec.getJSONArray("d"); |
| | | entity.setStartReading(StatisticEleReadingUtil.formatReadingArray(sArr)); |
| | | entity.setEndReading(StatisticEleReadingUtil.formatReadingArray(eArr)); |
| | | entity.setPrevReading(StatisticEleReadingUtil.firstReading(sArr)); |
| | | entity.setCurrReading(StatisticEleReadingUtil.firstReading(eArr)); |
| | | |
| | | fillConsumption(entity, dArr, ratio, sArr, eArr); |
| | | entity.setSyncTime(now); |
| | | list.add(entity); |
| | | } |
| | | } |
| | | list.sort(Comparator.comparing(TqdianbiaoEleRecord::getTimeKey)); |
| | | return list; |
| | | } |
| | | |
| | | public static List<StatisticEleRecordVo> toVoList(List<TqdianbiaoEleRecord> entities) { |
| | | List<StatisticEleRecordVo> list = new ArrayList<>(); |
| | | for (TqdianbiaoEleRecord entity : entities) { |
| | | list.add(toVo(entity)); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | public static StatisticEleRecordVo toVo(TqdianbiaoEleRecord entity) { |
| | | StatisticEleRecordVo vo = new StatisticEleRecordVo(); |
| | | vo.setId(entity.getId()); |
| | | vo.setTimeKey(entity.getTimeKey()); |
| | | vo.setMeterId(entity.getMeterId()); |
| | | vo.setMeterName(entity.getMeterName()); |
| | | vo.setRatio(entity.getRatio()); |
| | | vo.setReadingMethod(entity.getReadingMethod()); |
| | | vo.setPrevReading(toDouble(entity.getPrevReading())); |
| | | vo.setCurrReading(toDouble(entity.getCurrReading())); |
| | | vo.setStartTime(entity.getStartTime()); |
| | | vo.setEndTime(entity.getEndTime()); |
| | | vo.setTotalConsumption(toDouble(entity.getTotalConsumption())); |
| | | vo.setSharpConsumption(toDouble(entity.getSharpConsumption())); |
| | | vo.setPeakConsumption(toDouble(entity.getPeakConsumption())); |
| | | vo.setFlatConsumption(toDouble(entity.getFlatConsumption())); |
| | | vo.setValleyConsumption(toDouble(entity.getValleyConsumption())); |
| | | vo.setStartReading(entity.getStartReading()); |
| | | vo.setEndReading(entity.getEndReading()); |
| | | return vo; |
| | | } |
| | | |
| | | private static void fillConsumption(TqdianbiaoEleRecord entity, JSONArray d, Integer ratio, |
| | | JSONArray sArr, JSONArray eArr) { |
| | | if (d != null && !d.isEmpty()) { |
| | | entity.setTotalConsumption(StatisticEleReadingUtil.calcConsumptionFromRaw(d.getBigDecimal(0), ratio)); |
| | | entity.setSharpConsumption(consumptionAt(d, 1, ratio)); |
| | | entity.setPeakConsumption(consumptionAt(d, 2, ratio)); |
| | | entity.setFlatConsumption(consumptionAt(d, 3, ratio)); |
| | | entity.setValleyConsumption(consumptionAt(d, 4, ratio)); |
| | | entity.setDeepValleyConsumption(consumptionAt(d, 5, ratio)); |
| | | } else { |
| | | BigDecimal rawTotal = StatisticEleReadingUtil.calcConsumption( |
| | | StatisticEleReadingUtil.firstReading(sArr), |
| | | StatisticEleReadingUtil.firstReading(eArr), |
| | | 1); |
| | | entity.setTotalConsumption(StatisticEleReadingUtil.calcConsumptionFromRaw(rawTotal, ratio)); |
| | | } |
| | | } |
| | | |
| | | private static BigDecimal consumptionAt(JSONArray d, int index, Integer ratio) { |
| | | if (d.size() <= index) { |
| | | return null; |
| | | } |
| | | return StatisticEleReadingUtil.calcConsumptionFromRaw(d.getBigDecimal(index), ratio); |
| | | } |
| | | |
| | | private static Double toDouble(BigDecimal value) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | return value.setScale(StatisticEleReadingUtil.CONSUMPTION_SCALE, RoundingMode.HALF_UP).doubleValue(); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.util; |
| | | |
| | | import com.alibaba.fastjson2.JSONArray; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | |
| | | /** |
| | | * çµé读æ°ä¸åçè®¡ç® |
| | | */ |
| | | public final class StatisticEleReadingUtil { |
| | | |
| | | public static final int CONSUMPTION_SCALE = 4; |
| | | |
| | | private StatisticEleReadingUtil() { |
| | | } |
| | | |
| | | public static BigDecimal firstReading(JSONArray arr) { |
| | | if (arr == null || arr.isEmpty()) { |
| | | return null; |
| | | } |
| | | return arr.getBigDecimal(0); |
| | | } |
| | | |
| | | public static BigDecimal calcConsumption(BigDecimal prev, BigDecimal curr, Integer ratio) { |
| | | if (prev == null || curr == null) { |
| | | return null; |
| | | } |
| | | BigDecimal diff = curr.subtract(prev); |
| | | return applyRatio(diff, ratio); |
| | | } |
| | | |
| | | public static BigDecimal calcConsumptionFromRaw(BigDecimal raw, Integer ratio) { |
| | | if (raw == null) { |
| | | return null; |
| | | } |
| | | return applyRatio(raw, ratio); |
| | | } |
| | | |
| | | public static BigDecimal applyRatio(BigDecimal value, Integer ratio) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | int r = ratio == null || ratio <= 0 ? 1 : ratio; |
| | | return value.multiply(BigDecimal.valueOf(r)).setScale(CONSUMPTION_SCALE, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | public static String formatReadingArray(JSONArray arr) { |
| | | if (arr == null || arr.isEmpty()) { |
| | | return ""; |
| | | } |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (int i = 0; i < arr.size(); i++) { |
| | | if (i > 0) { |
| | | sb.append("/"); |
| | | } |
| | | sb.append(arr.getDouble(i)); |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | public static BigDecimal parseFirstReading(String reading) { |
| | | if (reading == null || reading.isBlank()) { |
| | | return null; |
| | | } |
| | | int idx = reading.indexOf('/'); |
| | | String first = idx > 0 ? reading.substring(0, idx) : reading; |
| | | try { |
| | | return new BigDecimal(first.trim()); |
| | | } catch (NumberFormatException e) { |
| | | return null; |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.vo; |
| | | |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * è½è综ååæç»æ |
| | | */ |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class StatisticEleAnalyticsVo extends StatisticEleSummaryVo { |
| | | |
| | | /** è´è·ç = å°æ¶å¹³å ÷ å°æ¶æå¤§ */ |
| | | private Double loadRate; |
| | | |
| | | /** å³°å¹³è°·å°æå */ |
| | | private List<StatisticEleSplitItemVo> periodSplits; |
| | | |
| | | /** ç½ç/å¤çæå */ |
| | | private List<StatisticEleSplitItemVo> shiftSplits; |
| | | |
| | | /** 工使¥/伿¯æ¥æå */ |
| | | private List<StatisticEleSplitItemVo> dayTypeSplits; |
| | | |
| | | /** ç¯æ¯ */ |
| | | private StatisticEleComparisonVo chainComparison; |
| | | |
| | | /** 忝 */ |
| | | private StatisticEleComparisonVo yoyComparison; |
| | | |
| | | /** è¶å¿å¾ç²åº¦ */ |
| | | private String trendGranularity; |
| | | |
| | | /** è¶å¿å¾æ°æ®ï¼æ trendGranularity èåï¼ */ |
| | | private List<StatisticEleRecordVo> trendRecords; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.vo; |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * 忝/ç¯æ¯å¯¹æ¯ |
| | | */ |
| | | @Data |
| | | public class StatisticEleComparisonVo { |
| | | |
| | | /** 对æ¯ç±»åï¼chain-ç¯æ¯, yoy-忝 */ |
| | | private String type; |
| | | |
| | | /** å¯¹æ¯ææ ç¾ï¼å¦ã䏿ããå»å¹´åæã */ |
| | | private String label; |
| | | |
| | | /** å¯¹æ¯ææ»ç¨çµé(kWh) */ |
| | | private Double compareTotal; |
| | | |
| | | /** æ¬ææ»ç¨çµé(kWh) */ |
| | | private Double currentTotal; |
| | | |
| | | /** å·®å¼(kWh)ï¼æ¬æ-å¯¹æ¯æ */ |
| | | private Double delta; |
| | | |
| | | /** ååç(%) */ |
| | | private Double changeRate; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.vo; |
| | | |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * çµè¡¨ç»è®¡æ°æ®è®°å½ |
| | | */ |
| | | @Data |
| | | public class StatisticEleRecordVo { |
| | | |
| | | private Long id; |
| | | |
| | | @Excel(name = "æ¶é´æ è¯") |
| | | private String timeKey; |
| | | |
| | | @Excel(name = "çµè¡¨ID") |
| | | private Long meterId; |
| | | |
| | | @Excel(name = "çµè¡¨åç§°") |
| | | private String meterName; |
| | | |
| | | @Excel(name = "表å°å") |
| | | private String address; |
| | | |
| | | @Excel(name = "åç") |
| | | private Integer ratio; |
| | | |
| | | @Excel(name = "䏿¬¡çµé") |
| | | private Double prevReading; |
| | | |
| | | @Excel(name = "æ¬æ¬¡çµé") |
| | | private Double currReading; |
| | | |
| | | @Excel(name = "æ¬æ¬¡ç¨çµé(kWh)") |
| | | private Double totalConsumption; |
| | | |
| | | @Excel(name = "æè¡¨æ¹å¼") |
| | | private String readingMethod; |
| | | |
| | | @Excel(name = "å¼å§æ¶é´") |
| | | private String startTime; |
| | | |
| | | @Excel(name = "ç»ææ¶é´") |
| | | private String endTime; |
| | | |
| | | @Excel(name = "å°å³°(kWh)") |
| | | private Double sharpConsumption; |
| | | |
| | | @Excel(name = "å³°(kWh)") |
| | | private Double peakConsumption; |
| | | |
| | | @Excel(name = "å¹³(kWh)") |
| | | private Double flatConsumption; |
| | | |
| | | @Excel(name = "è°·(kWh)") |
| | | private Double valleyConsumption; |
| | | |
| | | @Excel(name = "èµ·å§è¯»æ°") |
| | | private String startReading; |
| | | |
| | | @Excel(name = "ç»æè¯»æ°") |
| | | private String endReading; |
| | | |
| | | /** å
¼å®¹å段ï¼é¡µé¢ä¸å±ç¤º */ |
| | | private String collectorNo; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.vo; |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * ç¨çµéæåé¡¹ï¼æ¶æ®µ/çæ¬¡/æ¥ç±»åçï¼ |
| | | */ |
| | | @Data |
| | | public class StatisticEleSplitItemVo { |
| | | |
| | | private String name; |
| | | |
| | | /** ç¨çµé(kWh) */ |
| | | private Double consumption; |
| | | |
| | | /** å æ¯(%) */ |
| | | private Double ratio; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.vo; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * çµè¡¨ç»è®¡æ±æ» |
| | | */ |
| | | @Data |
| | | public class StatisticEleSummaryVo { |
| | | |
| | | /** æ»ç¨çµé(kWh) */ |
| | | private Double totalConsumption; |
| | | |
| | | /** å¹³åç¨çµé(kWh) */ |
| | | private Double avgConsumption; |
| | | |
| | | /** æå¤§ç¨çµé(kWh) */ |
| | | private Double maxConsumption; |
| | | |
| | | /** æå°ç¨çµé(kWh) */ |
| | | private Double minConsumption; |
| | | |
| | | /** æ°æ®æ¡æ° */ |
| | | private Integer recordCount; |
| | | |
| | | /** å¾è¡¨æ°æ®ï¼ææ¶é´æ±æ»ï¼ */ |
| | | private List<StatisticEleRecordVo> chartRecords; |
| | | |
| | | /** æç»è®°å½ï¼æçµè¡¨ï¼ */ |
| | | private List<StatisticEleRecordVo> records; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.http.vo; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.util.Map; |
| | | |
| | | @Data |
| | | public class StatisticEleSyncStatusVo { |
| | | |
| | | private Integer meterCount; |
| | | |
| | | private Integer collectorCount; |
| | | |
| | | private Integer onlineCollectorCount; |
| | | |
| | | private Map<String, Long> recordCountByDimension; |
| | | |
| | | private Map<String, String> lastSyncTimeByType; |
| | | } |
| | |
| | | compressQuality: 0.5 # å缩质é(0.0-1.0) |
| | | knowledge: |
| | | one: D:\æ°ç大ç½ç´ ä¼ä¸äº§åä½ç³»è¯´æææ¡£.md |
| | | |
| | | tqdianbiao: |
| | | base-url: https://168.tqdianbiao.com |
| | | auth: b6229401590539d5def7f2bf897b33de |
| | | ignore-radio: 1 |
| | | sync: |
| | | enabled: true |
| | | hour-window: 1 |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="UTF-8" ?> |
| | | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| | | <mapper namespace="com.ruoyi.http.mapper.TqdianbiaoEleRecordMapper"> |
| | | |
| | | <select id="selectRecordList" resultType="com.ruoyi.http.vo.StatisticEleRecordVo"> |
| | | SELECT |
| | | r.id AS id, |
| | | r.time_key AS timeKey, |
| | | r.meter_id AS meterId, |
| | | COALESCE(m.meter_name, m.address) AS meterName, |
| | | m.address AS address, |
| | | r.ratio AS ratio, |
| | | r.reading_method AS readingMethod, |
| | | r.prev_reading AS prevReading, |
| | | r.curr_reading AS currReading, |
| | | r.start_time AS startTime, |
| | | r.end_time AS endTime, |
| | | r.total_consumption AS totalConsumption, |
| | | r.sharp_consumption AS sharpConsumption, |
| | | r.peak_consumption AS peakConsumption, |
| | | r.flat_consumption AS flatConsumption, |
| | | r.valley_consumption AS valleyConsumption, |
| | | r.start_reading AS startReading, |
| | | r.end_reading AS endReading |
| | | FROM tqdianbiao_ele_record r |
| | | LEFT JOIN tqdianbiao_meter m ON r.meter_id = m.meter_id |
| | | WHERE r.dimension IN |
| | | <foreach collection="dimensions" item="dim" open="(" separator="," close=")"> |
| | | #{dim} |
| | | </foreach> |
| | | AND RPAD(r.time_key, 12, '0') >= #{startTime} |
| | | AND RPAD(r.time_key, 12, '0') <= #{endTime} |
| | | ORDER BY r.time_key DESC, r.meter_id ASC |
| | | </select> |
| | | |
| | | <select id="selectPrevReading" resultType="com.ruoyi.http.pojo.TqdianbiaoEleRecord"> |
| | | SELECT id, meter_id, time_key, curr_reading, end_reading, ratio |
| | | FROM tqdianbiao_ele_record |
| | | WHERE meter_id = #{meterId} |
| | | AND time_key < #{timeKey} |
| | | ORDER BY time_key DESC |
| | | LIMIT 1 |
| | | </select> |
| | | |
| | | </mapper> |