feat:完成大屏产能统计接口、产能近七天趋势图接口、能耗周趋势接口

plp
HuangHuiKang 6 days ago
parent 20d35645a3
commit a8ca54bb1f

@ -20,9 +20,11 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
@Service @Service
@Slf4j @Slf4j
@ -30,12 +32,13 @@ public class TDengineService {
@Resource @Resource
private JdbcTemplate jdbcTemplate; private JdbcTemplate jdbcTemplate;
@DS("tdengine") // @DS("tdengine")
public void testConnection() { // public void testConnection() {
String testSQL = "SELECT SERVER_STATUS()"; // String testSQL = "SELECT SERVER_STATUS()";
jdbcTemplate.queryForObject(testSQL, Integer.class); // jdbcTemplate.queryForObject(testSQL, Integer.class);
System.out.println("TDengine连接正常"); // log.info("TDengine连接正常");
} //
// }
@DS("tdengine") @DS("tdengine")
public void initDatabaseAndTable(Long id) { public void initDatabaseAndTable(Long id) {
@ -61,10 +64,9 @@ public class TDengineService {
tableName, id); tableName, id);
jdbcTemplate.execute(createTableSql); jdbcTemplate.execute(createTableSql);
System.out.println("TDengine表创建成功: " + tableName); log.info("TDengine表创建成功 {}", tableName);
}
}
/** /**
@ -93,41 +95,9 @@ public class TDengineService {
String timestampStr = sdf.format(ts); String timestampStr = sdf.format(ts);
result.put("timestamp", timestampStr); result.put("timestamp", timestampStr);
// 读取 query_data二进制字段
byte[] blob = rs.getBytes("query_data"); byte[] blob = rs.getBytes("query_data");
if (blob != null) { result.put("queryData", decodeQueryData(blob));
String jsonStr = new String(blob, StandardCharsets.UTF_8);
try {
// 1. 先去除外层的双引号(如果存在)
String trimmed = jsonStr.trim();
if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) {
trimmed = trimmed.substring(1, trimmed.length() - 1);
}
// 2. 检查是否是十六进制字符串
if (isHexString(trimmed)) {
// 3. 十六进制解码
String decodedJson = hexToString(trimmed);
result.put("queryData", decodedJson);
} else {
// 如果不是十六进制,尝试直接解析
ObjectMapper objectMapper = new ObjectMapper();
List<Map<String, Object>> queryData = objectMapper.readValue(
trimmed,
new TypeReference<List<Map<String, Object>>>() {}
);
result.put("queryData", queryData);
}
} catch (Exception e) {
System.err.println("解析JSON失败: " + e.getMessage());
result.put("queryData", new ArrayList<>());
result.put("rawData", jsonStr);
}
} else {
result.put("queryData", new ArrayList<>());
}
result.put("deviceId", id); result.put("deviceId", id);
@ -136,8 +106,7 @@ public class TDengineService {
} }
}); });
} catch (Exception e) { } catch (Exception e) {
System.out.println("查询设备" + id + "的最新数据时发生异常: " + e.getMessage()); log.error("查询设备id为 {} 的最新数据时发生异常", id, e);
// 可以考虑记录更详细的日志,或抛出更明确的业务异常
return null; return null;
} }
} }
@ -177,7 +146,7 @@ public class TDengineService {
ps.setBytes(2, blobData); ps.setBytes(2, blobData);
}) > 0; }) > 0;
} catch (Exception e) { } catch (Exception e) {
System.out.println("向设备" + id + "插入数据时发生异常: " + e.getMessage()); log.error("向设备id为 {} 插入数据时发生异常", id, e);
return false; return false;
} }
} }
@ -200,42 +169,9 @@ public class TDengineService {
result.put("timestamp", rs.getTimestamp("ts")); result.put("timestamp", rs.getTimestamp("ts"));
result.put("deviceId", id); result.put("deviceId", id);
// 读取 query_data二进制字段
byte[] blob = rs.getBytes("query_data"); byte[] blob = rs.getBytes("query_data");
if (blob != null) { result.put("queryData", decodeQueryData(blob));
String jsonStr = new String(blob, StandardCharsets.UTF_8);
try {
// 1. 先去除外层的双引号(如果存在)
String trimmed = jsonStr.trim();
if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) {
trimmed = trimmed.substring(1, trimmed.length() - 1);
}
// 2. 检查是否是十六进制字符串
if (isHexString(trimmed)) {
// 3. 十六进制解码
String decodedJson = hexToString(trimmed);
result.put("queryData", decodedJson);
} else {
// 如果不是十六进制,尝试直接解析
ObjectMapper objectMapper = new ObjectMapper();
List<Map<String, Object>> queryData = objectMapper.readValue(
trimmed,
new TypeReference<List<Map<String, Object>>>() {}
);
result.put("queryData", queryData);
}
} catch (Exception e) {
System.err.println("解析JSON失败: " + e.getMessage());
result.put("queryData", new ArrayList<>());
result.put("rawData", jsonStr);
}
} else {
result.put("queryData", new ArrayList<>());
}
return result; return result;
} }
@ -243,8 +179,7 @@ public class TDengineService {
} catch (EmptyResultDataAccessException e) { } catch (EmptyResultDataAccessException e) {
return Collections.singletonList(createEmptyResult(id)); return Collections.singletonList(createEmptyResult(id));
} catch (Exception e) { } catch (Exception e) {
System.err.println("查询设备" + id + "的最新数据时发生异常: " + e.getMessage()); log.error("查询设备id为 {} 的最新数据时发生异常", id, e);
e.printStackTrace();
return Collections.singletonList(createEmptyResult(id)); return Collections.singletonList(createEmptyResult(id));
} }
} }
@ -267,37 +202,11 @@ public class TDengineService {
byte[] bytes = Hex.decodeHex(hex); byte[] bytes = Hex.decodeHex(hex);
return new String(bytes, StandardCharsets.UTF_8); return new String(bytes, StandardCharsets.UTF_8);
} catch (DecoderException e) { } catch (DecoderException e) {
throw new RuntimeException("十六进制解码失败: " + e.getMessage(), e); log.error("十六进制解码失败: {}", e);
} return "";
} }
private List<Map<String, Object>> parseJsonData(byte[] blob) {
if (blob == null || blob.length == 0) {
return new ArrayList<>();
} }
try {
String jsonStr = new String(blob, StandardCharsets.UTF_8);
// 检查是否是有效的JSON
if (jsonStr.trim().startsWith("[") && jsonStr.trim().endsWith("]")) {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(jsonStr,
new TypeReference<List<Map<String, Object>>>() {});
} else {
// 可能是字符串化的JSON尝试去除引号
jsonStr = jsonStr.trim();
if (jsonStr.startsWith("\"") && jsonStr.endsWith("\"")) {
jsonStr = jsonStr.substring(1, jsonStr.length() - 1);
return new ObjectMapper().readValue(jsonStr,
new TypeReference<List<Map<String, Object>>>() {});
}
}
} catch (Exception e) {
System.err.println("解析JSON数据失败: " + e.getMessage());
}
return new ArrayList<>();
}
private Map<String, Object> createEmptyResult(Long deviceId) { private Map<String, Object> createEmptyResult(Long deviceId) {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
@ -343,42 +252,9 @@ public class TDengineService {
result.put("timestamp", rs.getTimestamp("ts")); result.put("timestamp", rs.getTimestamp("ts"));
result.put("deviceId", id); result.put("deviceId", id);
// 读取 query_data二进制字段
byte[] blob = rs.getBytes("query_data"); byte[] blob = rs.getBytes("query_data");
if (blob != null) { result.put("queryData", decodeQueryData(blob));
String jsonStr = new String(blob, StandardCharsets.UTF_8);
try {
// 1. 先去除外层的双引号(如果存在)
String trimmed = jsonStr.trim();
if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) {
trimmed = trimmed.substring(1, trimmed.length() - 1);
}
// 2. 检查是否是十六进制字符串
if (isHexString(trimmed)) {
// 3. 十六进制解码
String decodedJson = hexToString(trimmed);
result.put("queryData", decodedJson);
} else {
// 如果不是十六进制,尝试直接解析
ObjectMapper objectMapper = new ObjectMapper();
List<Map<String, Object>> queryData = objectMapper.readValue(
trimmed,
new TypeReference<List<Map<String, Object>>>() {}
);
result.put("queryData", queryData);
}
} catch (Exception e) {
System.err.println("解析JSON失败: " + e.getMessage());
result.put("queryData", new ArrayList<>());
result.put("rawData", jsonStr);
}
} else {
result.put("queryData", new ArrayList<>());
}
return result; return result;
} }
@ -451,31 +327,12 @@ public class TDengineService {
// 读取 query_data二进制字段 // 读取 query_data二进制字段
byte[] blob = rs.getBytes("query_data"); byte[] blob = rs.getBytes("query_data");
if (blob != null) { result.put("queryData", decodeQueryData(blob));
// 转为字符串
String json = new String(blob, StandardCharsets.UTF_8).trim();
// 去除 TDengine 中可能存在的外层双引号
if (json.startsWith("\"") && json.endsWith("\"")) {
json = json.substring(1, json.length() - 1);
}
// 如果是十六进制字符串,先解码
if (isHexString(json)) {
json = hexToString(json);
}
// 统一约定queryData 始终返回 String由上层决定是否解析 JSON
result.put("queryData", json);
} else {
// 没有数据时返回空数组字符串
result.put("queryData", "[]");
}
return result; return result;
}); });
} catch (Exception e) { } catch (Exception e) {
log.error("TDengine 查询失败deviceId={} td表不存在", deviceId); log.error("TDengine 查询失败deviceId={} td表不存在", deviceId,e);
return null; return null;
} }
} }
@ -557,26 +414,7 @@ public class TDengineService {
// 读取 query_data二进制字段 // 读取 query_data二进制字段
byte[] blob = rs.getBytes("query_data"); byte[] blob = rs.getBytes("query_data");
if (blob != null) { map.put("queryData", decodeQueryData(blob));
// 转为字符串
String json = new String(blob, StandardCharsets.UTF_8).trim();
// 去除 TDengine 中可能存在的外层双引号
if (json.startsWith("\"") && json.endsWith("\"")) {
json = json.substring(1, json.length() - 1);
}
// 如果是十六进制字符串,先解码
if (isHexString(json)) {
json = hexToString(json);
}
// 统一约定queryData 始终返回 String由上层决定是否解析 JSON
map.put("queryData", json);
} else {
// 没有数据时返回空数组字符串
map.put("queryData", "[]");
}
return map; return map;
}); });
@ -607,29 +445,10 @@ public class TDengineService {
map.put("timestamp", rs.getTimestamp("ts")); map.put("timestamp", rs.getTimestamp("ts"));
map.put("deviceId", deviceId); map.put("deviceId", deviceId);
// 读取 query_data二进制字段 // 读取 query_data二进制字段
byte[] blob = rs.getBytes("query_data"); byte[] blob = rs.getBytes("query_data");
if (blob != null) { map.put("queryData", decodeQueryData(blob));
// 转为字符串
String json = new String(blob, StandardCharsets.UTF_8).trim();
// 去除 TDengine 中可能存在的外层双引号
if (json.startsWith("\"") && json.endsWith("\"")) {
json = json.substring(1, json.length() - 1);
}
// 如果是十六进制字符串,先解码
if (isHexString(json)) {
json = hexToString(json);
}
// 统一约定queryData 始终返回 String由上层决定是否解析 JSON
map.put("queryData", json);
} else {
// 没有数据时返回空数组字符串
map.put("queryData", "[]");
}
return map; return map;
}); });
@ -659,32 +478,137 @@ public class TDengineService {
// 读取 query_data二进制字段 // 读取 query_data二进制字段
byte[] blob = rs.getBytes("query_data"); byte[] blob = rs.getBytes("query_data");
if (blob != null) { map.put("queryData", decodeQueryData(blob));
// 转为字符串
return map;
});
}
@DS("tdengine")
public List<Map<String, Object>> queryLastDataByHourBatch(
Set<Long> deviceIds,
LocalDateTime startTime,
LocalDateTime endTime) {
List<Map<String, Object>> result = new ArrayList<>();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
for (Long deviceId : deviceIds) {
String sql =
"SELECT ts, query_data FROM besure.d_" + deviceId +
" WHERE ts >= '" + startTime.format(fmt) + "'" +
" AND ts <= '" + endTime.format(fmt) + "'" +
" ORDER BY ts ASC";
jdbcTemplate.query(sql, rs -> {
Map<String, Object> map = new HashMap<>();
map.put("deviceId", deviceId);
map.put("timestamp", rs.getTimestamp("ts").toLocalDateTime());
map.put("queryData", decodeQueryData(rs.getBytes("query_data")));
result.add(map);
});
}
return result;
}
@DS("tdengine")
public List<Map<String, Object>> queryLastDataByDaySafe(
Set<Long> deviceIds,
LocalDate startDate,
int days
) {
List<Map<String, Object>> result = new ArrayList<>();
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
for (int i = 0; i < days; i++) {
LocalDate day = startDate.plusDays(i);
LocalDateTime dayStart = day.atStartOfDay();
LocalDateTime dayEnd = day.plusDays(1).atStartOfDay();
for (Long deviceId : deviceIds) {
String tableName = "besure.d_" + deviceId;
String sql =
"SELECT ts, query_data FROM " + tableName +
" WHERE ts >= '" + dayStart.format(formatter) + "'" +
" AND ts < '" + dayEnd.format(formatter) + "'" +
" ORDER BY ts DESC LIMIT 1";
try {
List<Map<String, Object>> rows = jdbcTemplate.query(
sql,
(rs, rowNum) -> {
Map<String, Object> map = new HashMap<>();
map.put("deviceId", deviceId);
map.put("day", day.toString());
Timestamp ts = rs.getTimestamp("ts");
map.put("timestamp",
ts != null ? ts.toLocalDateTime() : null);
byte[] blob = rs.getBytes("query_data");
map.put("queryData", decodeQueryData(blob));
return map;
}
);
if (!rows.isEmpty()) {
result.add(rows.get(0));
}
} catch (Exception e) {
// 表不存在 / 查询失败 → 直接跳过
log.warn("Skip TDengine table: {}, reason: {}",
tableName, e.getMessage());
}
}
}
return result;
}
private String decodeQueryData(byte[] blob) {
if (blob == null || blob.length == 0) return "[]";
String json = new String(blob, StandardCharsets.UTF_8).trim(); String json = new String(blob, StandardCharsets.UTF_8).trim();
// 去除 TDengine 中可能存在的外层双引号 // 去掉外层引号
if (json.startsWith("\"") && json.endsWith("\"")) { if (json.startsWith("\"") && json.endsWith("\"")) {
json = json.substring(1, json.length() - 1); json = json.substring(1, json.length() - 1);
} }
// 如果是十六进制字符串,先解码 // 如果是十六进制字符串,解码
if (isHexString(json)) { if (isHexString(json)) {
json = hexToString(json); json = hexToString(json);
} }
// 统一约定queryData 始终返回 String由上层决定是否解析 JSON return json;
map.put("queryData", json);
} else {
// 没有数据时返回空数组字符串
map.put("queryData", "[]");
} }
return map;
});
}
} }

@ -166,6 +166,8 @@ public class EnergyDeviceController {
@GetMapping("/lastEnergyStatistics") @GetMapping("/lastEnergyStatistics")
@Operation(summary = "获得近七小时能耗记录")
@PreAuthorize("@ss.hasPermission('mes:energy-device:query')")
public CommonResult<List<HourEnergyValueVO>> lastEnergyStatistics(@RequestParam("deviceTypeId") Long deviceTypeId, public CommonResult<List<HourEnergyValueVO>> lastEnergyStatistics(@RequestParam("deviceTypeId") Long deviceTypeId,
@RequestParam("orgId") Long orgId) { @RequestParam("orgId") Long orgId) {
@ -174,4 +176,14 @@ public class EnergyDeviceController {
return success(hourEnergyValueVOS); return success(hourEnergyValueVOS);
} }
@GetMapping("/latestSevenDaysStatistics")
@Operation(summary = "获得近七天能耗记录")
@PreAuthorize("@ss.hasPermission('mes:energy-device:query')")
public CommonResult<List<DayEnergyValueVO>> latestSevenDaysStatistics(
@RequestParam Long deviceTypeId,
@RequestParam Long orgId) {
return CommonResult.success(energyDeviceService.latestSevenDaysStatistics(deviceTypeId, orgId));
}
} }

@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class DayEnergyValueVO {
@Schema(description = "日期yyyy-MM-dd")
private String Day;
@Schema(description = "按规则计算后的值")
private String value;
}

@ -7,9 +7,9 @@ import lombok.Data;
@Data @Data
public class HourEnergyValueVO { public class HourEnergyValueVO {
/** 小时yyyy-MM-dd HH */ @Schema(description = "小时yyyy-MM-dd HH")
private String hour; private String hour;
/** 按规则计算后的值 */ @Schema(description = "按规则计算后的值")
private String value; private String value;
} }

