Commit 71bd5d496b3992ae0e4bbef933f2f19e0a190f52

Authored by 杨鸣坤
1 parent b9bdb7bd

feat: 新增设备运行时长明细查询接口

... ... @@ -157,4 +157,24 @@ public class HealthController {
157 157 @RequestParam String date) {
158 158 return energySearchService.queryEnergyDetailByDate(dtuSn, date);
159 159 }
  160 +
  161 + /**
  162 + * 根据dtuSn查询指定设备的运行时长明细
  163 + * type=1(时): 传startDate,获取指定日期的运行时长明细
  164 + * type=2(天): 传startDate和endDate,按日统计
  165 + * type=3(月): 查本年年初到现在,按月统计
  166 + *
  167 + * @param dtuSn 设备序列号
  168 + * @param type 类型:1-时,2-天,3-月
  169 + * @param startDate 开始日期 yyyy-MM-dd (type=1,2必填)
  170 + * @param endDate 结束日期 yyyy-MM-dd (type=2必填)
  171 + */
  172 + @GetMapping("/energy/runtimeDetail")
  173 + public Map<String, Object> energyRuntimeDetail(
  174 + @RequestParam String dtuSn,
  175 + @RequestParam(defaultValue = "1") String type,
  176 + @RequestParam(required = false) String startDate,
  177 + @RequestParam(required = false) String endDate) {
  178 + return energySearchService.queryEnergyRuntimeDetail(dtuSn, type, startDate, endDate);
  179 + }
160 180 }
... ...
... ... @@ -13,6 +13,7 @@ import jakarta.annotation.Resource;
13 13
14 14 import java.math.BigDecimal;
15 15 import java.math.RoundingMode;
  16 +import java.text.SimpleDateFormat;
