liuwei 2 mesiacov pred
rodič
commit
26f3e72829

+ 12 - 4
src/main/java/com/jeesite/modules/tianHong/entity/MachineAttrHistory.java

@@ -3,13 +3,16 @@ package com.jeesite.modules.tianHong.entity;
 import java.util.Date;
 import java.util.List;
 
+import org.springframework.format.annotation.DateTimeFormat;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.jeesite.common.entity.DataEntity;
 
 /**
  * 设备历史属性Entity
- * 对应 bzh 库 machine_attr_history 表,关联 crutemphistory、alutemphistory、mouldtemphistory、pressurehistory、EleConsumeHistory
+ * 列表/详情由主库 tianhong_backup 按秒展开填充
  * @author ljr
- * @version 2026-07-07
+ * @version 2026-08-15
  */
 public class MachineAttrHistory extends DataEntity<MachineAttrHistory> {
 
@@ -23,7 +26,8 @@ public class MachineAttrHistory extends DataEntity<MachineAttrHistory> {
 	private String prePres;				// 预压压力设定(attr='PrePres')
 	private String holdPressure;		// 保压开始信号(attr='HoldPressure')
 	private String moldFillingPre;		// 充型压力(attr='moldFillingPre')
-	private Date attrEventTime;			// machine_attr_history 最后更新时间
+	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	private Date attrEventTime;			// 属性更新时间(秒)
 
 	// ===== 关联表:crutemphistory 炉膛温度 =====
 	private String cruTemp;				// 炉膛温度
@@ -47,8 +51,12 @@ public class MachineAttrHistory extends DataEntity<MachineAttrHistory> {
 	private String eleConsume;			// 电量
 	private Date eleConsumeTime;		// 电量时间
 
-	// ===== 查询参数:时间范围 =====
+	// ===== 查询参数:时间范围(映射 tianhong_backup.create_date)=====
+	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
 	private Date beginTime;
+	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
 	private Date endTime;
 
 	// ===== 手工分页(避免拦截器对复杂 SQL 二次 LIMIT)=====

+ 319 - 172
src/main/java/com/jeesite/modules/tianHong/service/MachineAttrHistoryService.java

@@ -1,15 +1,20 @@
 package com.jeesite.modules.tianHong.service;
 
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -21,15 +26,36 @@ import com.jeesite.modules.tianHong.entity.MachineAttrHistory;
 
 /**
  * 设备历史属性Service
- * @author ljr
- * @version 2026-07-07
+ * 读主库 mes_cloud_ljr.tianhong_backup(eqp_id + create_date),
+ * create_date 分钟个位向下取整为 10min 窗口末尾,# 串拆成 600 秒展示。
  */
 @Service
 @Transactional(readOnly = true)
 public class MachineAttrHistoryService extends CrudService<MachineAttrHistoryDao, MachineAttrHistory> {
 
+	private static final int POINT_COUNT = 600;
+	/** 列表查询 create_date 跨度上限(毫秒):30 分钟 */
+	private static final long MAX_RANGE_MS = 30L * 60L * 1000L;
+
+	private static final String SELECT_BACKUP_SQL =
+			"SELECT eqp_id, create_date,"
+			+ " cru_temp, alu_temp, mould_info,"
+			+ " theory_pressure, real_pressure, pressure_level, ele_consume,"
+			+ " alu_val, pressurize_time, cooling_time, pre_pres, hold_pressure, mold_filling_pre,"
+			+ " point_count"
+			+ " FROM tianhong_backup"
+			+ " WHERE eqp_id = ?"
+			+ " AND create_date >= ?"
+			+ " AND create_date <= ?"
+			+ " ORDER BY create_date DESC";
+
+	@Autowired
+	private DataSource dataSource;
+
 	/**
-	 * 分页:必须先选设备;按 event_time 倒序,每页默认 20;关联表按「秒」补齐
+	 * 分页:eqpId + beginTime(上传时间/create_date);秒级正序。
+	 * 无截止时间时自动按上传时间起向后取最长 30 分钟;
+	 * 展示从上传时间减 3 秒起,更早的秒隐藏。
 	 */
 	@Override
 	public Page<MachineAttrHistory> findPage(MachineAttrHistory machineAttrHistory) {
@@ -38,11 +64,13 @@ public class MachineAttrHistoryService extends CrudService<MachineAttrHistoryDao
 			page = new Page<>();
 			machineAttrHistory.setPage(page);
 		}
-		if (StringUtils.isBlank(machineAttrHistory.getEqpId())) {
+		if (StringUtils.isBlank(machineAttrHistory.getEqpId())
+				|| machineAttrHistory.getBeginTime() == null) {
 			page.setList(Collections.emptyList());
 			page.setCount(0);
 			return page;
 		}
+
 		int pageSize = page.getPageSize() > 0 ? page.getPageSize() : 20;
 		int pageNo = page.getPageNo() > 0 ? page.getPageNo() : 1;
 		if (pageSize > 100) {
@@ -51,217 +79,336 @@ public class MachineAttrHistoryService extends CrudService<MachineAttrHistoryDao
 		page.setPageSize(pageSize);
 		page.setPageNo(pageNo);
 
-		machineAttrHistory.setPage(null);
-		Long count = dao.findHistoryCount(machineAttrHistory);
-		page.setCount(count == null ? 0L : count);
+		Date uploadTime = machineAttrHistory.getBeginTime();
+		Date begin = uploadTime;
+		Date end = machineAttrHistory.getEndTime();
+		if (end == null || end.before(begin)) {
+			end = new Date(begin.getTime() + MAX_RANGE_MS);
+		}
+		if (end.getTime() - begin.getTime() > MAX_RANGE_MS) {
+			end = new Date(begin.getTime() + MAX_RANGE_MS);
+		}
 
-		machineAttrHistory.setLimit(pageSize);
-		machineAttrHistory.setOffset((pageNo - 1) * pageSize);
-		List<MachineAttrHistory> list = dao.findList(machineAttrHistory);
-		fillRelatedBySecond(machineAttrHistory.getEqpId(), list);
-		page.setList(list);
+		List<BackupRow> backups;
+		try {
+			backups = loadBackups(machineAttrHistory.getEqpId(), begin, end);
+		} catch (Exception e) {
+			logger.error("load tianhong_backup failed, eqpId={}", machineAttrHistory.getEqpId(), e);
+			page.setList(Collections.emptyList());
+			page.setCount(0);
+			return page;
+		}
+
+		List<MachineAttrHistory> all = expandBackups(backups);
+		// 展示从上传时间减 3 秒开始,前面隐藏
+		final long showFromMs = uploadTime.getTime() - 3L * 1000L;
+		List<MachineAttrHistory> filtered = new ArrayList<>();
+		for (MachineAttrHistory row : all) {
+			if (row.getAttrEventTime() != null && row.getAttrEventTime().getTime() >= showFromMs) {
+				filtered.add(row);
+			}
+		}
+		filtered.sort(Comparator.comparing(MachineAttrHistory::getAttrEventTime,
+				Comparator.nullsLast(Comparator.naturalOrder())));
+
+		page.setCount(filtered.size());
+		int from = (pageNo - 1) * pageSize;
+		if (from >= filtered.size()) {
+			page.setList(Collections.emptyList());
+			return page;
+		}
+		int to = Math.min(from + pageSize, filtered.size());
+		page.setList(filtered.subList(from, to));
 		return page;
 	}
 
-	/**
-	 * 查询列表(必须带设备号)
-	 */
 	@Override
 	public List<MachineAttrHistory> findList(MachineAttrHistory machineAttrHistory) {
-		if (StringUtils.isBlank(machineAttrHistory.getEqpId())) {
+		Page<MachineAttrHistory> page = findPage(machineAttrHistory);
+		return page.getList() == null ? Collections.emptyList() : page.getList();
+	}
+
+	/**
+	 * 属性明细:主库 backup 展开后按秒取槽位拼 attrList
+	 */
+	public List<MachineAttrHistory> findAttrList(MachineAttrHistory machineAttrHistory) {
+		MachineAttrHistory one = resolveOneSecond(machineAttrHistory);
+		if (one == null || one.getAttrList() == null) {
 			return Collections.emptyList();
 		}
-		if (machineAttrHistory.getLimit() == null) {
-			machineAttrHistory.setLimit(20);
-		}
-		if (machineAttrHistory.getOffset() == null) {
-			machineAttrHistory.setOffset(0);
-		}
-		List<MachineAttrHistory> list = dao.findList(machineAttrHistory);
-		fillRelatedBySecond(machineAttrHistory.getEqpId(), list);
-		return list;
+		return one.getAttrList();
 	}
 
 	/**
-	 * 用本页时间点截断到秒,按索引范围补齐温压电量(避免大表按秒 JOIN)
+	 * 单条详情(含 attrList),全部主库
 	 */
-	private void fillRelatedBySecond(String eqpId, List<MachineAttrHistory> list) {
-		if (StringUtils.isBlank(eqpId) || list == null || list.isEmpty()) {
-			return;
-		}
-		Set<Date> secSet = new LinkedHashSet<>();
-		for (MachineAttrHistory row : list) {
-			if (row.getAttrEventTime() != null) {
-				secSet.add(truncateToSecond(row.getAttrEventTime()));
-			}
+	@Override
+	public MachineAttrHistory get(MachineAttrHistory machineAttrHistory) {
+		if (StringUtils.isBlank(machineAttrHistory.getEqpId())) {
+			return null;
 		}
-		if (secSet.isEmpty()) {
-			return;
+		MachineAttrHistory entity = resolveOneSecond(machineAttrHistory);
+		if (entity == null) {
+			entity = new MachineAttrHistory();
+			entity.setEqpId(machineAttrHistory.getEqpId());
+			entity.setAttrList(Collections.emptyList());
 		}
-		List<Date> secList = new ArrayList<>(secSet);
-
-		Map<Long, MachineAttrHistory> cruMap = latestBySec(dao.findCruBySecList(eqpId, secList));
-		Map<Long, MachineAttrHistory> aluMap = latestBySec(dao.findAluBySecList(eqpId, secList));
-		Map<Long, MachineAttrHistory> mouldMap = latestBySec(dao.findMouldBySecList(eqpId, secList));
-		Map<Long, MachineAttrHistory> pressMap = latestBySec(dao.findPressureBySecList(eqpId, secList));
-		Map<Long, MachineAttrHistory> eleMap = latestBySec(dao.findEleBySecList(eqpId, secList));
+		return entity;
+	}
 
-		for (MachineAttrHistory row : list) {
-			if (row.getAttrEventTime() == null) {
-				continue;
-			}
-			Long key = secKey(row.getAttrEventTime());
-			MachineAttrHistory cru = cruMap.get(key);
-			if (cru != null) {
-				row.setCruTemp(cru.getCruTemp());
-				row.setCruTempTime(cru.getCruTempTime());
-			}
-			MachineAttrHistory alu = aluMap.get(key);
-			if (alu != null) {
-				row.setAluTemp(alu.getAluTemp());
-				row.setAluTempTime(alu.getAluTempTime());
-			}
-			MachineAttrHistory mould = mouldMap.get(key);
-			if (mould != null) {
-				row.setMouldInfo(mould.getMouldInfo());
-				row.setMouldTempTime(mould.getMouldTempTime());
+	private MachineAttrHistory resolveOneSecond(MachineAttrHistory query) {
+		if (query == null || StringUtils.isBlank(query.getEqpId())) {
+			return null;
+		}
+		Date target = resolveTargetSecond(query);
+		if (target == null) {
+			return null;
+		}
+		Date windowStart = floorTo10Minutes(target);
+		Date windowEnd = addMinutes(windowStart, 10);
+		Date createFrom = windowEnd;
+		Date createTo = addMinutes(windowEnd, 10);
+		List<BackupRow> backups;
+		try {
+			backups = loadBackups(query.getEqpId(), createFrom, createTo);
+			if (backups.isEmpty()) {
+				backups = loadBackups(query.getEqpId(), addMinutes(windowEnd, -1), createTo);
 			}
-			MachineAttrHistory press = pressMap.get(key);
-			if (press != null) {
-				row.setTheoryPressure(press.getTheoryPressure());
-				row.setRealPressure(press.getRealPressure());
-				row.setPressureLevel(press.getPressureLevel());
-				row.setPressureTime(press.getPressureTime());
+		} catch (Exception e) {
+			logger.error("load tianhong_backup for detail failed, eqpId={}", query.getEqpId(), e);
+			return null;
+		}
+		BackupRow hit = null;
+		for (BackupRow row : backups) {
+			Date we = floorTo10Minutes(row.createDate);
+			Date ws = addMinutes(we, -10);
+			if (!target.before(ws) && target.before(we)) {
+				hit = row;
+				break;
 			}
-			MachineAttrHistory ele = eleMap.get(key);
-			if (ele != null) {
-				row.setEleConsume(ele.getEleConsume());
-				row.setEleConsumeTime(ele.getEleConsumeTime());
+		}
+		if (hit == null && !backups.isEmpty()) {
+			hit = backups.get(0);
+			windowEnd = floorTo10Minutes(hit.createDate);
+			windowStart = addMinutes(windowEnd, -10);
+		} else if (hit != null) {
+			windowEnd = floorTo10Minutes(hit.createDate);
+			windowStart = addMinutes(windowEnd, -10);
+		} else {
+			return null;
+		}
+		int idx = secIndex(target, windowStart);
+		if (idx < 0 || idx >= POINT_COUNT) {
+			return null;
+		}
+		MachineAttrHistory row = buildSecondRow(query.getEqpId(), windowStart, idx, hit);
+		row.setAttrList(buildAttrList(row));
+		return row;
+	}
+
+	private Date resolveTargetSecond(MachineAttrHistory query) {
+		if (StringUtils.isNotBlank(query.getEventTimeKey())) {
+			Date parsed = parseEventTimeKey(query.getEventTimeKey());
+			if (parsed != null) {
+				return truncateToSecond(parsed);
 			}
 		}
+		if (query.getEventTime() != null) {
+			return truncateToSecond(query.getEventTime());
+		}
+		if (query.getAttrEventTime() != null) {
+			return truncateToSecond(query.getAttrEventTime());
+		}
+		if (query.getBeginTime() != null) {
+			return truncateToSecond(query.getBeginTime());
+		}
+		return null;
 	}
 
-	/** 同一秒多条时保留 EventTime 最新的一条 */
-	private Map<Long, MachineAttrHistory> latestBySec(List<MachineAttrHistory> rows) {
-		Map<Long, MachineAttrHistory> map = new HashMap<>();
-		if (rows == null) {
-			return map;
+	/** eventTimeKey: yyyyMMddHHmmss + 微秒6位,共20位 */
+	private Date parseEventTimeKey(String key) {
+		String k = StringUtils.trim(key);
+		if (k == null || k.length() < 14) {
+			return null;
 		}
-		for (MachineAttrHistory row : rows) {
-			Date t = row.getEventTime();
-			if (t == null) {
-				t = row.getCruTempTime();
-			}
-			if (t == null) {
-				t = row.getAluTempTime();
-			}
-			if (t == null) {
-				t = row.getMouldTempTime();
-			}
-			if (t == null) {
-				t = row.getPressureTime();
-			}
-			if (t == null) {
-				t = row.getEleConsumeTime();
-			}
-			if (t == null) {
-				continue;
-			}
-			Long key = secKey(t);
-			MachineAttrHistory old = map.get(key);
-			if (old == null || compareTime(t, resolveTime(old)) > 0) {
-				if (row.getEventTime() == null) {
-					row.setEventTime(t);
+		try {
+			return new SimpleDateFormat("yyyyMMddHHmmss").parse(k.substring(0, 14));
+		} catch (Exception e) {
+			return null;
+		}
+	}
+
+	private List<BackupRow> loadBackups(String eqpId, Date createFrom, Date createTo) throws Exception {
+		List<BackupRow> list = new ArrayList<>();
+		try (Connection conn = dataSource.getConnection();
+				PreparedStatement ps = conn.prepareStatement(SELECT_BACKUP_SQL)) {
+			ps.setString(1, eqpId);
+			ps.setTimestamp(2, new Timestamp(createFrom.getTime()));
+			ps.setTimestamp(3, new Timestamp(createTo.getTime()));
+			try (ResultSet rs = ps.executeQuery()) {
+				while (rs.next()) {
+					BackupRow row = new BackupRow();
+					row.eqpId = rs.getString("eqp_id");
+					Timestamp cd = rs.getTimestamp("create_date");
+					row.createDate = cd == null ? null : new Date(cd.getTime());
+					row.cruTemp = rs.getString("cru_temp");
+					row.aluTemp = rs.getString("alu_temp");
+					row.mouldInfo = rs.getString("mould_info");
+					row.theoryPressure = rs.getString("theory_pressure");
+					row.realPressure = rs.getString("real_pressure");
+					row.pressureLevel = rs.getString("pressure_level");
+					row.eleConsume = rs.getString("ele_consume");
+					row.aluVal = rs.getString("alu_val");
+					row.pressurizeTime = rs.getString("pressurize_time");
+					row.coolingTime = rs.getString("cooling_time");
+					row.prePres = rs.getString("pre_pres");
+					row.holdPressure = rs.getString("hold_pressure");
+					row.moldFillingPre = rs.getString("mold_filling_pre");
+					list.add(row);
 				}
-				map.put(key, row);
 			}
 		}
-		return map;
+		return list;
 	}
 
-	private Date resolveTime(MachineAttrHistory row) {
-		if (row.getEventTime() != null) {
-			return row.getEventTime();
-		}
-		if (row.getCruTempTime() != null) {
-			return row.getCruTempTime();
+	private List<MachineAttrHistory> expandBackups(List<BackupRow> backups) {
+		List<MachineAttrHistory> result = new ArrayList<>();
+		if (backups == null || backups.isEmpty()) {
+			return result;
 		}
-		if (row.getAluTempTime() != null) {
-			return row.getAluTempTime();
+		for (BackupRow backup : backups) {
+			if (backup.createDate == null) {
+				continue;
+			}
+			Date windowEnd = floorTo10Minutes(backup.createDate);
+			Date windowStart = addMinutes(windowEnd, -10);
+			for (int i = 0; i < POINT_COUNT; i++) {
+				result.add(buildSecondRow(backup.eqpId, windowStart, i, backup));
+			}
 		}
-		if (row.getMouldTempTime() != null) {
-			return row.getMouldTempTime();
+		return result;
+	}
+
+	private MachineAttrHistory buildSecondRow(String eqpId, Date windowStart, int idx, BackupRow backup) {
+		Date sec = new Date(windowStart.getTime() + idx * 1000L);
+		MachineAttrHistory row = new MachineAttrHistory();
+		row.setEqpId(eqpId);
+		row.setAttrEventTime(sec);
+		row.setEventTime(sec);
+		row.setId(eqpId + "_" + formatEventTimeKey(sec));
+
+		row.setCruTemp(slot(backup.cruTemp, idx));
+		row.setAluTemp(slot(backup.aluTemp, idx));
+		row.setMouldInfo(slot(backup.mouldInfo, idx));
+		row.setTheoryPressure(slot(backup.theoryPressure, idx));
+		row.setRealPressure(slot(backup.realPressure, idx));
+		row.setPressureLevel(slot(backup.pressureLevel, idx));
+		row.setEleConsume(slot(backup.eleConsume, idx));
+		row.setAluVal(slot(backup.aluVal, idx));
+		row.setPressurizeTime(slot(backup.pressurizeTime, idx));
+		row.setCoolingTime(slot(backup.coolingTime, idx));
+		row.setPrePres(slot(backup.prePres, idx));
+		row.setHoldPressure(slot(backup.holdPressure, idx));
+		row.setMoldFillingPre(slot(backup.moldFillingPre, idx));
+
+		row.setCruTempTime(sec);
+		row.setAluTempTime(sec);
+		row.setMouldTempTime(sec);
+		row.setPressureTime(sec);
+		row.setEleConsumeTime(sec);
+		return row;
+	}
+
+	private List<MachineAttrHistory> buildAttrList(MachineAttrHistory row) {
+		List<MachineAttrHistory> list = new ArrayList<>();
+		addAttr(list, row, "alu_val", row.getAluVal());
+		addAttr(list, row, "pressurizeTime", row.getPressurizeTime());
+		addAttr(list, row, "coolingTime", row.getCoolingTime());
+		addAttr(list, row, "PrePres", row.getPrePres());
+		addAttr(list, row, "HoldPressure", row.getHoldPressure());
+		addAttr(list, row, "moldFillingPre", row.getMoldFillingPre());
+		addAttr(list, row, "cruTemp", row.getCruTemp());
+		addAttr(list, row, "aluTemp", row.getAluTemp());
+		addAttr(list, row, "mouldInfo", row.getMouldInfo());
+		addAttr(list, row, "theoryPressure", row.getTheoryPressure());
+		addAttr(list, row, "realPressure", row.getRealPressure());
+		addAttr(list, row, "pressureLevel", row.getPressureLevel());
+		addAttr(list, row, "eleConsume", row.getEleConsume());
+		return list;
+	}
+
+	private void addAttr(List<MachineAttrHistory> list, MachineAttrHistory src, String attr, String val) {
+		MachineAttrHistory item = new MachineAttrHistory();
+		item.setEqpId(src.getEqpId());
+		item.setAttr(attr);
+		item.setVal(val == null ? "" : val);
+		item.setEventTime(src.getAttrEventTime());
+		list.add(item);
+	}
+
+	private static String slot(String series, int idx) {
+		if (series == null) {
+			return "";
 		}
-		if (row.getPressureTime() != null) {
-			return row.getPressureTime();
+		String[] parts = series.split("#", -1);
+		if (idx < 0 || idx >= parts.length) {
+			return "";
 		}
-		return row.getEleConsumeTime();
+		return parts[idx] == null ? "" : parts[idx];
 	}
 
-	private int compareTime(Date a, Date b) {
-		if (a == null && b == null) {
-			return 0;
-		}
-		if (a == null) {
+	private static int secIndex(Date eventTime, Date windowStart) {
+		long ms = eventTime.getTime() - windowStart.getTime();
+		if (ms < 0) {
 			return -1;
 		}
-		if (b == null) {
-			return 1;
-		}
-		return a.compareTo(b);
+		return (int) (ms / 1000L);
 	}
 
-	private Date truncateToSecond(Date date) {
+	/** 分钟个位向下取整(对 10 取整),秒/毫秒清零 → 10min 窗口锚点/末尾 */
+	private static Date floorTo10Minutes(Date date) {
 		Calendar cal = Calendar.getInstance();
 		cal.setTime(date);
+		cal.set(Calendar.SECOND, 0);
 		cal.set(Calendar.MILLISECOND, 0);
+		int minute = cal.get(Calendar.MINUTE);
+		cal.set(Calendar.MINUTE, minute - (minute % 10));
 		return cal.getTime();
 	}
 
-	/** 用 epoch 秒做 map key,避免 Date 毫秒差异 */
-	private Long secKey(Date date) {
-		return date.getTime() / 1000L;
+	private static Date addMinutes(Date date, int minutes) {
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(date);
+		cal.add(Calendar.MINUTE, minutes);
+		return cal.getTime();
 	}
 
-	/**
-	 * 查询指定设备的所有属性明细(用于详情展示)
-	 */
-	public List<MachineAttrHistory> findAttrList(MachineAttrHistory machineAttrHistory) {
-		if (StringUtils.isBlank(machineAttrHistory.getEqpId())) {
-			return Collections.emptyList();
-		}
-		return dao.findAttrList(machineAttrHistory);
+	private static Date truncateToSecond(Date date) {
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(date);
+		cal.set(Calendar.MILLISECOND, 0);
+		return cal.getTime();
 	}
 
-	/**
-	 * 获取单条数据(按 eqp_id + 快照时间查询属性明细)
-	 * 须带 eventTimeKey / eventTime / beginTime~endTime,禁止无时间条件全量加载
-	 */
-	@Override
-	public MachineAttrHistory get(MachineAttrHistory machineAttrHistory) {
-		if (StringUtils.isBlank(machineAttrHistory.getEqpId())) {
-			return null;
-		}
-		MachineAttrHistory entity = new MachineAttrHistory();
-		entity.setEqpId(machineAttrHistory.getEqpId());
-		entity.setAttrEventTime(machineAttrHistory.getEventTime());
-		entity.setEventTimeKey(machineAttrHistory.getEventTimeKey());
-		boolean hasTime = StringUtils.isNotBlank(machineAttrHistory.getEventTimeKey())
-				|| machineAttrHistory.getEventTime() != null
-				|| machineAttrHistory.getBeginTime() != null
-				|| machineAttrHistory.getEndTime() != null;
-		if (!hasTime) {
-			entity.setAttrList(Collections.emptyList());
-			return entity;
-		}
-		MachineAttrHistory queryParam = new MachineAttrHistory();
-		queryParam.setEqpId(machineAttrHistory.getEqpId());
-		queryParam.setEventTimeKey(machineAttrHistory.getEventTimeKey());
-		queryParam.setEventTime(machineAttrHistory.getEventTime());
-		queryParam.setBeginTime(machineAttrHistory.getBeginTime());
-		queryParam.setEndTime(machineAttrHistory.getEndTime());
-		entity.setAttrList(dao.findAttrList(queryParam));
-		return entity;
+	private static String formatEventTimeKey(Date sec) {
+		return new SimpleDateFormat("yyyyMMddHHmmss").format(sec) + "000000";
 	}
 
+	private static final class BackupRow {
+		String eqpId;
+		Date createDate;
+		String cruTemp;
+		String aluTemp;
+		String mouldInfo;
+		String theoryPressure;
+		String realPressure;
+		String pressureLevel;
+		String eleConsume;
+		String aluVal;
+		String pressurizeTime;
+		String coolingTime;
+		String prePres;
+		String holdPressure;
+		String moldFillingPre;
+	}
 }

+ 9 - 4
src/main/java/com/jeesite/modules/tianHong/web/MachineAttrHistoryController.java

@@ -25,9 +25,10 @@ import com.jeesite.modules.tianHong.service.MachineAttrHistoryService;
 
 /**
  * 设备历史属性Controller
- * 对应 bzh 库 machine_attr_history 表,关联温度/压力/电量等历史表
+ * 列表/详情读主库 mes_cloud_ljr.tianhong_backup(eqp_id + create_date),
+ * 按 create_date 分钟个位取整得到 10min 窗口末尾后拆 # 为秒级数据。
  * @author ljr
- * @version 2026-07-07
+ * @version 2026-08-15
  */
 @Controller
 @RequestMapping(value = "${adminPath}/tianHong/machineAttrHistory")
@@ -86,12 +87,16 @@ public class MachineAttrHistoryController extends BaseController {
 	@RequiresPermissions("tianHong:machineAttrHistory:view")
 	@RequestMapping(value = { "list", "" })
 	public String list(MachineAttrHistory machineAttrHistory, Model model) {
+		// 默认上传时间=当前
+		if (machineAttrHistory.getBeginTime() == null) {
+			machineAttrHistory.setBeginTime(new Date());
+		}
 		model.addAttribute("machineAttrHistory", machineAttrHistory);
 		return "modules/tianHong/machineAttrHistoryList";
 	}
 
 	/**
-	 * 查询列表数据:须传设备号;按 event_time 倒序分页(默认每页 20)
+	 * 查询列表数据:须传设备号 + 上传时间(create_date);秒级正序分页
 	 */
 	@RequiresPermissions("tianHong:machineAttrHistory:view")
 	@RequestMapping(value = "listData")
@@ -103,7 +108,7 @@ public class MachineAttrHistoryController extends BaseController {
 	}
 
 	/**
-	 * 查询指定设备的所有属性明细(用于详情模态框)
+	 * 查询指定设备某一秒的属性明细(主库 backup)
 	 */
 	@RequiresPermissions("tianHong:machineAttrHistory:view")
 	@RequestMapping(value = "attrListData")

+ 1 - 0
src/main/resources/config/application.yml

@@ -684,6 +684,7 @@ shiro:
     ${adminPath}/mes/mesProductRecord/ccdcheck = anon
     ${adminPath}/mes/mesProductRecord/updateOprno = anon
     ${adminPath}/mes/mesProductRecord/checkQuality = anon
+    ${adminPath}/mes/mesProductTj/upload = anon
     ${adminPath}/mes/mesProduct/alarmData = anon
     ${adminPath}/mes/mesProcessAlarm/add = anon
     ${adminPath}/mes/mesProductRecord/checkDJ = anon

+ 8 - 1
src/main/resources/views/modules/tianHong/machineAttrHistoryForm.html

@@ -27,7 +27,14 @@ var attrNameMap = {
 	'coolingTime': '冷却时间',
 	'PrePres': '预压压力设定',
 	'HoldPressure': '保压开始信号',
-	'moldFillingPre': '各段压力(冲型压力)'
+	'moldFillingPre': '各段压力(冲型压力)',
+	'cruTemp': '炉膛温度',
+	'aluTemp': '铝水温度',
+	'mouldInfo': '模具温度',
+	'theoryPressure': '理论压力',
+	'realPressure': '实际压力',
+	'pressureLevel': '压力段位',
+	'eleConsume': '电量总计'
 };
 var attrNameRules = [
 	{re:/^COOL_FLOW_(\d+)$/, name:function(m){ return '冷却流量['+m[1]+']'; }},

+ 116 - 41
src/main/resources/views/modules/tianHong/machineAttrHistoryList.html

@@ -1,4 +1,58 @@
 <% layout('/layouts/default.html', {title: '设备历史属性', libs: ['dataGrid','laydate']}){ %>
+<style>
+	/* 仅本页查询区样式,不影响其他模块 */
+	.mah-search {
+		display: flex;
+		flex-wrap: wrap;
+		align-items: center;
+		gap: 12px 20px;
+		padding: 14px 16px;
+		margin-bottom: 12px;
+		background: #f7f9fc;
+		border: 1px solid #e6ebf2;
+		border-radius: 6px;
+	}
+	.mah-search .mah-item {
+		display: flex;
+		align-items: center;
+		margin: 0;
+	}
+	.mah-search .mah-item > label {
+		margin: 0 8px 0 0;
+		white-space: nowrap;
+		color: #556070;
+		font-weight: 500;
+	}
+	.mah-search .form-control {
+		height: 34px;
+		border-radius: 4px;
+		border-color: #d0d7e2;
+		box-shadow: none;
+	}
+	.mah-search .form-control:focus {
+		border-color: #5b9bd5;
+		box-shadow: 0 0 0 2px rgba(91, 155, 213, 0.15);
+	}
+	.mah-search #eqpId {
+		width: 140px;
+	}
+	.mah-search #beginTime {
+		width: 178px;
+		cursor: pointer;
+		background: #fff;
+	}
+	.mah-search .mah-actions {
+		display: flex;
+		gap: 8px;
+		margin-left: auto;
+	}
+	.mah-search .mah-actions .btn {
+		min-width: 72px;
+		height: 34px;
+		padding: 0 14px;
+		border-radius: 4px;
+	}
+</style>
 <div class="main-content">
 	<div class="box box-main">
 		<div class="box-header">
@@ -14,29 +68,24 @@
 		<div class="box-body">
 			<#form:form id="searchForm" model="${machineAttrHistory}" action="${ctx}/tianHong/machineAttrHistory/listData" method="post" class="form-inline hide"
 					data-page-no="${parameter.pageNo}" data-page-size="20" data-order-by="${parameter.orderBy}">
-				<div class="form-group">
-				<label class="control-label">${text('设备号')}:</label>
-				<div class="control-inline">
-					<#form:input path="eqpId" maxlength="40" class="form-control width-120" required="true"/>
-				</div>
-			</div>
-			<div class="form-group">
-				<label class="control-label">${text('开始时间')}:</label>
-				<div class="control-inline">
-					<#form:input path="beginTime" maxlength="20" class="form-control laydate width-160"
-						dateFmt="yyyy-MM-dd HH:mm:ss" />
-				</div>
-			</div>
-			<div class="form-group">
-				<label class="control-label">${text('结束时间')}:</label>
-				<div class="control-inline">
-					<#form:input path="endTime" maxlength="20" class="form-control laydate width-160"
-						dateFmt="yyyy-MM-dd HH:mm:ss" />
-				</div>
-			</div>
-				<div class="form-group">
-					<button type="submit" class="btn btn-primary btn-sm">${text('查询')}</button>
-					<button type="reset" class="btn btn-default btn-sm">${text('重置')}</button>
+				<div class="mah-search">
+					<div class="mah-item">
+						<label>${text('设备号')}</label>
+						<#form:input path="eqpId" maxlength="40" class="form-control" required="true" placeholder="如 D001"/>
+					</div>
+					<div class="mah-item">
+						<label>${text('上传时间')}</label>
+						<input type="text" id="beginTime" name="beginTime" readonly="readonly"
+							maxlength="19" class="form-control laydate required"
+							data-type="datetime" data-format="yyyy-MM-dd HH:mm:ss"
+							data-msg-required="请选择上传时间"
+							placeholder="上传时间"
+							value="${machineAttrHistory.beginTime!, 'yyyy-MM-dd HH:mm:ss'}"/>
+					</div>
+					<div class="mah-actions">
+						<button type="submit" class="btn btn-primary btn-sm"><i class="fa fa-search"></i> ${text('查询')}</button>
+						<button type="reset" class="btn btn-default btn-sm"><i class="fa fa-undo"></i> ${text('重置')}</button>
+					</div>
 				</div>
 			</#form:form>
 			<table id="dataGrid"></table>
@@ -46,32 +95,39 @@
 </div>
 <% } %>
 <script>
-// 初始化DataGrid对象(须先填设备号;每页 20,时间倒序由后端保证)
+// 初始化DataGrid对象(须先填设备号;每页 20,时间正序由后端保证)
 $('#dataGrid').dataGrid({
 	searchForm: $("#searchForm"),
 	pageSize: 20,
+	shrinkToFit: false,
 	columnModel: [
-		{header:'${text("设备号")}', name:'eqpId', index:'t.eqp_id', width:120, align:"center", frozen:true},
-		{header:'${text("炉膛温度(℃)")}', name:'cruTemp', index:'c.Value', width:110, align:"center"},
-		{header:'${text("铝水温度(℃)")}', name:'aluTemp', index:'a.Value', width:110, align:"center"},
-		{header:'${text("模具温度(℃)")}', name:'mouldInfo', index:'mt.mould_info', width:200, align:"center"},
-		{header:'${text("理论压力")}', name:'theoryPressure', index:'p.TheoryValue', width:100, align:"center"},
-		{header:'${text("实际压力")}', name:'realPressure', index:'p.RealValue', width:100, align:"center"},
-		{header:'${text("压力段位")}', name:'pressureLevel', index:'p.LevelID', width:90, align:"center"},
-		{header:'${text("电量总计")}', name:'eleConsume', index:'e.Value', width:110, align:"center"},
-		{header:'${text("剩余模次")}', name:'aluVal', width:90, align:"center"},
-		{header:'${text("保压时间")}', name:'pressurizeTime', width:90, align:"center"},
-		{header:'${text("冷却时间")}', name:'coolingTime', width:90, align:"center"},
-		{header:'${text("预压压力设定")}', name:'prePres', width:110, align:"center"},
-		{header:'${text("保压开始信号")}', name:'holdPressure', width:110, align:"center"},
-		{header:'${text("充型压力")}', name:'moldFillingPre', width:100, align:"center"},
-		{header:'${text("属性更新时间")}', name:'attrEventTime', width:150, align:"center"},
+		{header:'${text("设备号")}', name:'eqpId', index:'t.eqp_id', width:100, align:"center", frozen:true},
+		{header:'${text("炉膛温度(℃)")}', name:'cruTemp', index:'c.Value', width:100, align:"center"},
+		{header:'${text("铝水温度(℃)")}', name:'aluTemp', index:'a.Value', width:100, align:"center"},
+		{header:'${text("模具温度(℃)")}', name:'mouldInfo', index:'mt.mould_info', width:160, align:"center"},
+		{header:'${text("理论压力")}', name:'theoryPressure', index:'p.TheoryValue', width:90, align:"center"},
+		{header:'${text("实际压力")}', name:'realPressure', index:'p.RealValue', width:90, align:"center"},
+		{header:'${text("压力段位")}', name:'pressureLevel', index:'p.LevelID', width:80, align:"center"},
+		{header:'${text("电量总计")}', name:'eleConsume', index:'e.Value', width:90, align:"center"},
+		{header:'${text("剩余模次")}', name:'aluVal', width:80, align:"center"},
+		{header:'${text("保压时间")}', name:'pressurizeTime', width:80, align:"center"},
+		{header:'${text("冷却时间")}', name:'coolingTime', width:80, align:"center"},
+		{header:'${text("预压压力设定")}', name:'prePres', width:100, align:"center"},
+		{header:'${text("保压开始信号")}', name:'holdPressure', width:100, align:"center"},
+		{header:'${text("充型压力")}', name:'moldFillingPre', width:90, align:"center"},
+		{header:'${text("属性更新时间")}', name:'attrEventTime', width:170, align:"center",
+			formatter: function(val){
+				if (!val) return '';
+				if (typeof val === 'string') {
+					return val.length >= 19 ? val.substring(0, 19) : val;
+				}
+				return val;
+			}},
 		{header:'${text("操作")}', name:'actions', width:120, align:"center", formatter: function(val, obj, row, act){
 			var actions = [];
 			var eqpId = js.val(row,'eqpId');
 			var rowId = js.val(row,'id') || '';
 			var href = '${ctx}/tianHong/machineAttrHistory/form?id=' + encodeURIComponent(eqpId || '');
-			// id 形如 D001_20260803172341921000,取末尾 20 位时间键精确查明细
 			if (rowId && /_\d{20}$/.test(rowId)) {
 				href += '&eventTimeKey=' + encodeURIComponent(rowId.slice(-20));
 			} else {
@@ -86,8 +142,27 @@ $('#dataGrid').dataGrid({
 	]
 });
 
-// 刷新按钮
 $('#btnRefresh').click(function(){
 	$('#dataGrid').dataGrid('refresh');
 });
+
+function pad(n){ return n < 10 ? '0' + n : '' + n; }
+function formatDate(d) {
+	return d.getFullYear() + '-' + pad(d.getMonth()+1) + '-' + pad(d.getDate())
+		+ ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
+}
+function formatNow() { return formatDate(new Date()); }
+
+(function ensureDateTime(){
+	var re = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
+	var $b = $('#beginTime');
+	if (!$b.val() || !re.test($b.val())) {
+		$b.val(formatNow());
+	}
+})();
+$('#searchForm').on('reset', function(){
+	setTimeout(function(){
+		$('#beginTime').val(formatNow());
+	}, 0);
+});
 </script>