@ -10,15 +10,9 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.iot.framework.mqtt.utils.DateUtils; import cn.iocoder.yudao.module.iot.framework.mqtt.utils.DateUtils;
import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.DeviceTypePieOptionsVO;
import cn.iocoder.yudao.module.mes.controller.admin.itemrequisition.vo.ItemRequisitionRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.itemrequisition.vo.ItemRequisitionSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.itemrequisition.vo.ItemRequisitionSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.*; import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.*;
import cn.iocoder.yudao.module.mes.controller.admin.zjproductrecord.vo.ZjProductRecordRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.zjproductrecord.vo.ZjProductRecordSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.zjproductrecord.vo.ZjProductRecordSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.dvrepair.DvRepairDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.itemrequisition.ItemRequisitionDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.organization.OrganizationDO; import cn.iocoder.yudao.module.mes.dal.dataobject.organization.OrganizationDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO; import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.zjproduct.ZjProductDO; import cn.iocoder.yudao.module.mes.dal.dataobject.zjproduct.ZjProductDO;
@ -30,12 +24,10 @@ import cn.iocoder.yudao.module.mes.service.organization.OrganizationService;
import cn.iocoder.yudao.module.mes.service.plan.PlanService; import cn.iocoder.yudao.module.mes.service.plan.PlanService;
import cn.iocoder.yudao.module.mes.service.zjproduct.ZjProductService; import cn.iocoder.yudao.module.mes.service.zjproduct.ZjProductService;
import cn.iocoder.yudao.module.mes.service.zjproductrecord.ZjProductRecordService; import cn.iocoder.yudao.module.mes.service.zjproductrecord.ZjProductRecordService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.lang3.StringUtils;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -45,9 +37,9 @@ import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid; import javax.validation.Valid;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.DayOfWeek; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.*;
import java.time.ZoneId; import java.time.temporal.TemporalAdjusters;
import java.util.*; import java.util.*;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@ -374,35 +366,131 @@ public class PlanController {
return success(planWeekTrendVO); return success(planWeekTrendVO);
} }
@GetMapping("/getDayCapacity") @GetMapping("/getPlanCapacity")
@Operation(summary = "综合大屏-日产能达成情况") @Operation(summary = "综合大屏-日/月产能达成情况")
@Parameter(name = "id", description = "编号", required = true, example = "1024") @Parameter(name = "type", description = "类型 1-日 2-月", required = true, example = "1024")
public CommonResult<DayCapacityVO> getDayCapacity() { public CommonResult<PlanCapacityVO> getPlanCapacity(
DayCapacityVO dayCapacityVO = new DayCapacityVO(); @RequestParam(name = "type", defaultValue = "1") Integer type) {
List<Integer> statusList = new ArrayList<>(Collections.singletonList(PlanStatusEnum..getValue()));
List<PlanDO> planDOList = planService.getPlanByStatus(statusList); PlanCapacityVO vo = new PlanCapacityVO();
// 排产单数量
dayCapacityVO.setOrders(planDOList.size()); // 根据 type 计算时间区间
// 排产数量 LocalDate today = LocalDate.now();
int plan = 0; LocalDateTime startTime;
for (PlanDO planDO : planDOList) { LocalDateTime endTime;
if (type != null && type == 2) {
// 月
startTime = today.withDayOfMonth(1).atStartOfDay();
endTime = today.with(TemporalAdjusters.lastDayOfMonth())
.atTime(LocalTime.MAX);
} else {
// 日(默认)
startTime = today.atStartOfDay();
endTime = today.atTime(LocalTime.MAX);
}
// 一次性查询该时间段内所有计划
List<PlanDO> planList =
planService.getPlanCapacity(null, startTime, endTime);
long orders = 0;
long planTotal = 0;
long finishedTotal = 0;
long totalPass = 0;
long totalFinish = 0;
for (PlanDO planDO : planList) {
Integer status = planDO.getStatus();
// 已排产
if (Objects.equals(status, PlanStatusEnum..getValue())) {
orders++;
if (planDO.getPlanNumber() != null) { if (planDO.getPlanNumber() != null) {
plan = plan + planDO.getPlanNumber().intValue(); planTotal += planDO.getPlanNumber();
}
}
// 已生产(已入库 + 待入库)
if (Objects.equals(status, PlanStatusEnum..getValue())
|| Objects.equals(status, PlanStatusEnum..getValue())) {
if (planDO.getWangongNumber() != null) {
finishedTotal += planDO.getWangongNumber();
} }
} }
dayCapacityVO.setPlan(plan);
// 已生产数量 // 产率(全部计划)
statusList = Arrays.asList(PlanStatusEnum..getValue(),PlanStatusEnum..getValue());; if (planDO.getPassNumber() != null) {
planDOList = planService.getPlanByStatus(statusList); totalPass += planDO.getPassNumber();
int finish = 0; }
for (PlanDO planDO : planDOList) {
if (planDO.getWangongNumber() != null) { if (planDO.getWangongNumber() != null) {
finish = finish + planDO.getWangongNumber().intValue(); totalFinish += planDO.getWangongNumber();
}
}
// 设置返回值
vo.setOrders((int) orders);
vo.setPlan((int) planTotal);
vo.setPending((int) finishedTotal);
// 产率(百分比)
double rate = 0D;
if (totalFinish > 0) {
rate = BigDecimal.valueOf(totalPass)
.multiply(BigDecimal.valueOf(100))
.divide(BigDecimal.valueOf(totalFinish), 2, RoundingMode.HALF_UP)
.doubleValue();
} }
vo.setRate(rate);
return success(vo);
} }
dayCapacityVO.setPending(finish);
return success(dayCapacityVO);
@GetMapping("/getLastDaysRate")
@Operation(summary = "综合大屏-前7天每日产率百分比")
public CommonResult<Map<String, Double>> getLastDaysRate() {
Map<String, Double> result = new LinkedHashMap<>();
LocalDate today = LocalDate.now();
// 遍历前七天
for (int i = 6; i >= 0; i--) { // 从 6 天前到今天
LocalDate date = today.minusDays(i);
LocalDateTime startTime = date.atStartOfDay();
LocalDateTime endTime = date.atTime(LocalTime.MAX);
// 查询当天的所有计划
List<PlanDO> planList = planService.getPlanCapacity(null, startTime, endTime);
long totalPass = 0;
long totalFinish = 0;
for (PlanDO planDO : planList) {
if (planDO.getPassNumber() != null) totalPass += planDO.getPassNumber();
if (planDO.getWangongNumber() != null) totalFinish += planDO.getWangongNumber();
} }
double rate = 0D;
if (totalFinish > 0) {
rate = BigDecimal.valueOf(totalPass)
.multiply(BigDecimal.valueOf(100))
.divide(BigDecimal.valueOf(totalFinish), 2, RoundingMode.HALF_UP)
.doubleValue();
}
// key = yyyy-MM-dd
result.put(date.toString(), rate);
}
return success(result);
}
} }