16 17 import java.util.*;
17 18
18 19 @Slf4j
... ... @@ -235,6 +236,209 @@ public class EnergySearchService {
235 236 }
236 237
237 238 /**
  239 + * 根据dtuSn查询指定设备的运行时长明细
  240 + * 核心数据为设备时用电量(eq_kwh),其中包含每个状态的运行时长,需要统计
  241 + * type=1(时): 传startDate,获取指定日期的时用电量明细+各状态时长统计
  242 + * type=2(天): 传startDate和endDate,按日统计
  243 + * type=3(月): 查本年年初到现在,按月统计
  244 + *
  245 + * @param dtuSn 设备序列号
  246 + * @param type 类型:1-时,2-天,3-月
  247 + * @param startDate 开始日期 yyyy-MM-dd (type=1,2必填)
  248 + * @param endDate 结束日期 yyyy-MM-dd (type=2必填)
  249 + */
  250 + public Map<String, Object> queryEnergyRuntimeDetail(String dtuSn, String type,
  251 + String startDate, String endDate) {
  252 + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  253 + List<String> dateList = new ArrayList<>();
  254 +
  255 + // 根据type构建日期列表
  256 + if ("1".equals(type)) {
  257 + dateList.add(startDate);
  258 + } else if ("2".equals(type)) {
  259 + try {
  260 + Date start = sdf.parse(startDate);
  261 + Date end = sdf.parse(endDate);
  262 + Calendar cur = Calendar.getInstance();
  263 + cur.setTime(start);
  264 + while (!cur.getTime().after(end)) {
  265 + dateList.add(sdf.format(cur.getTime()));
  266 + cur.add(Calendar.DAY_OF_MONTH, 1);
  267 + }
  268 + } catch (Exception e) {
  269 + return Map.of("code", 400, "msg", "日期格式错误");
  270 + }
  271 + } else if ("3".equals(type)) {
  272 + Calendar now = Calendar.getInstance();
  273 + int year = now.get(Calendar.YEAR);
  274 + Calendar startCal = Calendar.getInstance();
  275 + startCal.set(year, Calendar.JANUARY, 1);
  276 + while (!startCal.getTime().after(now.getTime())) {
  277 + dateList.add(sdf.format(startCal.getTime()));
  278 + startCal.add(Calendar.DAY_OF_MONTH, 1);
  279 + }
  280 + }
  281 +
  282 + // 统计汇总数据
  283 + long totalDurationAll = 0;
  284 + BigDecimal totalKwhAll = BigDecimal.ZERO;
  285 + Map<Integer, Long> statusDurationAllMap = initStatusMap();
  286 +
  287 + // type=1: 直接返回每日明细; type=2/3: 按日期聚合后返回
  288 + List<Map<String, Object>> detailList = new ArrayList<>();
  289 + // type=3 按月聚合用
  290 + Map<String, Map<String, Object>> monthAggMap = "3".equals(type) ? new LinkedHashMap<>() : null;
  291 +
  292 + for (String dateStr : dateList) {
  293 + // 从eq_kwh表查询时用电量数据
  294 + Object kwhRawData = Collections.emptyList();
  295 + BigDecimal dayKwh = BigDecimal.ZERO;
  296 + long dayTotalDuration = 0;
  297 + Map<Integer, Long> dayStatusDuration = initStatusMap();
  298 +
  299 + try {
  300 + String kwhSql = "SELECT description FROM " + eqKwhTableName
  301 + + " WHERE corp_code = ? AND dtuSn = ? AND use_date = ?";
  302 + Map<String, Object> kwhRow = jdbcTemplate.queryForMap(kwhSql, energyCorpCode, dtuSn, dateStr + " 00:00:00");
  303 + if (kwhRow != null && kwhRow.get("description") != null) {
  304 + String desc = String.valueOf(kwhRow.get("description"));
  305 + JSONArray dataArray = JSON.parseArray(desc);
  306 +
  307 + for (int i = 0; i < dataArray.size(); i++) {
  308 + JSONObject item = dataArray.getJSONObject(i);
  309 + Double value = item.getDouble("value");
  310 + if (value != null) dayKwh = dayKwh.add(BigDecimal.valueOf(value));
  311 +
  312 + // 从字段 0/1/2/3 统计各状态运行时长 + 格式化新字段
  313 + for (int statusKey = 0; statusKey <= 3; statusKey++) {
  314 + Long dur = item.getLong(String.valueOf(statusKey));
  315 + item.put(statusKey + "Formatted", formatDuration(dur != null ? dur : 0L));
  316 + if (dur != null && dur > 0) {
  317 + dayTotalDuration += dur;
  318 + totalDurationAll += dur;
  319 + dayStatusDuration.merge(statusKey, dur, Long::sum);
  320 + statusDurationAllMap.merge(statusKey, dur, Long::sum);
  321 + }
  322 + }
  323 + }
  324 + kwhRawData = dataArray;
  325 + }
  326 + } catch (Exception ignored) {}
  327 + totalKwhAll = totalKwhAll.add(dayKwh);
  328 +
  329 + String periodKey = "3".equals(type) ? dateStr.substring(0, 7) : dateStr;
  330 +
  331 + if ("1".equals(type)) {
  332 + // type=1(时): 返回原始kwhList + 当日统计
  333 + Map<String, Object> entry = new LinkedHashMap<>();
  334 + entry.put("date", periodKey);
  335 + entry.put("kwhList", kwhRawData);
  336 + entry.put("totalKwh", dayKwh.setScale(2, RoundingMode.HALF_UP));
  337 + entry.put("totalDurationFormatted", formatDuration(dayTotalDuration));
  338 + entry.put("totalDurationSeconds", dayTotalDuration);
  339 + entry.put("statusStats", buildStatusStats(dayStatusDuration, dayTotalDuration));
  340 + detailList.add(entry);
  341 + } else if ("2".equals(type)) {
  342 + // type=2(天): 返回每日汇总统计,不含kwhList
  343 + Map<String, Object> entry = new LinkedHashMap<>();
  344 + entry.put("date", periodKey);
  345 + entry.put("totalKwh", dayKwh.setScale(2, RoundingMode.HALF_UP));
  346 + entry.put("totalDurationFormatted", formatDuration(dayTotalDuration));
  347 + entry.put("totalDurationSeconds", dayTotalDuration);
  348 + entry.put("statusStats", buildStatusStats(dayStatusDuration, dayTotalDuration));
  349 + detailList.add(entry);
  350 + } else if ("3".equals(type)) {
  351 + // type=3(月): 按月聚合
  352 + monthAggMap.computeIfAbsent(periodKey, k -> {
  353 + Map<String, Object> m = new LinkedHashMap<>();
  354 + m.put("date", k);
  355 + m.put("totalKwh", BigDecimal.ZERO);
  356 + m.put("totalDurationSeconds", 0L);
  357 + m.put("statusDurationMap", initStatusMap());
  358 + return m;
  359 + });
  360 + Map<String, Object> monthEntry = monthAggMap.get(periodKey);
  361 + monthEntry.put("totalKwh", ((BigDecimal) monthEntry.get("totalKwh")).add(dayKwh));
  362 + monthEntry.put("totalDurationSeconds", (Long) monthEntry.get("totalDurationSeconds") + dayTotalDuration);
  363 + @SuppressWarnings("unchecked")
  364 + Map<Integer, Long> mStatusMap = (Map<Integer, Long>) monthEntry.get("statusDurationMap");
  365 + for (int sk = 0; sk <= 3; sk++) {
  366 + if (dayStatusDuration.get(sk) > 0) {
  367 + mStatusMap.merge(sk, dayStatusDuration.get(sk), Long::sum);
  368 + }
  369 + }
  370 + }
  371 + }
  372 +
  373 + // type=3 构建月度聚合结果
  374 + if ("3".equals(type) && monthAggMap != null) {
  375 + for (Map.Entry<String, Map<String, Object>> me : monthAggMap.entrySet()) {
  376 + Map<String, Object> m = me.getValue();
  377 + long mDur = (Long) m.get("totalDurationSeconds");
  378 + @SuppressWarnings("unchecked")
  379 + Map<Integer, Long> mStatusMap = (Map<Integer, Long>) m.get("statusDurationMap");
  380 +
  381 + Map<String, Object> entry = new LinkedHashMap<>();
  382 + entry.put("date", m.get("date"));
  383 + entry.put("totalKwh", ((BigDecimal) m.get("totalKwh")).setScale(2, RoundingMode.HALF_UP));
  384 + entry.put("totalDurationFormatted", formatDuration(mDur));
  385 + entry.put("totalDurationSeconds", mDur);
  386 + entry.put("statusStats", buildStatusStats(mStatusMap, mDur));
  387 + detailList.add(entry);
  388 + }
  389 + }
  390 +
  391 + List<Map<String, Object>> allStatusStats = buildStatusStats(statusDurationAllMap, totalDurationAll);
  392 +
  393 + return Map.of(
  394 + "code", 200,
  395 + "msg", "请求成功",
  396 + "dtuSn", dtuSn,
  397 + "type", type,
  398 + "detailList", detailList,
  399 + "summary", Map.of(
  400 + "totalDurationFormatted", formatDuration(totalDurationAll),
  401 + "totalDurationSeconds", totalDurationAll,
  402 + "totalKwh", totalKwhAll.setScale(2, RoundingMode.HALF_UP),
  403 + "statusStats", allStatusStats
  404 + )
  405 + );
  406 + }
  407 +
  408 + private static Map<Integer, Long> initStatusMap() {
  409 + Map<Integer, Long> map = new LinkedHashMap<>();
  410 + map.put(0, 0L);
  411 + map.put(1, 0L);
  412 + map.put(2, 0L);
  413 + map.put(3, 0L);
  414 + return map;
  415 + }
  416 +
  417 + private static List<Map<String, Object>> buildStatusStats(Map<Integer, Long> statusMap, long totalDur) {
  418 + List<Map<String, Object>> list = new ArrayList<>();
  419 + for (Map.Entry<Integer, Long> entry : statusMap.entrySet()) {
  420 + long dur = entry.getValue();
  421 + double percent = totalDur > 0 ? BigDecimal.valueOf(dur * 100.0 / totalDur)
  422 + .setScale(2, RoundingMode.HALF_UP).doubleValue() : 0.0;
  423 + Map<String, Object> stat = new LinkedHashMap<>();
  424 + stat.put("status", entry.getKey());
  425 + stat.put("durationSeconds", dur);
  426 + stat.put("durationFormatted", formatDuration(dur));
  427 + stat.put("percent", percent);
  428 + list.add(stat);
  429 + }
  430 + return list;
  431 + }
  432 +
  433 + private static Object itemToMap(JSONArray dataArray) {
  434 + List<Object> list = new ArrayList<>();
  435 + for (int i = 0; i < dataArray.size(); i++) {
  436 + list.add(dataArray.getJSONObject(i));
  437 + }
  438 + return list;
  439 + }
  440 +
  441 + /**
238 442 * 格式化时长为 xx时xx分xx秒 格式
239 443 * 时不为0则展示xx时xx分xx秒
240 444 * 时和分都为0则只展示xx秒
... ...