@ -3,19 +3,17 @@ package cn.iocoder.yudao.module.mes.controller.admin.plan.vo;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.util.List;
@Schema(description = "综合大屏 - 周生产趋势 Resp VO") @Schema(description = "综合大屏 - 周生产趋势 Resp VO")
@Data @Data
public class DayCapacityVO { public class PlanCapacityVO {
@Schema(description = "orders") @Schema(description = "排产单数量")
private Integer orders; private Integer orders;
@Schema(description = "plan") @Schema(description = "已排产")
private Integer plan; private Integer plan;
@Schema(description = "pending") @Schema(description = "已生产")
private Integer pending; private Integer pending;
@Schema(description = "rate") @Schema(description = "rate")

@ -13,6 +13,7 @@ import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update; import org.apache.ibatis.annotations.Update;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -129,4 +130,13 @@ public interface PlanMapper extends BaseMapperX<PlanDO> {
} }
return MapUtil.getLong(result.get(0), "sumCount", 0L); return MapUtil.getLong(result.get(0), "sumCount", 0L);
} }
default List<PlanDO> getPlanCapacity(List<Integer> statusList, LocalDateTime startTime, LocalDateTime endTime) {
return selectList(new LambdaQueryWrapperX<PlanDO>()
.inIfPresent(PlanDO::getStatus, statusList)
.betweenIfPresent(PlanDO::getStartTime, startTime, endTime)
.orderByDesc(PlanDO::getPriorityNum)
.orderByDesc(PlanDO::getStartTime));
}
} }

@ -2,11 +2,7 @@ package cn.iocoder.yudao.module.mes.service.energydevice;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.HourEnergyValueVO; import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.*;
import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.EnergyDeviceConsumptionReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.EnergyDevicePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.EnergyDeviceRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.EnergyDeviceSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.energydevice.EnergyDeviceCheckRecordDO; import cn.iocoder.yudao.module.mes.dal.dataobject.energydevice.EnergyDeviceCheckRecordDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.energydevice.EnergyDeviceDO; import cn.iocoder.yudao.module.mes.dal.dataobject.energydevice.EnergyDeviceDO;
@ -114,4 +110,6 @@ public interface EnergyDeviceService {
List<HourEnergyValueVO> lastEnergyStatistics(Long deviceTypeId,Long orgId); List<HourEnergyValueVO> lastEnergyStatistics(Long deviceTypeId,Long orgId);
List<DayEnergyValueVO> latestSevenDaysStatistics(Long deviceTypeId, Long orgId);
} }

@ -29,6 +29,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
@ -329,31 +330,29 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService {
// 4. 时间范围:最近 7 小时 // 4. 时间范围:最近 7 小时
LocalDateTime end = LocalDateTime.now(); LocalDateTime end = LocalDateTime.now();
LocalDateTime start = end.minusHours(7); LocalDateTime start = end.minusHours(7);
DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH"); DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH");
// hour -> deviceId -> 数据缓存 // 5. 批量查询所有设备数据
Map<String, Map<Long, TimePointCache>> hourCacheMap = new LinkedHashMap<>(); List<Map<String, Object>> allRows = tDengineService.queryLastDataByHourBatch(deviceIds, start, end);
// 5. 查询每个设备的原始数据
for (Long deviceId : deviceIds) {
List<Map<String, Object>> rawData = tDengineService.queryLastDataByHour(deviceId, start, end);
for (Map<String, Object> row : rawData) { // 6. 按小时 + 设备构建缓存(每小时最新覆盖)
Map<String, Map<Long, TimePointCache>> hourCacheMap = new LinkedHashMap<>();
for (Map<String, Object> row : allRows) {
LocalDateTime ts = (LocalDateTime) row.get("timestamp"); LocalDateTime ts = (LocalDateTime) row.get("timestamp");
if (ts == null) continue;
String hourKey = ts.format(hourFormatter); String hourKey = ts.format(hourFormatter);
Long deviceId = (Long) row.get("deviceId");
String queryData = (String) row.get("queryData");
TimePointCache cache = new TimePointCache(); TimePointCache cache = new TimePointCache();
cache.setTimestamp(ts); cache.setTimestamp(ts);
cache.setQueryData((String) row.get("queryData")); cache.setQueryData(queryData);
// 每小时最新覆盖之前的 hourCacheMap.computeIfAbsent(hourKey, k -> new HashMap<>()).put(deviceId, cache);
hourCacheMap.computeIfAbsent(hourKey, k -> new HashMap<>())
.put(deviceId, cache);
}
} }
// 6. 生成每小时结果,补齐缺失小时 // 7. 生成每小时结果,补齐缺失小时
List<HourEnergyValueVO> result = new ArrayList<>(); List<HourEnergyValueVO> result = new ArrayList<>();
for (int i = 0; i < 7; i++) { for (int i = 0; i < 7; i++) {
LocalDateTime hourTime = start.plusHours(i); LocalDateTime hourTime = start.plusHours(i);
@ -372,6 +371,95 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService {
return result; return result;
} }
@Override
public List<DayEnergyValueVO> latestSevenDaysStatistics(Long deviceTypeId, Long orgId) {
// 找最新的能耗设备配置
EnergyDeviceDO energyDevice = energyDeviceMapper.selectOne(
Wrappers.<EnergyDeviceDO>lambdaQuery()
.eq(EnergyDeviceDO::getDeviceTypeId, deviceTypeId)
.eq(EnergyDeviceDO::getOrgId, orgId)
.orderByDesc(EnergyDeviceDO::getCreateTime)
.last("LIMIT 1")
);
if (energyDevice == null || StringUtils.isBlank(energyDevice.getRules())) {
return Collections.emptyList();
}
// 解析规则
List<OperationRulesVO> rules =
JsonUtils.parseArray(energyDevice.getRules(), OperationRulesVO.class);
if (rules.isEmpty()) {
return Collections.emptyList();
}
// 收集 deviceId
Set<Long> deviceIds = rules.stream()
.map(OperationRulesVO::getDeviceId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (deviceIds.isEmpty()) {
return Collections.emptyList();
}
// 时间范围:前 7 天
LocalDate startDate = LocalDate.now().minusDays(7);
int days = 7;
// 查询 TDengine方案 B
List<Map<String, Object>> rows =
tDengineService.queryLastDataByDaySafe(deviceIds, startDate, days);
// day -> deviceId -> cache
Map<String, Map<Long, TimePointCache>> dayCacheMap = new LinkedHashMap<>();
for (Map<String, Object> row : rows) {
String day = (String) row.get("day");
Long deviceId = (Long) row.get("deviceId");
LocalDateTime ts = (LocalDateTime) row.get("timestamp");
if (day == null || deviceId == null || ts == null) {
continue;
}
TimePointCache cache = new TimePointCache();
cache.setTimestamp(ts);
cache.setQueryData((String) row.get("queryData"));
dayCacheMap
.computeIfAbsent(day, k -> new HashMap<>())
.put(deviceId, cache);
}
// 生成结果(补齐缺失日期)
List<DayEnergyValueVO> result = new ArrayList<>();
for (int i = 0; i < days; i++) {
LocalDate day = startDate.plusDays(i);
String dayKey = day.toString();
Map<Long, TimePointCache> deviceCache =
dayCacheMap.getOrDefault(dayKey, Collections.emptyMap());
Double value = calculateByRules(rules, deviceCache);
DayEnergyValueVO vo = new DayEnergyValueVO();
vo.setDay(dayKey);
vo.setValue(formatDouble(value));
result.add(vo);
}
return result;
}
// =================== 核心方法 =================== // =================== 核心方法 ===================

@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO;
import cn.iocoder.yudao.module.mes.service.itemrequisition.entity.ItemRequisitionAndStock; import cn.iocoder.yudao.module.mes.service.itemrequisition.entity.ItemRequisitionAndStock;
import javax.validation.Valid; import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -104,4 +105,6 @@ public interface PlanService {
* @return * @return
*/ */
PlanRespVO getPlanRespVO(PlanDO planDO); PlanRespVO getPlanRespVO(PlanDO planDO);
List<PlanDO> getPlanCapacity(List<Integer> statusList, LocalDateTime startTime, LocalDateTime endTime);
} }

@ -228,6 +228,13 @@ public class PlanServiceImpl implements PlanService {
return planRespVO; return planRespVO;
} }
@Override
public List<PlanDO> getPlanCapacity(List<Integer> statusList, LocalDateTime startTime, LocalDateTime endTime) {
return planMapper.getPlanCapacity(statusList,startTime,endTime);
}
@Override @Override
public PageResult<PlanRespVO> getPlanPage(PlanPageReqVO pageReqVO) { public PageResult<PlanRespVO> getPlanPage(PlanPageReqVO pageReqVO) {
PageResult<PlanDO> pageResult = planMapper.selectPage(pageReqVO); PageResult<PlanDO> pageResult = planMapper.selectPage(pageReqVO);

Loading…
Cancel
Save