dev #2

Merged
dashixiong merged 349 commits from dev into main 2026-02-11 01:55:46 +00:00
214 changed files with 23163 additions and 954 deletions
Showing only changes of commit 725ae14548 - Show all commits

View File

@ -6,7 +6,7 @@ spring:
druid:
# 主库数据源
master:
url: jdbc:mysql://122.51.194.184:13306/setri_ems?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://122.51.194.184:13306/setri_ems?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: ems
password: 12345678
# 从库数据源

View File

@ -51,4 +51,9 @@ public class RedisKeyConstants
* BMSD原始数据 redis key
*/
public static final String ORIGINAL_BMSD = "BMSD_";
/**
* mqtt监听的原始报文
*/
public static final String MQTT_ORIGINAL = "mqtt_";
}

View File

@ -276,4 +276,15 @@ public class RedisCache
{
return redisTemplate.delete(key);
}
/**
* 批量缓存
* @param cacheMap
*/
public <T> void multiSet(final Map<String, String> cacheMap)
{
redisTemplate.opsForValue().multiSet(cacheMap);
}
}

View File

@ -200,4 +200,10 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
long date = calendar.get(Calendar.DAY_OF_MONTH); // 月份从0开始所以要加1
return date;
}
// LocalDateTime 转 Date带时区
public static Date convertToDate(LocalDateTime localDateTime) {
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zonedDateTime.toInstant());
}
}

View File

@ -0,0 +1,242 @@
package com.xzzn.ems.domain;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xzzn.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.xzzn.common.annotation.Excel;
/**
* 单体电池天级数据对象 ems_battery_data_day
*
* @author xzzn
* @date 2025-08-20
*/
public class EmsBatteryDataDay extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** $column.columnComment */
private Long id;
/** 电池堆 */
@Excel(name = "电池堆")
private String batteryPack;
/** 电池簇 */
@Excel(name = "电池簇")
private String batteryCluster;
/** 单体编号 */
@Excel(name = "单体编号")
private String batteryCellId;
/** 电压 (V) */
@Excel(name = "电压 (V)")
private BigDecimal voltage;
/** 温度 (℃) */
@Excel(name = "温度 (℃)")
private BigDecimal temperature;
/** SOC (%) */
@Excel(name = "SOC (%)")
private BigDecimal soc;
/** SOH (%) */
@Excel(name = "SOH (%)")
private BigDecimal soh;
/** 数据采集时间 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "数据采集时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date dataTimestamp;
/** 站点id */
@Excel(name = "站点id")
private String siteId;
/** 设备唯一标识符 */
@Excel(name = "设备唯一标识符")
private String deviceId;
/** 簇设备id */
@Excel(name = "簇设备id")
private String clusterDeviceId;
/** 单体电池内阻 */
@Excel(name = "单体电池内阻")
private BigDecimal interResistance;
/** 天级时间维度 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "天级时间维度", width = 30, dateFormat = "yyyy-MM-dd")
private Date dayTime;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setBatteryPack(String batteryPack)
{
this.batteryPack = batteryPack;
}
public String getBatteryPack()
{
return batteryPack;
}
public void setBatteryCluster(String batteryCluster)
{
this.batteryCluster = batteryCluster;
}
public String getBatteryCluster()
{
return batteryCluster;
}
public void setBatteryCellId(String batteryCellId)
{
this.batteryCellId = batteryCellId;
}
public String getBatteryCellId()
{
return batteryCellId;
}
public void setVoltage(BigDecimal voltage)
{
this.voltage = voltage;
}
public BigDecimal getVoltage()
{
return voltage;
}
public void setTemperature(BigDecimal temperature)
{
this.temperature = temperature;
}
public BigDecimal getTemperature()
{
return temperature;
}
public void setSoc(BigDecimal soc)
{
this.soc = soc;
}
public BigDecimal getSoc()
{
return soc;
}
public void setSoh(BigDecimal soh)
{
this.soh = soh;
}
public BigDecimal getSoh()
{
return soh;
}
public void setDataTimestamp(Date dataTimestamp)
{
this.dataTimestamp = dataTimestamp;
}
public Date getDataTimestamp()
{
return dataTimestamp;
}
public void setSiteId(String siteId)
{
this.siteId = siteId;
}
public String getSiteId()
{
return siteId;
}
public void setDeviceId(String deviceId)
{
this.deviceId = deviceId;
}
public String getDeviceId()
{
return deviceId;
}
public void setClusterDeviceId(String clusterDeviceId)
{
this.clusterDeviceId = clusterDeviceId;
}
public String getClusterDeviceId()
{
return clusterDeviceId;
}
public void setInterResistance(BigDecimal interResistance)
{
this.interResistance = interResistance;
}
public BigDecimal getInterResistance()
{
return interResistance;
}
public void setDayTime(Date dayTime)
{
this.dayTime = dayTime;
}
public Date getDayTime()
{
return dayTime;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("batteryPack", getBatteryPack())
.append("batteryCluster", getBatteryCluster())
.append("batteryCellId", getBatteryCellId())
.append("voltage", getVoltage())
.append("temperature", getTemperature())
.append("soc", getSoc())
.append("soh", getSoh())
.append("dataTimestamp", getDataTimestamp())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.append("siteId", getSiteId())
.append("deviceId", getDeviceId())
.append("clusterDeviceId", getClusterDeviceId())
.append("interResistance", getInterResistance())
.append("dayTime", getDayTime())
.toString();
}
}

View File

@ -0,0 +1,242 @@
package com.xzzn.ems.domain;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xzzn.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.xzzn.common.annotation.Excel;
/**
* 单体电池小时级数据对象 ems_battery_data_hour
*
* @author xzzn
* @date 2025-08-19
*/
public class EmsBatteryDataHour extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** $column.columnComment */
private Long id;
/** 电池堆 */
@Excel(name = "电池堆")
private String batteryPack;
/** 电池簇 */
@Excel(name = "电池簇")
private String batteryCluster;
/** 单体编号 */
@Excel(name = "单体编号")
private String batteryCellId;
/** 电压 (V) */
@Excel(name = "电压 (V)")
private BigDecimal voltage;
/** 温度 (℃) */
@Excel(name = "温度 (℃)")
private BigDecimal temperature;
/** SOC (%) */
@Excel(name = "SOC (%)")
private BigDecimal soc;
/** SOH (%) */
@Excel(name = "SOH (%)")
private BigDecimal soh;
/** 数据采集时间 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "数据采集时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date dataTimestamp;
/** 站点id */
@Excel(name = "站点id")
private String siteId;
/** 设备唯一标识符 */
@Excel(name = "设备唯一标识符")
private String deviceId;
/** 簇设备id */
@Excel(name = "簇设备id")
private String clusterDeviceId;
/** 单体电池内阻 */
@Excel(name = "单体电池内阻")
private BigDecimal interResistance;
/** 小时级时间维度 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "小时级时间维度", width = 30, dateFormat = "yyyy-MM-dd")
private Date hourTime;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setBatteryPack(String batteryPack)
{
this.batteryPack = batteryPack;
}
public String getBatteryPack()
{
return batteryPack;
}
public void setBatteryCluster(String batteryCluster)
{
this.batteryCluster = batteryCluster;
}
public String getBatteryCluster()
{
return batteryCluster;
}
public void setBatteryCellId(String batteryCellId)
{
this.batteryCellId = batteryCellId;
}
public String getBatteryCellId()
{
return batteryCellId;
}
public void setVoltage(BigDecimal voltage)
{
this.voltage = voltage;
}
public BigDecimal getVoltage()
{
return voltage;
}
public void setTemperature(BigDecimal temperature)
{
this.temperature = temperature;
}
public BigDecimal getTemperature()
{
return temperature;
}
public void setSoc(BigDecimal soc)
{
this.soc = soc;
}
public BigDecimal getSoc()
{
return soc;
}
public void setSoh(BigDecimal soh)
{
this.soh = soh;
}
public BigDecimal getSoh()
{
return soh;
}
public void setDataTimestamp(Date dataTimestamp)
{
this.dataTimestamp = dataTimestamp;
}
public Date getDataTimestamp()
{
return dataTimestamp;
}
public void setSiteId(String siteId)
{
this.siteId = siteId;
}
public String getSiteId()
{
return siteId;
}
public void setDeviceId(String deviceId)
{
this.deviceId = deviceId;
}
public String getDeviceId()
{
return deviceId;
}
public void setClusterDeviceId(String clusterDeviceId)
{
this.clusterDeviceId = clusterDeviceId;
}
public String getClusterDeviceId()
{
return clusterDeviceId;
}
public void setInterResistance(BigDecimal interResistance)
{
this.interResistance = interResistance;
}
public BigDecimal getInterResistance()
{
return interResistance;
}
public void setHourTime(Date hourTime)
{
this.hourTime = hourTime;
}
public Date getHourTime()
{
return hourTime;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("batteryPack", getBatteryPack())
.append("batteryCluster", getBatteryCluster())
.append("batteryCellId", getBatteryCellId())
.append("voltage", getVoltage())
.append("temperature", getTemperature())
.append("soc", getSoc())
.append("soh", getSoh())
.append("dataTimestamp", getDataTimestamp())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.append("siteId", getSiteId())
.append("deviceId", getDeviceId())
.append("clusterDeviceId", getClusterDeviceId())
.append("interResistance", getInterResistance())
.append("hourTime", getHourTime())
.toString();
}
}

View File

@ -0,0 +1,226 @@
package com.xzzn.ems.domain;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xzzn.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.xzzn.common.annotation.Excel;
/**
* 单体电池分钟级数据对象 ems_battery_data_minutes
*
* @author xzzn
* @date 2025-08-18
*/
public class EmsBatteryDataMinutes extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** $column.columnComment */
private Long id;
/** 电池堆 */
@Excel(name = "电池堆")
private String batteryPack;
/** 电池簇 */
@Excel(name = "电池簇")
private String batteryCluster;
/** 单体编号 */
@Excel(name = "单体编号")
private String batteryCellId;
/** 电压 (V) */
@Excel(name = "电压 (V)")
private BigDecimal voltage;
/** 温度 (℃) */
@Excel(name = "温度 (℃)")
private BigDecimal temperature;
/** SOC (%) */
@Excel(name = "SOC (%)")
private BigDecimal soc;
/** SOH (%) */
@Excel(name = "SOH (%)")
private BigDecimal soh;
/** 数据采集时间 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "数据采集时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date dataTimestamp;
/** 站点id */
@Excel(name = "站点id")
private String siteId;
/** 设备唯一标识符 */
@Excel(name = "设备唯一标识符")
private String deviceId;
/** 簇设备id */
@Excel(name = "簇设备id")
private String clusterDeviceId;
/** 单体电池内阻 */
@Excel(name = "单体电池内阻")
private BigDecimal interResistance;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setBatteryPack(String batteryPack)
{
this.batteryPack = batteryPack;
}
public String getBatteryPack()
{
return batteryPack;
}
public void setBatteryCluster(String batteryCluster)
{
this.batteryCluster = batteryCluster;
}
public String getBatteryCluster()
{
return batteryCluster;
}
public void setBatteryCellId(String batteryCellId)
{
this.batteryCellId = batteryCellId;
}
public String getBatteryCellId()
{
return batteryCellId;
}
public void setVoltage(BigDecimal voltage)
{
this.voltage = voltage;
}
public BigDecimal getVoltage()
{
return voltage;
}
public void setTemperature(BigDecimal temperature)
{
this.temperature = temperature;
}
public BigDecimal getTemperature()
{
return temperature;
}
public void setSoc(BigDecimal soc)
{
this.soc = soc;
}
public BigDecimal getSoc()
{
return soc;
}
public void setSoh(BigDecimal soh)
{
this.soh = soh;
}
public BigDecimal getSoh()
{
return soh;
}
public void setDataTimestamp(Date dataTimestamp)
{
this.dataTimestamp = dataTimestamp;
}
public Date getDataTimestamp()
{
return dataTimestamp;
}
public void setSiteId(String siteId)
{
this.siteId = siteId;
}
public String getSiteId()
{
return siteId;
}
public void setDeviceId(String deviceId)
{
this.deviceId = deviceId;
}
public String getDeviceId()
{
return deviceId;
}
public void setClusterDeviceId(String clusterDeviceId)
{
this.clusterDeviceId = clusterDeviceId;
}
public String getClusterDeviceId()
{
return clusterDeviceId;
}
public void setInterResistance(BigDecimal interResistance)
{
this.interResistance = interResistance;
}
public BigDecimal getInterResistance()
{
return interResistance;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("batteryPack", getBatteryPack())
.append("batteryCluster", getBatteryCluster())
.append("batteryCellId", getBatteryCellId())
.append("voltage", getVoltage())
.append("temperature", getTemperature())
.append("soc", getSoc())
.append("soh", getSoh())
.append("dataTimestamp", getDataTimestamp())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.append("siteId", getSiteId())
.append("deviceId", getDeviceId())
.append("clusterDeviceId", getClusterDeviceId())
.append("interResistance", getInterResistance())
.toString();
}
}

View File

@ -0,0 +1,242 @@
package com.xzzn.ems.domain;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xzzn.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.xzzn.common.annotation.Excel;
/**
* 单体电池月级数据对象 ems_battery_data_month
*
* @author xzzn
* @date 2025-08-22
*/
public class EmsBatteryDataMonth extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** $column.columnComment */
private Long id;
/** 电池堆 */
@Excel(name = "电池堆")
private String batteryPack;
/** 电池簇 */
@Excel(name = "电池簇")
private String batteryCluster;
/** 单体编号 */
@Excel(name = "单体编号")
private String batteryCellId;
/** 电压 (V) */
@Excel(name = "电压 (V)")
private BigDecimal voltage;
/** 温度 (℃) */
@Excel(name = "温度 (℃)")
private BigDecimal temperature;
/** SOC (%) */
@Excel(name = "SOC (%)")
private BigDecimal soc;
/** SOH (%) */
@Excel(name = "SOH (%)")
private BigDecimal soh;
/** 数据采集时间 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "数据采集时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date dataTimestamp;
/** 站点id */
@Excel(name = "站点id")
private String siteId;
/** 设备唯一标识符 */
@Excel(name = "设备唯一标识符")
private String deviceId;
/** 簇设备id */
@Excel(name = "簇设备id")
private String clusterDeviceId;
/** 单体电池内阻 */
@Excel(name = "单体电池内阻")
private BigDecimal interResistance;
/** 月级时间维度 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "月级时间维度", width = 30, dateFormat = "yyyy-MM-dd")
private Date monthTime;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setBatteryPack(String batteryPack)
{
this.batteryPack = batteryPack;
}
public String getBatteryPack()
{
return batteryPack;
}
public void setBatteryCluster(String batteryCluster)
{
this.batteryCluster = batteryCluster;
}
public String getBatteryCluster()
{
return batteryCluster;
}
public void setBatteryCellId(String batteryCellId)
{
this.batteryCellId = batteryCellId;
}
public String getBatteryCellId()
{
return batteryCellId;
}
public void setVoltage(BigDecimal voltage)
{
this.voltage = voltage;
}
public BigDecimal getVoltage()
{
return voltage;
}
public void setTemperature(BigDecimal temperature)
{
this.temperature = temperature;
}
public BigDecimal getTemperature()
{
return temperature;
}
public void setSoc(BigDecimal soc)
{
this.soc = soc;
}
public BigDecimal getSoc()
{
return soc;
}
public void setSoh(BigDecimal soh)
{
this.soh = soh;
}
public BigDecimal getSoh()
{
return soh;
}
public void setDataTimestamp(Date dataTimestamp)
{
this.dataTimestamp = dataTimestamp;
}
public Date getDataTimestamp()
{
return dataTimestamp;
}
public void setSiteId(String siteId)
{
this.siteId = siteId;
}
public String getSiteId()
{
return siteId;
}
public void setDeviceId(String deviceId)
{
this.deviceId = deviceId;
}
public String getDeviceId()
{
return deviceId;
}
public void setClusterDeviceId(String clusterDeviceId)
{
this.clusterDeviceId = clusterDeviceId;
}
public String getClusterDeviceId()
{
return clusterDeviceId;
}
public void setInterResistance(BigDecimal interResistance)
{
this.interResistance = interResistance;
}
public BigDecimal getInterResistance()
{
return interResistance;
}
public void setMonthTime(Date monthTime)
{
this.monthTime = monthTime;
}
public Date getMonthTime()
{
return monthTime;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("batteryPack", getBatteryPack())
.append("batteryCluster", getBatteryCluster())
.append("batteryCellId", getBatteryCellId())
.append("voltage", getVoltage())
.append("temperature", getTemperature())
.append("soc", getSoc())
.append("soh", getSoh())
.append("dataTimestamp", getDataTimestamp())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.append("siteId", getSiteId())
.append("deviceId", getDeviceId())
.append("clusterDeviceId", getClusterDeviceId())
.append("interResistance", getInterResistance())
.append("monthTime", getMonthTime())
.toString();
}
}

View File

@ -0,0 +1,72 @@
package com.xzzn.ems.mapper;
import java.time.LocalDateTime;
import java.util.List;
import com.xzzn.ems.domain.EmsBatteryDataDay;
import com.xzzn.ems.domain.EmsBatteryDataHour;
import org.apache.ibatis.annotations.Param;
/**
* 单体电池天级数据Mapper接口
*
* @author xzzn
* @date 2025-08-20
*/
public interface EmsBatteryDataDayMapper
{
/**
* 查询单体电池天级数据
*
* @param id 单体电池天级数据主键
* @return 单体电池天级数据
*/
public EmsBatteryDataDay selectEmsBatteryDataDayById(Long id);
/**
* 查询单体电池天级数据列表
*
* @param emsBatteryDataDay 单体电池天级数据
* @return 单体电池天级数据集合
*/
public List<EmsBatteryDataDay> selectEmsBatteryDataDayList(EmsBatteryDataDay emsBatteryDataDay);
/**
* 新增单体电池天级数据
*
* @param emsBatteryDataDay 单体电池天级数据
* @return 结果
*/
public int insertEmsBatteryDataDay(EmsBatteryDataDay emsBatteryDataDay);
/**
* 修改单体电池天级数据
*
* @param emsBatteryDataDay 单体电池天级数据
* @return 结果
*/
public int updateEmsBatteryDataDay(EmsBatteryDataDay emsBatteryDataDay);
/**
* 删除单体电池天级数据
*
* @param id 单体电池天级数据主键
* @return 结果
*/
public int deleteEmsBatteryDataDayById(Long id);
/**
* 批量删除单体电池天级数据
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteEmsBatteryDataDayByIds(Long[] ids);
EmsBatteryDataDay findDayMaxTemp(@Param("siteId") String siteId, @Param("stackId") String stackId,
@Param("clusterId") String clusterId, @Param("batteryId") String batteryId, @Param("dayStart") LocalDateTime dayStart);
/**
* 批量插入或更新天级数据(存在则更新,不存在则插入)
*/
void batchInsertOrUpdate(@Param("list") List<EmsBatteryDataDay> dayDataList);
}

View File

@ -0,0 +1,72 @@
package com.xzzn.ems.mapper;
import java.time.LocalDateTime;
import java.util.List;
import com.xzzn.ems.domain.EmsBatteryDataHour;
import org.apache.ibatis.annotations.Param;
/**
* 单体电池小时级数据Mapper接口
*
* @author xzzn
* @date 2025-08-19
*/
public interface EmsBatteryDataHourMapper
{
/**
* 查询单体电池小时级数据
*
* @param id 单体电池小时级数据主键
* @return 单体电池小时级数据
*/
public EmsBatteryDataHour selectEmsBatteryDataHourById(Long id);
/**
* 查询单体电池小时级数据列表
*
* @param emsBatteryDataHour 单体电池小时级数据
* @return 单体电池小时级数据集合
*/
public List<EmsBatteryDataHour> selectEmsBatteryDataHourList(EmsBatteryDataHour emsBatteryDataHour);
/**
* 新增单体电池小时级数据
*
* @param emsBatteryDataHour 单体电池小时级数据
* @return 结果
*/
public int insertEmsBatteryDataHour(EmsBatteryDataHour emsBatteryDataHour);
/**
* 修改单体电池小时级数据
*
* @param emsBatteryDataHour 单体电池小时级数据
* @return 结果
*/
public int updateEmsBatteryDataHour(EmsBatteryDataHour emsBatteryDataHour);
/**
* 删除单体电池小时级数据
*
* @param id 单体电池小时级数据主键
* @return 结果
*/
public int deleteEmsBatteryDataHourById(Long id);
/**
* 批量删除单体电池小时级数据
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteEmsBatteryDataHourByIds(Long[] ids);
EmsBatteryDataHour findHourMaxTemp(@Param("siteId") String siteId, @Param("stackId") String stackId,
@Param("clusterId") String clusterId, @Param("batteryId") String batteryId, @Param("hourStart") LocalDateTime hourStart);
/**
* 批量插入或更新小时级数据(存在则更新,不存在则插入)
*/
void batchInsertOrUpdate(@Param("list") List<EmsBatteryDataHour> hourDataList);
}

View File

@ -0,0 +1,68 @@
package com.xzzn.ems.mapper;
import java.util.Date;
import java.util.List;
import com.xzzn.ems.domain.EmsBatteryData;
import com.xzzn.ems.domain.EmsBatteryDataMinutes;
/**
* 单体电池分钟级数据Mapper接口
*
* @author xzzn
* @date 2025-08-17
*/
public interface EmsBatteryDataMinutesMapper
{
/**
* 查询单体电池分钟级数据
*
* @param dateDay 单体电池分钟级数据主键
* @return 单体电池分钟级数据
*/
public EmsBatteryDataMinutes selectEmsBatteryDataMinutesByDateDay(Date dateDay);
/**
* 查询单体电池分钟级数据列表
*
* @param emsBatteryDataMinutes 单体电池分钟级数据
* @return 单体电池分钟级数据集合
*/
public List<EmsBatteryDataMinutes> selectEmsBatteryDataMinutesList(EmsBatteryDataMinutes emsBatteryDataMinutes);
/**
* 新增单体电池分钟级数据
*
* @param emsBatteryDataMinutes 单体电池分钟级数据
* @return 结果
*/
public int insertEmsBatteryDataMinutes(EmsBatteryDataMinutes emsBatteryDataMinutes);
/**
* 修改单体电池分钟级数据
*
* @param emsBatteryDataMinutes 单体电池分钟级数据
* @return 结果
*/
public int updateEmsBatteryDataMinutes(EmsBatteryDataMinutes emsBatteryDataMinutes);
/**
* 删除单体电池分钟级数据
*
* @param dateDay 单体电池分钟级数据主键
* @return 结果
*/
public int deleteEmsBatteryDataMinutesByDateDay(Date dateDay);
/**
* 批量删除单体电池分钟级数据
*
* @param dateDays 需要删除的数据主键集合
* @return 结果
*/
public int deleteEmsBatteryDataMinutesByDateDays(Date[] dateDays);
int insertMinutesBatteryDataList(List<EmsBatteryDataMinutes> emsBatteryDataList);
public void deleteByTimeBeforeOneHour(String oneHourAgoStr);
}

View File

@ -0,0 +1,72 @@
package com.xzzn.ems.mapper;
import java.time.LocalDateTime;
import java.util.List;
import com.xzzn.ems.domain.EmsBatteryDataMonth;
import org.apache.ibatis.annotations.Param;
/**
* 单体电池月级数据Mapper接口
*
* @author xzzn
* @date 2025-08-22
*/
public interface EmsBatteryDataMonthMapper
{
/**
* 查询单体电池月级数据
*
* @param id 单体电池月级数据主键
* @return 单体电池月级数据
*/
public EmsBatteryDataMonth selectEmsBatteryDataMonthById(Long id);
/**
* 查询单体电池月级数据列表
*
* @param emsBatteryDataMonth 单体电池月级数据
* @return 单体电池月级数据集合
*/
public List<EmsBatteryDataMonth> selectEmsBatteryDataMonthList(EmsBatteryDataMonth emsBatteryDataMonth);
/**
* 新增单体电池月级数据
*
* @param emsBatteryDataMonth 单体电池月级数据
* @return 结果
*/
public int insertEmsBatteryDataMonth(EmsBatteryDataMonth emsBatteryDataMonth);
/**
* 修改单体电池月级数据
*
* @param emsBatteryDataMonth 单体电池月级数据
* @return 结果
*/
public int updateEmsBatteryDataMonth(EmsBatteryDataMonth emsBatteryDataMonth);
/**
* 删除单体电池月级数据
*
* @param id 单体电池月级数据主键
* @return 结果
*/
public int deleteEmsBatteryDataMonthById(Long id);
/**
* 批量删除单体电池月级数据
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteEmsBatteryDataMonthByIds(Long[] ids);
EmsBatteryDataMonth findMonthMaxTemp(@Param("siteId") String siteId, @Param("stackId") String stackId,
@Param("clusterId") String clusterId, @Param("batteryId") String batteryId, @Param("monthStart") LocalDateTime monthStart);
/**
* 批量插入或更新月级数据(存在则更新,不存在则插入)
*/
void batchInsertOrUpdate(@Param("list") List<EmsBatteryDataMonth> monthDataList);
}

View File

@ -14,6 +14,7 @@ import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.*;
import com.xzzn.ems.mapper.*;
import com.xzzn.ems.service.IDDSDataProcessService;
import com.xzzn.ems.utils.AbstractBatteryDataProcessor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
@ -22,20 +23,22 @@ import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
public class DDSDataProcessServiceImpl implements IDDSDataProcessService {
public class DDSDataProcessServiceImpl extends AbstractBatteryDataProcessor implements IDDSDataProcessService {
private static final Log log = LogFactory.getLog(DDSDataProcessServiceImpl.class);
private static final String SITE_ID = "021_DDS_01";
// 正则表达式匹配BMS设备编号和属性
private static final Pattern PATTERN = Pattern.compile("(BMSD\\d{2})(ZT|SOC|SOH|DL|DY|BDSC)");
// 匹配DTDC+数字格式的正则(提取序号)
private static final Pattern DTDC_PATTERN = Pattern.compile("DTDC(\\d+)([A-Za-z]*)");
// 初始化ObjectMapper可以作为全局变量
private static final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private EmsBatteryClusterMapper emsBatteryClusterMapper;
@ -65,6 +68,12 @@ public class DDSDataProcessServiceImpl implements IDDSDataProcessService {
private EmsDhDataMapper emsDhDataMapper;
@Autowired
private EmsBatteryGroupMapper emsBatteryGroupMapper;
@Autowired
private EmsBatteryDataMinutesMapper emsBatteryDataMinutesMapper;
public DDSDataProcessServiceImpl(ObjectMapper objectMapper) {
super(objectMapper);
}
@Override
public void handleDdsData(String message) {
@ -231,7 +240,6 @@ public class DDSDataProcessServiceImpl implements IDDSDataProcessService {
}
// 批量插入数据库
log.info("批量插入");
if (!CollectionUtils.isEmpty(groupMap)) {
List<EmsBatteryGroup> batteryGroupList = new ArrayList<>(groupMap.values());
emsBatteryGroupMapper.batchInsertGroupData(batteryGroupList);
@ -273,8 +281,17 @@ public class DDSDataProcessServiceImpl implements IDDSDataProcessService {
//电池组
Map<String, Object> obj = JSON.parseObject(dataJson, new TypeReference<Map<String, Object>>() {
});
List<EmsBatteryDataDailyLatest> dailyList = new ArrayList<>();
List<EmsBatteryData> dataList = new ArrayList<>();
// 前一个小时
LocalDateTime oneHourAgo = LocalDateTime.now().minus(1, ChronoUnit.HOURS);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String oneHourAgoStr = oneHourAgo.format(formatter);
Map<String, EmsBatteryData> dataMap = new HashMap<>();
Map<String, EmsBatteryDataDailyLatest> dailyMap = new HashMap<>();
Map<String, EmsBatteryDataMinutes> minutesMap = new HashMap<>();
for (Map.Entry<String, Object> entry : obj.entrySet()) {
String key = entry.getKey();
@ -302,26 +319,45 @@ public class DDSDataProcessServiceImpl implements IDDSDataProcessService {
setDTDCPropertyValue(data, property, entry.getValue());
dataMap.put(batteryCellId, data);
// 每日最新数据
EmsBatteryDataDailyLatest daily = new EmsBatteryDataDailyLatest();
// 每日最新数据按batteryCellId去重
EmsBatteryDataDailyLatest daily = dailyMap.getOrDefault(batteryCellId, new EmsBatteryDataDailyLatest());
BeanUtils.copyProperties(data, daily);
daily.setDateDay(DateUtils.getNowDate());
dailyList.add(daily);
dailyMap.put(batteryCellId, daily);
// 分钟级的表,上报的数据直接存入,数据上限是 1 个小时
EmsBatteryDataMinutes minutes = minutesMap.getOrDefault(batteryCellId, new EmsBatteryDataMinutes());
BeanUtils.copyProperties(data, minutes);
minutesMap.put(batteryCellId, minutes);
}
}
}
if (!CollectionUtils.isEmpty(dataMap)) {
List<EmsBatteryData> dataList = new ArrayList<>(dataMap.values());
dataList = new ArrayList<>(dataMap.values());
emsBatteryDataMapper.insertEmsBatteryDataList(new ArrayList<>(dataList));
redisCache.deleteList(RedisKeyConstants.BATTERY + SITE_ID + "_" + "BMSC01");
redisCache.setCacheList(RedisKeyConstants.BATTERY + SITE_ID + "_" + "BMSC01" , dataList);
}
// 批量处理每日最新数据
if (dailyList != null && dailyList.size() > 0) {
List<EmsBatteryDataDailyLatest> dailyList = new ArrayList<>(dailyMap.values());
if (!dailyList.isEmpty()) {
dailyList = new ArrayList<>(dailyMap.values());
emsBatteryDailyLatestServiceImpl.batchProcessBatteryData(dailyList);
}
// 实时插入每分钟数据
List<EmsBatteryDataMinutes> minutesList = new ArrayList<>(minutesMap.values());
if (!minutesList.isEmpty()) {
emsBatteryDataMinutesMapper.insertMinutesBatteryDataList(minutesList);
}
// 清理分钟级表里一小时前数据
emsBatteryDataMinutesMapper.deleteByTimeBeforeOneHour(oneHourAgoStr);
// 分片处理时级,天级,月级数据
if (dataList.size() > 0) {
super.processBatch(dataList);
}
}
private void setDTDCPropertyValue(EmsBatteryData data, String property, Object value) {
@ -884,33 +920,6 @@ public class DDSDataProcessServiceImpl implements IDDSDataProcessService {
return records;
}
private static Map<String, Map<String, Object>> processDataPrefix(Map<String, Object> rawData) {
Map<String, Map<String, Object>> records = new HashMap<>();
for (Map.Entry<String, Object> entry : rawData.entrySet()) {
String key = entry.getKey();
// 确保键长度足够
if (key.length() < 3) {
continue;
}
// 提取记录ID前3位
String recordId = key.substring(0, 3);
if (!recordId.startsWith("DY")) {
continue;
}
// 提取字段类型(剩余部分)
String fieldType = key.substring(3);
// 初始化记录
records.putIfAbsent(recordId, new HashMap<>());
// 存入字段值
records.get(recordId).put(fieldType, entry.getValue());
}
return records;
}
// 空数据不处理
private boolean checkJsonDataEmpty(String jsonData) {
boolean flag = false;

View File

@ -15,20 +15,22 @@ import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.*;
import com.xzzn.ems.mapper.*;
import com.xzzn.ems.service.IFXXDataProcessService;
import com.xzzn.ems.utils.AbstractBatteryDataProcessor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
@Service
public class FXXDataProcessServiceImpl implements IFXXDataProcessService {
public class FXXDataProcessServiceImpl extends AbstractBatteryDataProcessor implements IFXXDataProcessService {
private static final Log log = LogFactory.getLog(FXXDataProcessServiceImpl.class);
private static final String SITE_ID = "021_FXX_01";
// 初始化ObjectMapper可以作为全局变量
private static final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private EmsBatteryClusterMapper emsBatteryClusterMapper;
@ -55,6 +57,13 @@ public class FXXDataProcessServiceImpl implements IFXXDataProcessService {
private EmsAmmeterDataMapper emsAmmeterDataMapper;
@Autowired
private EmsBatteryDailyLatestServiceImpl emsBatteryDailyLatestServiceImpl;
@Autowired
private EmsBatteryDataMinutesMapper emsBatteryDataMinutesMapper;
// 构造方法(调用父类构造)
public FXXDataProcessServiceImpl(ObjectMapper objectMapper) {
super(objectMapper);
}
@Override
public void handleFxData(String message) {
@ -268,6 +277,11 @@ public class FXXDataProcessServiceImpl implements IFXXDataProcessService {
Map<String, Map<String, Object>> records = processData(JSON.parseObject(dataJson, new TypeReference<Map<String, Object>>() {}));
List<EmsBatteryData> list = new ArrayList<>();
List<EmsBatteryDataDailyLatest> dailyList = new ArrayList<>();
List<EmsBatteryDataMinutes> minutesList = new ArrayList<>();
// 前一个小时
LocalDateTime oneHourAgo = LocalDateTime.now().minus(1, ChronoUnit.HOURS);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String oneHourAgoStr = oneHourAgo.format(formatter);
//单体电池
for (Map.Entry<String, Map<String, Object>> record : records.entrySet()) {
String recordId = record.getKey();
@ -286,11 +300,14 @@ public class FXXDataProcessServiceImpl implements IFXXDataProcessService {
// 时间戳
batteryData.setDataTimestamp(new Date());
// 系统管理字段
batteryData.setCreateBy("system");
batteryData.setCreateTime(DateUtils.getNowDate());
batteryData.setUpdateBy("system");
batteryData.setUpdateTime(DateUtils.getNowDate());
// ID字段
batteryData.setSiteId(SITE_ID);
batteryData.setClusterDeviceId(deviceId);
list.add(batteryData);
// 每日最新数据
@ -298,18 +315,34 @@ public class FXXDataProcessServiceImpl implements IFXXDataProcessService {
BeanUtils.copyProperties(batteryData, daily);
daily.setDateDay(DateUtils.getNowDate());
dailyList.add(daily);
// 分钟级的表,上报的数据直接存入,数据上限是 1 个小时
EmsBatteryDataMinutes minutes = new EmsBatteryDataMinutes();
BeanUtils.copyProperties(batteryData, minutes);
minutesList.add(minutes);
}
if (list.size() > 0 ) {
if (list.size() > 0) {
emsBatteryDataMapper.insertEmsBatteryDataList(list);
redisCache.deleteList(RedisKeyConstants.BATTERY + SITE_ID + "_" +deviceId);
redisCache.setCacheList(RedisKeyConstants.BATTERY + SITE_ID + "_" +deviceId, list);
redisCache.deleteList(RedisKeyConstants.BATTERY + SITE_ID + "_" + deviceId);
redisCache.setCacheList(RedisKeyConstants.BATTERY + SITE_ID + "_" + deviceId, list);
}
// 批量处理每日最新数据
if (dailyList != null && dailyList.size() > 0) {
emsBatteryDailyLatestServiceImpl.batchProcessBatteryData(dailyList);
}
// 实时插入每分钟数据
if (minutesList != null && minutesList.size() > 0) {
emsBatteryDataMinutesMapper.insertMinutesBatteryDataList(minutesList);
}
// 清理分钟级表里一小时前数据
emsBatteryDataMinutesMapper.deleteByTimeBeforeOneHour(oneHourAgoStr);
// 分片处理时级,天级,月级数据
if (list.size() > 0) {
super.processBatch(list);
}
}
private void pcsDataProcess(String deviceId, String dataJson) {

View File

@ -0,0 +1,681 @@
package com.xzzn.ems.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.utils.DateUtils;
import com.xzzn.ems.domain.EmsBatteryData;
import com.xzzn.ems.domain.EmsBatteryDataDay;
import com.xzzn.ems.domain.EmsBatteryDataHour;
import com.xzzn.ems.domain.EmsBatteryDataMonth;
import com.xzzn.ems.mapper.EmsBatteryDataDayMapper;
import com.xzzn.ems.mapper.EmsBatteryDataHourMapper;
import com.xzzn.ems.mapper.EmsBatteryDataMonthMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public abstract class AbstractBatteryDataProcessor {
private static final Log log = LogFactory.getLog(AbstractBatteryDataProcessor.class);
protected static final DateTimeFormatter HOUR_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH");
protected static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
protected static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
// 公共缓存:<batteryId_granularity, 缓存值>
protected final Map<String, CacheValue> tempCache = new ConcurrentHashMap<>();
// 公共锁缓存:<batteryId_granularity, 锁>
protected final Map<String, ReentrantLock> lockCache = new ConcurrentHashMap<>();
protected final ObjectMapper objectMapper;
@Autowired
private EmsBatteryDataHourMapper emsBatteryDataHourMapper;
@Autowired
private EmsBatteryDataDayMapper emsBatteryDataDayMapper;
@Autowired
private EmsBatteryDataMonthMapper emsBatteryDataMonthMapper;
@Autowired
private RedisCache redisCache;
// 公共线程池IO密集型子类共享
protected ThreadPoolTaskExecutor ioExecutor;
// 批量缓冲区(线程安全队列,按级别区分)
protected ConcurrentLinkedQueue<EmsBatteryData> hourBatchQueue = new ConcurrentLinkedQueue<>();
protected ConcurrentLinkedQueue<EmsBatteryData> dayBatchQueue = new ConcurrentLinkedQueue<>();
protected ConcurrentLinkedQueue<EmsBatteryData> monthBatchQueue = new ConcurrentLinkedQueue<>();
// 批量提交阈值满100条触发数据库操作
protected static final int BATCH_THRESHOLD = 50;
// 死锁重试等待时间基数(毫秒)
private static final int RETRY_BASE_DELAY = 100;
// 随机数生成器,用于重试退避
private static final Random random = new Random();
// 初始化方法(构造时初始化线程池)
public AbstractBatteryDataProcessor(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
ioExecutor = new ThreadPoolTaskExecutor();
int coreThreads = Runtime.getRuntime().availableProcessors() * 2;
ioExecutor.setCorePoolSize(coreThreads);
ioExecutor.setMaxPoolSize(coreThreads * 2);
ioExecutor.setQueueCapacity(10000);
ioExecutor.setThreadNamePrefix("battery-io-");
// 线程池饱和策略:使用调用者线程执行,避免任务丢失
ioExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
ioExecutor.initialize();
}
// 模板方法:定义数据处理主流程(子类无需重写)
public void processBatch(List<EmsBatteryData> batchData) {
// 1. 数据分片(实现通用分片逻辑)
List<List<EmsBatteryData>> shards = splitIntoShards(batchData, 50);
int totalShards = shards.size();
// 2. 并行处理每个分片(使用父类线程池)
for (int i = 0; i < totalShards; i++) {
List<EmsBatteryData> shard = shards.get(i);
boolean isLastShard = (i == totalShards - 1); // 判断是否为最后一个分片
// 传递分片索引和是否最后一个分片的标记
ioExecutor.execute(() -> processShard(shard, isLastShard));
}
//shards.forEach(shard -> ioExecutor.execute(() -> processShard(shard)));
}
/**
* 将大批量数据拆分为多个小分片
* @param dataList 原始数据列表
* @param shardSize 每个分片的最大大小如100条/片)
* @return 分片后的列表(每个元素是一个子列表)
*/
protected List<List<EmsBatteryData>> splitIntoShards(List<EmsBatteryData> dataList, int shardSize) {
// 1. 校验参数:避免空列表或无效分片大小
if (dataList == null || dataList.isEmpty()) {
return new ArrayList<>();
}
if (shardSize <= 0) {
throw new IllegalArgumentException("分片大小必须大于0");
}
// 2. 计算总分片数向上取整如150条数据分片大小100 → 2片
int totalSize = dataList.size();
int shardCount = (totalSize + shardSize - 1) / shardSize;
// 3. 拆分数据为分片使用IntStream生成分片索引
return IntStream.range(0, shardCount)
.mapToObj(shardIndex -> {
// 计算当前分片的起始索引和结束索引
int startIndex = shardIndex * shardSize;
int endIndex = Math.min(startIndex + shardSize, totalSize);
// 截取子列表作为一个分片
return dataList.subList(startIndex, endIndex);
})
.collect(Collectors.toList());
}
// 处理单个分片(父类定义流程,调用子类实现的具体判断和更新逻辑)
private void processShard(List<EmsBatteryData> shard, boolean isLastShard) {
int shardSize = shard.size();
log.info("分片校验:" + shardSize + "<UNK>");
for (int i = 0; i < shardSize; i++) {
EmsBatteryData data = shard.get(i);
if (!isValidData(data)) continue;
// 判断当前数据是否为最后一个分片的最后一条数据
boolean isLastData = isLastShard && (i == shardSize - 1);
// shouldUpdate判断是否需要更新
if (shouldUpdate(data, "hour")) {
addToHourBatch(data, isLastData); // 加入小时级批量队列
}
if (shouldUpdate(data, "day")) {
addToDayBatch(data, isLastData); // 加入天级批量队列
}
if (shouldUpdate(data, "month")) {
addToMonthBatch(data, isLastData);
}
}
}
// 加入小时级批量队列,达到阈值时触发批量更新
protected void addToHourBatch(EmsBatteryData data, boolean isLastData) {
hourBatchQueue.add(data);
if (hourBatchQueue.size() >= BATCH_THRESHOLD || isLastData) {
List<EmsBatteryData> batch = drainQueue(hourBatchQueue);
ioExecutor.execute(() -> batchUpdateHour(batch)); // 异步批量更新
}
}
// 加入天级批量队列,达到阈值时触发批量更新
protected void addToDayBatch(EmsBatteryData data, boolean isLastData) {
dayBatchQueue.add(data);
if (dayBatchQueue.size() >= BATCH_THRESHOLD || isLastData) {
List<EmsBatteryData> batch = drainQueue(dayBatchQueue);
ioExecutor.execute(() -> batchUpdateDay(batch)); // 异步批量更新
}
}
// 加入月级批量队列,达到阈值时触发批量更新
protected void addToMonthBatch(EmsBatteryData data, boolean isLastData) {
monthBatchQueue.add(data);
if (monthBatchQueue.size() >= BATCH_THRESHOLD || isLastData) {
List<EmsBatteryData> batch = drainQueue(monthBatchQueue);
ioExecutor.execute(() -> batchUpdateMonth(batch)); // 异步批量更新
}
}
// 实现父类的抽象方法:批量更新小时级数据
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void batchUpdateHour(List<EmsBatteryData> batch) {
if (batch.isEmpty()) return;
int maxRetry = 3; // 最多重试3次
int retryCount = 0;
while (retryCount < maxRetry) {
try {
// 关键优化:按唯一键排序,保证所有线程处理相同记录时顺序一致
List<EmsBatteryData> sortedBatch = batch.stream()
.sorted(Comparator.comparing(data -> generateCacheKey(data, "hour"))) // 用唯一键排序
.collect(Collectors.toList());
// 1. 转换原始数据为小时级统计对象(分组取最高温)
List<EmsBatteryDataHour> hourDataList = sortedBatch.stream()
.collect(Collectors.groupingBy(
data -> generateCacheKey(data, "hour"), // 分组键
Collectors.collectingAndThen(
Collectors.maxBy((d1, d2) -> d1.getTemperature().compareTo(d2.getTemperature())),
maxData -> convertToHourData(maxData.get()) // 取每组最高温转换为HourData
)
))
.values()
.stream()
.sorted(Comparator.comparing(this::getHourDataUniqueKey))
.collect(Collectors.toList());
// 2. 批量插入或更新数据库(依赖数据库唯一索引)
emsBatteryDataHourMapper.batchInsertOrUpdate(hourDataList);
// 3. 同步更新Redis缓存批量操作
batchUpdateHourCache(hourDataList);
log.info("小时级批量更新成功,批次大小:" + batch.size() + ",重试次数:" + retryCount);
return;
} catch (Exception e) {
log.error("小时级批量更新失败", e);
// 使用改进的死锁判断方法,直接传入异常对象
if (isDeadlockException(e) && retryCount < maxRetry - 1) {
retryCount++;
long sleepTime = calculateBackoffTime(retryCount);
log.info("检测到死锁,第" + retryCount + "次重试(当前批次大小:" + batch.size() + "", e);
try {
// 指数退避策略100ms, 200ms, 300ms...
Thread.sleep(sleepTime);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // 保留中断状态
break;
}
} else {
log.error("小时级批量更新失败(已达最大重试次数)", e);
//retryQueue.addAll(batch); // 加入重试队列
return;
}
}
}
}
// 辅助方法:获取小时级数据的唯一键(用于排序)
private String getHourDataUniqueKey(EmsBatteryDataHour data) {
return data.getSiteId() + "_" + data.getBatteryPack() + "_" + data.getClusterDeviceId()
+ "_" + data.getDeviceId() + "_" + data.getHourTime();
}
// 批量更新小时级数据缓存-小时级统计数据列表(已按唯一键分组并取最高温)
public void batchUpdateHourCache(List<EmsBatteryDataHour> hourDataList) {
if (hourDataList.isEmpty()) return;
// 1. 构建缓存键值对(批量操作提升效率)
Map<String, String> tempCacheMap = new HashMap<>(hourDataList.size());
for (EmsBatteryDataHour data : hourDataList) {
EmsBatteryData cacheData = new EmsBatteryData();
BeanUtils.copyProperties(data, cacheData);
// 2. 生成唯一缓存键(与数据库唯一键一致)
String cacheKey = generateCacheKey(cacheData, "hour");
// 3. 存储最高温度和最后更新时间(两个独立缓存项)
tempCacheMap.put(cacheKey, data.getTemperature().toString());
}
try {
// 4. 批量设置缓存Redis批量操作减少网络交互
redisCache.multiSet(tempCacheMap);
} catch (Exception e) {
log.error("批量更新小时级缓存失败", e);
// 缓存更新失败不影响主流程,但需记录日志便于排查
}
}
// 转换为小时级数据对象
private EmsBatteryDataHour convertToHourData(EmsBatteryData data) {
LocalDateTime localDateTime = data.getDataTimestamp().toInstant().atZone(ZoneId.of("Asia/Shanghai"))
.toLocalDateTime();
LocalDateTime hourStart = localDateTime.truncatedTo(ChronoUnit.HOURS);
EmsBatteryDataHour hourData = new EmsBatteryDataHour();
BeanUtils.copyProperties(data, hourData);
hourData.setUpdateTime(new Date());
hourData.setHourTime(DateUtils.convertToDate(hourStart));
return hourData;
}
// 实现父类的抽象方法:批量更新天级数据
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void batchUpdateDay(List<EmsBatteryData> batch) {
if (batch.isEmpty()) return;
int maxRetry = 3; // 最多重试3次
int retryCount = 0;
while (retryCount < maxRetry) {
try {
// 关键优化:按唯一键排序,保证所有线程处理相同记录时顺序一致
List<EmsBatteryData> sortedBatch = batch.stream()
.sorted(Comparator.comparing(data -> generateCacheKey(data, "day"))) // 用唯一键排序
.collect(Collectors.toList());
// 1. 转换原始数据为天级统计对象(分组取最高温)
List<EmsBatteryDataDay> dayDataList = sortedBatch.stream()
.collect(Collectors.groupingBy(
data -> generateCacheKey(data, "day"), // 分组键
Collectors.collectingAndThen(
Collectors.maxBy((d1, d2) -> d1.getTemperature().compareTo(d2.getTemperature())),
maxData -> convertToDayData(maxData.get()) // 取每组最高温转换为HourData
)
))
.values()
.stream()
.sorted(Comparator.comparing(this::getDayDataUniqueKey))
.collect(Collectors.toList());
// 2. 批量插入或更新数据库(依赖数据库唯一索引)
emsBatteryDataDayMapper.batchInsertOrUpdate(dayDataList);
// 3. 同步更新Redis缓存批量操作
batchUpdateDayCache(dayDataList);
log.info("天级批量更新成功,批次大小:" + batch.size() + ",重试次数:" + retryCount);
return;
} catch (Exception e) {
log.error("天级批量更新失败", e);
// 使用改进的死锁判断方法,直接传入异常对象
if (isDeadlockException(e) && retryCount < maxRetry - 1) {
retryCount++;
long sleepTime = calculateBackoffTime(retryCount);
log.info("检测到死锁,第" + retryCount + "次重试(当前批次大小:" + batch.size() + "", e);
try {
// 指数退避策略100ms, 200ms, 300ms...
Thread.sleep(sleepTime);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // 保留中断状态
break;
}
} else {
log.error("天级批量更新失败(已达最大重试次数)", e);
//retryQueue.addAll(batch); // 加入重试队列
return;
}
}
}
}
// 辅助方法:获取天级数据的唯一键(用于排序)
private String getDayDataUniqueKey(EmsBatteryDataDay data) {
return data.getSiteId() + "_" + data.getBatteryPack() + "_" + data.getClusterDeviceId()
+ "_" + data.getDeviceId() + "_" + data.getDayTime();
}
// 批量更新天级数据缓存-天级统计数据列表(已按唯一键分组并取最高温)
public void batchUpdateDayCache(List<EmsBatteryDataDay> dayDataList) {
if (dayDataList.isEmpty()) return;
// 1. 构建缓存键值对(批量操作提升效率)
Map<String, String> tempCacheMap = new HashMap<>(dayDataList.size());
for (EmsBatteryDataDay data : dayDataList) {
EmsBatteryData cacheData = new EmsBatteryData();
BeanUtils.copyProperties(data, cacheData);
// 2. 生成唯一缓存键(与数据库唯一键一致)
String cacheKey = generateCacheKey(cacheData, "day");
// 3. 存储最高温度和最后更新时间(两个独立缓存项)
tempCacheMap.put(cacheKey, data.getTemperature().toString());
}
try {
// 4. 批量设置缓存Redis批量操作减少网络交互
redisCache.multiSet(tempCacheMap);
} catch (Exception e) {
log.error("批量更新小时级缓存失败", e);
// 缓存更新失败不影响主流程,但需记录日志便于排查
}
}
// 转换为天级数据对象
private EmsBatteryDataDay convertToDayData(EmsBatteryData data) {
LocalDateTime localDateTime = data.getDataTimestamp().toInstant().atZone(ZoneId.of("Asia/Shanghai"))
.toLocalDateTime();
LocalDateTime dayStart = localDateTime.truncatedTo(ChronoUnit.DAYS);
EmsBatteryDataDay dayData = new EmsBatteryDataDay();
BeanUtils.copyProperties(data, dayData);
dayData.setUpdateTime(new Date());
dayData.setDayTime(DateUtils.convertToDate(dayStart));
return dayData;
}
// 实现父类的抽象方法:批量更新月级数据
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void batchUpdateMonth(List<EmsBatteryData> batch) {
if (batch.isEmpty()) return;
int maxRetry = 3; // 最多重试3次
int retryCount = 0;
while (retryCount < maxRetry) {
try {
// 关键优化:按唯一键排序,保证所有线程处理相同记录时顺序一致
List<EmsBatteryData> sortedBatch = batch.stream()
.sorted(Comparator.comparing(data -> generateCacheKey(data, "month"))) // 用唯一键排序
.collect(Collectors.toList());
// 1. 转换原始数据为小时级统计对象(分组取最高温)
List<EmsBatteryDataMonth> monthDataList = sortedBatch.stream()
.collect(Collectors.groupingBy(
data -> generateCacheKey(data, "month"), // 分组键
Collectors.collectingAndThen(
Collectors.maxBy((d1, d2) -> d1.getTemperature().compareTo(d2.getTemperature())),
maxData -> convertToMonthData(maxData.get()) // 取每组最高温转换为HourData
)
))
.values()
.stream()
.sorted(Comparator.comparing(this::getMonthDataUniqueKey))
.collect(Collectors.toList());
// 2. 批量插入或更新数据库(依赖数据库唯一索引)
emsBatteryDataMonthMapper.batchInsertOrUpdate(monthDataList);
// 3. 同步更新Redis缓存批量操作
batchUpdateMonthCache(monthDataList);
log.info("月级批量更新成功,批次大小:" + batch.size() + ",重试次数:" + retryCount);
return;
} catch (Exception e) {
log.error("月级批量更新失败", e);
// 使用改进的死锁判断方法,直接传入异常对象
if (isDeadlockException(e) && retryCount < maxRetry - 1) {
retryCount++;
long sleepTime = calculateBackoffTime(retryCount);
log.info("检测到死锁,第" + retryCount + "次重试(当前批次大小:" + batch.size() + "", e);
try {
// 指数退避策略100ms, 200ms, 300ms...
Thread.sleep(sleepTime);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // 保留中断状态
break;
}
} else {
log.error("月级批量更新失败(已达最大重试次数)", e);
//retryQueue.addAll(batch); // 加入重试队列
return;
}
}
}
}
// 辅助方法:获取月级数据的唯一键(用于排序)
private String getMonthDataUniqueKey(EmsBatteryDataMonth data) {
return data.getSiteId() + "_" + data.getBatteryPack() + "_" + data.getClusterDeviceId()
+ "_" + data.getDeviceId() + "_" + data.getMonthTime();
}
// 转换为月级数据对象
private EmsBatteryDataMonth convertToMonthData(EmsBatteryData data) {
LocalDateTime localDateTime = data.getDataTimestamp().toInstant().atZone(ZoneId.of("Asia/Shanghai"))
.toLocalDateTime();
LocalDateTime monthStart = localDateTime.withDayOfMonth(1).withHour(0).withMinute(0)
.withSecond(0).withNano(0);
EmsBatteryDataMonth monthData = new EmsBatteryDataMonth();
BeanUtils.copyProperties(data, monthData);
monthData.setUpdateTime(new Date());
monthData.setMonthTime(DateUtils.convertToDate(monthStart));
return monthData;
}
// 批量更新月级数据缓存-月级统计数据列表(已按唯一键分组并取最高温)
public void batchUpdateMonthCache(List<EmsBatteryDataMonth> monthDataList) {
if (monthDataList.isEmpty()) return;
// 1. 构建缓存键值对(批量操作提升效率)
Map<String, String> tempCacheMap = new HashMap<>(monthDataList.size());
for (EmsBatteryDataMonth data : monthDataList) {
EmsBatteryData cacheData = new EmsBatteryData();
BeanUtils.copyProperties(data, cacheData);
// 2. 生成唯一缓存键(与数据库唯一键一致)
String cacheKey = generateCacheKey(cacheData, "month");
// 3. 存储最高温度和最后更新时间(两个独立缓存项)
tempCacheMap.put(cacheKey, data.getTemperature().toString());
}
try {
// 4. 批量设置缓存Redis批量操作减少网络交互
redisCache.multiSet(tempCacheMap);
} catch (Exception e) {
log.error("批量更新月级缓存失败", e);
// 缓存更新失败不影响主流程,但需记录日志便于排查
}
}
/**
* 校验数据有效性(公共逻辑:检查核心字段)
*/
protected boolean isValidData(EmsBatteryData data) {
return data.getDeviceId() != null
&& data.getTemperature() != null
&& data.getDataTimestamp() != null;
}
/**
* 判断是否需要更新(公共缓存逻辑)
*/
protected boolean shouldUpdate(EmsBatteryData data, String granularity) {
String cacheKey = generateCacheKey(data, granularity);
if (cacheKey == null) return true;
BigDecimal newTemp = data.getTemperature();
CacheValue localCache = tempCache.get(cacheKey);
if (localCache != null) {
return newTemp.compareTo(localCache.maxTemp) > 0;
}
// 本地缓存未命中查Redis
try {
String redisValue = redisCache.getCacheObject(cacheKey);
if (redisValue != null) {
// Redis有值转换为BigDecimal比较
BigDecimal redisMaxTemp = new BigDecimal(redisValue);
// 同时更新本地缓存减少下次查询Redis的开销
tempCache.put(cacheKey, new CacheValue(redisMaxTemp, LocalDateTime.now()));
return newTemp.compareTo(redisMaxTemp) > 0;
}
} catch (Exception e) {
log.error("查询Redis缓存失败key: " + cacheKey + "", e);
}
// 数据库校验(缓存都未命中或新温度更高时)
BigDecimal dbMaxTemp = queryDbMaxTemp(data, granularity);
if (dbMaxTemp != null && newTemp.compareTo(dbMaxTemp) <= 0) {
// 数据库已有更高温度,更新缓存后返回
CacheValue dbCache = new CacheValue(dbMaxTemp, LocalDateTime.now());
tempCache.put(cacheKey, dbCache);
redisCache.setCacheObject(cacheKey, dbMaxTemp.toString()); // 同步到Redis
return false;
}
return true;
}
/**
* 查询数据库中该设备在指定时间粒度下的最大温度
*/
private BigDecimal queryDbMaxTemp(EmsBatteryData data, String granularity) {
try {
LocalDateTime dataTime = data.getDataTimestamp().toInstant()
.atZone(ZoneId.of("Asia/Shanghai")).toLocalDateTime();
String siteId = data.getSiteId();
String batteryPack = data.getBatteryPack();
String clusterId = data.getClusterDeviceId();
String deviceId = data.getDeviceId();
// 根据粒度查询对应的数据表
switch (granularity) {
case "hour":
LocalDateTime hourStart = dataTime.truncatedTo(ChronoUnit.HOURS);
// 调用Mapper查询该小时的最大温度需确保Mapper方法存在
EmsBatteryDataHour hourMax = emsBatteryDataHourMapper.findHourMaxTemp(
siteId, batteryPack, clusterId, deviceId, hourStart
);
return hourMax != null ? hourMax.getTemperature() : null;
case "day":
LocalDateTime dayStart = dataTime.truncatedTo(ChronoUnit.DAYS);
EmsBatteryDataDay dayMax = emsBatteryDataDayMapper.findDayMaxTemp(
siteId, batteryPack, clusterId, deviceId, dayStart
);
return dayMax != null ? dayMax.getTemperature() : null;
case "month":
// 设为当月1号 与其他表统一格式
LocalDateTime monthStart = dataTime.withDayOfMonth(1).withHour(0).withMinute(0)
.withSecond(0).withNano(0);
EmsBatteryDataMonth monthMax = emsBatteryDataMonthMapper.findMonthMaxTemp(
siteId, batteryPack, clusterId, deviceId, monthStart
);
return monthMax != null ? monthMax.getTemperature() : null;
default:
log.error("不支持的查询粒度:" + granularity);
return null;
}
} catch (Exception e) {
log.error("查询数据库最大温度失败", e);
return null; // 数据库查询失败时,暂不阻止更新(避免业务中断)
}
}
// 生成缓存键(不同级别格式不同)
private String generateCacheKey(EmsBatteryData data, String granularity) {
String siteId = data.getSiteId();
String stackId = data.getBatteryPack();
String clusterId = data.getClusterDeviceId();
String batteryId = data.getDeviceId();
try {
LocalDateTime localDateTime = data.getDataTimestamp().toInstant().atZone(ZoneId.of("Asia/Shanghai"))
.toLocalDateTime();
String timeStr;
// 根据级别格式化时间(小时/天/月)
switch (granularity) {
case "hour":
timeStr = localDateTime.format(HOUR_FORMATTER);
break;
case "day":
timeStr = localDateTime.format(DAY_FORMATTER);
break;
case "month":
timeStr = localDateTime.format(MONTH_FORMATTER);
break;
default:
log.info("不支持的级别:" + granularity);
return null;
}
// 缓存键格式
return siteId + "_" + stackId + "_" + clusterId + "_" + batteryId + "_" +timeStr;
} catch (Exception e) {
log.error("生成缓存键失败", e);
return null;
}
}
// 工具方法:从队列中取出所有元素
protected <T> List<T> drainQueue(ConcurrentLinkedQueue<T> queue) {
List<T> result = new ArrayList<>();
T element;
while ((element = queue.poll()) != null) {
result.add(element);
}
return result;
}
/**
* 获取电池+粒度的专属锁(公共锁逻辑)
*/
protected ReentrantLock getLock(String siteId, String stackId, String clusterId,
String batteryId, String granularity) {
String lockKey = siteId + "_" + stackId + "_" + clusterId + "_" + batteryId + "_" + granularity;
return lockCache.computeIfAbsent(lockKey, k -> new ReentrantLock());
}
// 计算退避时间:指数退避 + 随机抖动,避免重试风暴
private long calculateBackoffTime(int retryCount) {
// 100ms * 2^retry + 随机0-100ms
return (long)(RETRY_BASE_DELAY * Math.pow(2, retryCount)) + random.nextInt(100);
}
// 判断是否为死锁异常
private boolean isDeadlockException(Throwable e) {
// 遍历所有异常链包括cause的cause...
while (e != null) {
// 检查异常消息是否包含死锁关键字
if (e.getMessage() != null && e.getMessage().contains("Deadlock found when trying to get lock")) {
return true;
}
// 深入下一层异常
e = e.getCause();
}
return false;
}
/**
* 公共缓存实体(所有站点通用)
*/
protected static class CacheValue {
BigDecimal maxTemp;
LocalDateTime lastUpdateTime;
public CacheValue(BigDecimal maxTemp, LocalDateTime lastUpdateTime) {
this.maxTemp = maxTemp;
this.lastUpdateTime = lastUpdateTime;
}
}
// 新增定时任务每1分钟强制刷新一次队列防止数据长期积压
@Scheduled(fixedRate = 60000) // 1分钟 = 60000毫秒
protected void scheduledFlushBatch() {
if (!hourBatchQueue.isEmpty()) {
List<EmsBatteryData> remaining = drainQueue(hourBatchQueue);
ioExecutor.execute(() -> batchUpdateHour(remaining));
log.info("定时任务触发,处理小时级剩余数据,数量: " + remaining.size());
}
if (!dayBatchQueue.isEmpty()) {
List<EmsBatteryData> remaining = drainQueue(dayBatchQueue);
ioExecutor.execute(() -> batchUpdateDay(remaining));
log.info("定时任务触发,处理天级剩余数据,数量: " + remaining.size());
}
if (!monthBatchQueue.isEmpty()) {
List<EmsBatteryData> remaining = drainQueue(monthBatchQueue);
ioExecutor.execute(() -> batchUpdateMonth(remaining));
log.info("定时任务触发,处理月级剩余数据,数量: " + remaining.size());
}
}
}

View File

@ -0,0 +1,201 @@
<?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.xzzn.ems.mapper.EmsBatteryDataDayMapper">
<resultMap type="EmsBatteryDataDay" id="EmsBatteryDataDayResult">
<result property="id" column="id" />
<result property="batteryPack" column="battery_pack" />
<result property="batteryCluster" column="battery_cluster" />
<result property="batteryCellId" column="battery_cell_id" />
<result property="voltage" column="voltage" />
<result property="temperature" column="temperature" />
<result property="soc" column="soc" />
<result property="soh" column="soh" />
<result property="dataTimestamp" column="data_timestamp" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<result property="siteId" column="site_id" />
<result property="deviceId" column="device_id" />
<result property="clusterDeviceId" column="cluster_device_id" />
<result property="interResistance" column="inter_resistance" />
<result property="dayTime" column="day_time" />
</resultMap>
<sql id="selectEmsBatteryDataDayVo">
select id, battery_pack, battery_cluster, battery_cell_id, voltage, temperature, soc, soh, data_timestamp, create_by, create_time, update_by, update_time, remark, site_id, device_id, cluster_device_id, inter_resistance, day_time from ems_battery_data_day
</sql>
<select id="selectEmsBatteryDataDayList" parameterType="EmsBatteryDataDay" resultMap="EmsBatteryDataDayResult">
<include refid="selectEmsBatteryDataDayVo"/>
<where>
<if test="batteryPack != null and batteryPack != ''"> and battery_pack = #{batteryPack}</if>
<if test="batteryCluster != null and batteryCluster != ''"> and battery_cluster = #{batteryCluster}</if>
<if test="batteryCellId != null and batteryCellId != ''"> and battery_cell_id = #{batteryCellId}</if>
<if test="voltage != null "> and voltage = #{voltage}</if>
<if test="temperature != null "> and temperature = #{temperature}</if>
<if test="soc != null "> and soc = #{soc}</if>
<if test="soh != null "> and soh = #{soh}</if>
<if test="dataTimestamp != null "> and data_timestamp = #{dataTimestamp}</if>
<if test="siteId != null and siteId != ''"> and site_id = #{siteId}</if>
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
<if test="clusterDeviceId != null and clusterDeviceId != ''"> and cluster_device_id = #{clusterDeviceId}</if>
<if test="interResistance != null "> and inter_resistance = #{interResistance}</if>
<if test="dayTime != null "> and day_time = #{dayTime}</if>
</where>
</select>
<select id="selectEmsBatteryDataDayById" parameterType="Long" resultMap="EmsBatteryDataDayResult">
<include refid="selectEmsBatteryDataDayVo"/>
where id = #{id}
</select>
<insert id="insertEmsBatteryDataDay" parameterType="EmsBatteryDataDay" useGeneratedKeys="true" keyProperty="id">
insert into ems_battery_data_day
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="batteryPack != null">battery_pack,</if>
<if test="batteryCluster != null">battery_cluster,</if>
<if test="batteryCellId != null">battery_cell_id,</if>
<if test="voltage != null">voltage,</if>
<if test="temperature != null">temperature,</if>
<if test="soc != null">soc,</if>
<if test="soh != null">soh,</if>
<if test="dataTimestamp != null">data_timestamp,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
<if test="siteId != null">site_id,</if>
<if test="deviceId != null">device_id,</if>
<if test="clusterDeviceId != null">cluster_device_id,</if>
<if test="interResistance != null">inter_resistance,</if>
<if test="dayTime != null">day_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="batteryPack != null">#{batteryPack},</if>
<if test="batteryCluster != null">#{batteryCluster},</if>
<if test="batteryCellId != null">#{batteryCellId},</if>
<if test="voltage != null">#{voltage},</if>
<if test="temperature != null">#{temperature},</if>
<if test="soc != null">#{soc},</if>
<if test="soh != null">#{soh},</if>
<if test="dataTimestamp != null">#{dataTimestamp},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
<if test="siteId != null">#{siteId},</if>
<if test="deviceId != null">#{deviceId},</if>
<if test="clusterDeviceId != null">#{clusterDeviceId},</if>
<if test="interResistance != null">#{interResistance},</if>
<if test="dayTime != null">#{dayTime},</if>
</trim>
</insert>
<update id="updateEmsBatteryDataDay" parameterType="EmsBatteryDataDay">
update ems_battery_data_day
<trim prefix="SET" suffixOverrides=",">
<if test="batteryPack != null">battery_pack = #{batteryPack},</if>
<if test="batteryCluster != null">battery_cluster = #{batteryCluster},</if>
<if test="batteryCellId != null">battery_cell_id = #{batteryCellId},</if>
<if test="voltage != null">voltage = #{voltage},</if>
<if test="temperature != null">temperature = #{temperature},</if>
<if test="soc != null">soc = #{soc},</if>
<if test="soh != null">soh = #{soh},</if>
<if test="dataTimestamp != null">data_timestamp = #{dataTimestamp},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="siteId != null">site_id = #{siteId},</if>
<if test="deviceId != null">device_id = #{deviceId},</if>
<if test="clusterDeviceId != null">cluster_device_id = #{clusterDeviceId},</if>
<if test="interResistance != null">inter_resistance = #{interResistance},</if>
<if test="dayTime != null">day_time = #{dayTime},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteEmsBatteryDataDayById" parameterType="Long">
delete from ems_battery_data_day where id = #{id}
</delete>
<delete id="deleteEmsBatteryDataDayByIds" parameterType="String">
delete from ems_battery_data_day where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="findDayMaxTemp" resultMap="EmsBatteryDataDayResult">
<include refid="selectEmsBatteryDataDayVo"/>
WHERE site_id = #{siteId}
AND battery_pack = #{stackId}
AND cluster_device_id = #{clusterId}
AND device_id = #{batteryId}
AND day_time = #{dayStart}
LIMIT 1
</select>
<!-- 批量插入或更新-->
<insert id="batchInsertOrUpdate">
INSERT INTO ems_battery_data_day (
battery_pack,
battery_cluster,
battery_cell_id,
voltage,
temperature,
soc,
soh,
data_timestamp,
create_by,
create_time,
update_by,
update_time,
remark,
site_id,
device_id,
cluster_device_id,
inter_resistance,
day_time
) VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.batteryPack},
#{item.batteryCluster},
#{item.batteryCellId},
#{item.voltage},
#{item.temperature},
#{item.soc},
#{item.soh},
#{item.dataTimestamp},
#{item.createBy},
#{item.createTime}, #{item.updateBy},
#{item.updateTime},
#{item.remark},
#{item.siteId},
#{item.deviceId},
#{item.clusterDeviceId},
#{item.interResistance},
#{item.dayTime}
)
</foreach>
ON DUPLICATE KEY UPDATE
-- 仅当新温度高于现有温度时,才更新温度和相关字段
update_time = IF(VALUES(temperature) > temperature, NOW(), update_time),
data_timestamp = IF(VALUES(temperature) > temperature, VALUES(data_timestamp), data_timestamp),
voltage = IF(VALUES(temperature) > temperature, VALUES(voltage), voltage),
soc = IF(VALUES(temperature) > temperature, VALUES(soc), soc),
soh = IF(VALUES(temperature) > temperature, VALUES(soh), soh),
inter_resistance = IF(VALUES(temperature) > temperature, VALUES(inter_resistance), inter_resistance),
update_by = IF(VALUES(temperature) > temperature, VALUES(update_by), update_by)
temperature = IF(VALUES(temperature) > temperature, VALUES(temperature), temperature)
</insert>
</mapper>

View File

@ -0,0 +1,202 @@
<?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.xzzn.ems.mapper.EmsBatteryDataHourMapper">
<resultMap type="EmsBatteryDataHour" id="EmsBatteryDataHourResult">
<result property="id" column="id" />
<result property="batteryPack" column="battery_pack" />
<result property="batteryCluster" column="battery_cluster" />
<result property="batteryCellId" column="battery_cell_id" />
<result property="voltage" column="voltage" />
<result property="temperature" column="temperature" />
<result property="soc" column="soc" />
<result property="soh" column="soh" />
<result property="dataTimestamp" column="data_timestamp" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<result property="siteId" column="site_id" />
<result property="deviceId" column="device_id" />
<result property="clusterDeviceId" column="cluster_device_id" />
<result property="interResistance" column="inter_resistance" />
<result property="hourTime" column="hour_time" />
</resultMap>
<sql id="selectEmsBatteryDataHourVo">
select id, battery_pack, battery_cluster, battery_cell_id, voltage, temperature, soc, soh, data_timestamp, create_by, create_time, update_by, update_time, remark, site_id, device_id, cluster_device_id, inter_resistance, hour_time from ems_battery_data_hour
</sql>
<select id="selectEmsBatteryDataHourList" parameterType="EmsBatteryDataHour" resultMap="EmsBatteryDataHourResult">
<include refid="selectEmsBatteryDataHourVo"/>
<where>
<if test="batteryPack != null and batteryPack != ''"> and battery_pack = #{batteryPack}</if>
<if test="batteryCluster != null and batteryCluster != ''"> and battery_cluster = #{batteryCluster}</if>
<if test="batteryCellId != null and batteryCellId != ''"> and battery_cell_id = #{batteryCellId}</if>
<if test="voltage != null "> and voltage = #{voltage}</if>
<if test="temperature != null "> and temperature = #{temperature}</if>
<if test="soc != null "> and soc = #{soc}</if>
<if test="soh != null "> and soh = #{soh}</if>
<if test="dataTimestamp != null "> and data_timestamp = #{dataTimestamp}</if>
<if test="siteId != null and siteId != ''"> and site_id = #{siteId}</if>
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
<if test="clusterDeviceId != null and clusterDeviceId != ''"> and cluster_device_id = #{clusterDeviceId}</if>
<if test="interResistance != null "> and inter_resistance = #{interResistance}</if>
<if test="hourTime != null "> and hour_time = #{hourTime}</if>
</where>
</select>
<select id="selectEmsBatteryDataHourById" parameterType="Long" resultMap="EmsBatteryDataHourResult">
<include refid="selectEmsBatteryDataHourVo"/>
where id = #{id}
</select>
<insert id="insertEmsBatteryDataHour" parameterType="EmsBatteryDataHour" useGeneratedKeys="true" keyProperty="id">
insert into ems_battery_data_hour
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="batteryPack != null">battery_pack,</if>
<if test="batteryCluster != null">battery_cluster,</if>
<if test="batteryCellId != null">battery_cell_id,</if>
<if test="voltage != null">voltage,</if>
<if test="temperature != null">temperature,</if>
<if test="soc != null">soc,</if>
<if test="soh != null">soh,</if>
<if test="dataTimestamp != null">data_timestamp,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
<if test="siteId != null">site_id,</if>
<if test="deviceId != null">device_id,</if>
<if test="clusterDeviceId != null">cluster_device_id,</if>
<if test="interResistance != null">inter_resistance,</if>
<if test="hourTime != null">hour_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="batteryPack != null">#{batteryPack},</if>
<if test="batteryCluster != null">#{batteryCluster},</if>
<if test="batteryCellId != null">#{batteryCellId},</if>
<if test="voltage != null">#{voltage},</if>
<if test="temperature != null">#{temperature},</if>
<if test="soc != null">#{soc},</if>
<if test="soh != null">#{soh},</if>
<if test="dataTimestamp != null">#{dataTimestamp},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
<if test="siteId != null">#{siteId},</if>
<if test="deviceId != null">#{deviceId},</if>
<if test="clusterDeviceId != null">#{clusterDeviceId},</if>
<if test="interResistance != null">#{interResistance},</if>
<if test="hourTime != null">#{hourTime},</if>
</trim>
</insert>
<update id="updateEmsBatteryDataHour" parameterType="EmsBatteryDataHour">
update ems_battery_data_hour
<trim prefix="SET" suffixOverrides=",">
<if test="batteryPack != null">battery_pack = #{batteryPack},</if>
<if test="batteryCluster != null">battery_cluster = #{batteryCluster},</if>
<if test="batteryCellId != null">battery_cell_id = #{batteryCellId},</if>
<if test="voltage != null">voltage = #{voltage},</if>
<if test="temperature != null">temperature = #{temperature},</if>
<if test="soc != null">soc = #{soc},</if>
<if test="soh != null">soh = #{soh},</if>
<if test="dataTimestamp != null">data_timestamp = #{dataTimestamp},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="siteId != null">site_id = #{siteId},</if>
<if test="deviceId != null">device_id = #{deviceId},</if>
<if test="clusterDeviceId != null">cluster_device_id = #{clusterDeviceId},</if>
<if test="interResistance != null">inter_resistance = #{interResistance},</if>
<if test="hourTime != null">hour_time = #{hourTime},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteEmsBatteryDataHourById" parameterType="Long">
delete from ems_battery_data_hour where id = #{id}
</delete>
<delete id="deleteEmsBatteryDataHourByIds" parameterType="String">
delete from ems_battery_data_hour where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="findHourMaxTemp" resultMap="EmsBatteryDataHourResult">
<include refid="selectEmsBatteryDataHourVo"/>
WHERE site_id = #{siteId}
AND battery_pack = #{stackId}
AND cluster_device_id = #{clusterId}
AND device_id = #{batteryId}
AND hour_time = #{hourStart}
LIMIT 1
</select>
<!-- 批量插入或更新-->
<insert id="batchInsertOrUpdate">
INSERT INTO ems_battery_data_hour (
battery_pack,
battery_cluster,
battery_cell_id,
voltage,
temperature,
soc,
soh,
data_timestamp,
create_by,
create_time,
update_by,
update_time,
remark,
site_id,
device_id,
cluster_device_id,
inter_resistance,
hour_time
) VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.batteryPack},
#{item.batteryCluster},
#{item.batteryCellId},
#{item.voltage},
#{item.temperature},
#{item.soc},
#{item.soh},
#{item.dataTimestamp},
#{item.createBy},
#{item.createTime},
#{item.updateBy},
#{item.updateTime},
#{item.remark},
#{item.siteId},
#{item.deviceId},
#{item.clusterDeviceId},
#{item.interResistance},
#{item.hourTime}
)
</foreach>
ON DUPLICATE KEY UPDATE
-- 仅当新温度高于现有温度时,才更新温度和相关字段
update_time = IF(VALUES(temperature) > temperature, NOW(), update_time),
data_timestamp = IF(VALUES(temperature) > temperature, VALUES(data_timestamp), data_timestamp),
voltage = IF(VALUES(temperature) > temperature, VALUES(voltage), voltage),
soc = IF(VALUES(temperature) > temperature, VALUES(soc), soc),
soh = IF(VALUES(temperature) > temperature, VALUES(soh), soh),
inter_resistance = IF(VALUES(temperature) > temperature, VALUES(inter_resistance), inter_resistance),
update_by = IF(VALUES(temperature) > temperature, VALUES(update_by), update_by)
temperature = IF(VALUES(temperature) > temperature, VALUES(temperature), temperature)
</insert>
</mapper>

View File

@ -0,0 +1,152 @@
<?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.xzzn.ems.mapper.EmsBatteryDataMinutesMapper">
<resultMap type="EmsBatteryDataMinutes" id="EmsBatteryDataMinutesResult">
<result property="id" column="id" />
<result property="batteryPack" column="battery_pack" />
<result property="batteryCluster" column="battery_cluster" />
<result property="batteryCellId" column="battery_cell_id" />
<result property="voltage" column="voltage" />
<result property="temperature" column="temperature" />
<result property="soc" column="soc" />
<result property="soh" column="soh" />
<result property="dataTimestamp" column="data_timestamp" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<result property="siteId" column="site_id" />
<result property="deviceId" column="device_id" />
<result property="clusterDeviceId" column="cluster_device_id" />
<result property="interResistance" column="inter_resistance" />
</resultMap>
<sql id="selectEmsBatteryDataMinutesVo">
select id, battery_pack, battery_cluster, battery_cell_id, voltage, temperature, soc, soh, data_timestamp, create_by, create_time, update_by, update_time, remark, site_id, device_id, cluster_device_id, inter_resistance from ems_battery_data_minutes
</sql>
<select id="selectEmsBatteryDataMinutesList" parameterType="EmsBatteryDataMinutes" resultMap="EmsBatteryDataMinutesResult">
<include refid="selectEmsBatteryDataMinutesVo"/>
<where>
<if test="batteryPack != null and batteryPack != ''"> and battery_pack = #{batteryPack}</if>
<if test="batteryCluster != null and batteryCluster != ''"> and battery_cluster = #{batteryCluster}</if>
<if test="batteryCellId != null and batteryCellId != ''"> and battery_cell_id = #{batteryCellId}</if>
<if test="voltage != null "> and voltage = #{voltage}</if>
<if test="temperature != null "> and temperature = #{temperature}</if>
<if test="soc != null "> and soc = #{soc}</if>
<if test="soh != null "> and soh = #{soh}</if>
<if test="dataTimestamp != null "> and data_timestamp = #{dataTimestamp}</if>
<if test="siteId != null and siteId != ''"> and site_id = #{siteId}</if>
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
<if test="clusterDeviceId != null and clusterDeviceId != ''"> and cluster_device_id = #{clusterDeviceId}</if>
<if test="interResistance != null "> and inter_resistance = #{interResistance}</if>
</where>
</select>
<select id="selectEmsBatteryDataMinutesById" parameterType="Long" resultMap="EmsBatteryDataMinutesResult">
<include refid="selectEmsBatteryDataMinutesVo"/>
where id = #{id}
</select>
<insert id="insertEmsBatteryDataMinutes" parameterType="EmsBatteryDataMinutes" useGeneratedKeys="true" keyProperty="id">
insert into ems_battery_data_minutes
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="batteryPack != null">battery_pack,</if>
<if test="batteryCluster != null">battery_cluster,</if>
<if test="batteryCellId != null">battery_cell_id,</if>
<if test="voltage != null">voltage,</if>
<if test="temperature != null">temperature,</if>
<if test="soc != null">soc,</if>
<if test="soh != null">soh,</if>
<if test="dataTimestamp != null">data_timestamp,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
<if test="siteId != null">site_id,</if>
<if test="deviceId != null">device_id,</if>
<if test="clusterDeviceId != null">cluster_device_id,</if>
<if test="interResistance != null">inter_resistance,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="batteryPack != null">#{batteryPack},</if>
<if test="batteryCluster != null">#{batteryCluster},</if>
<if test="batteryCellId != null">#{batteryCellId},</if>
<if test="voltage != null">#{voltage},</if>
<if test="temperature != null">#{temperature},</if>
<if test="soc != null">#{soc},</if>
<if test="soh != null">#{soh},</if>
<if test="dataTimestamp != null">#{dataTimestamp},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
<if test="siteId != null">#{siteId},</if>
<if test="deviceId != null">#{deviceId},</if>
<if test="clusterDeviceId != null">#{clusterDeviceId},</if>
<if test="interResistance != null">#{interResistance},</if>
</trim>
</insert>
<update id="updateEmsBatteryDataMinutes" parameterType="EmsBatteryDataMinutes">
update ems_battery_data_minutes
<trim prefix="SET" suffixOverrides=",">
<if test="batteryPack != null">battery_pack = #{batteryPack},</if>
<if test="batteryCluster != null">battery_cluster = #{batteryCluster},</if>
<if test="batteryCellId != null">battery_cell_id = #{batteryCellId},</if>
<if test="voltage != null">voltage = #{voltage},</if>
<if test="temperature != null">temperature = #{temperature},</if>
<if test="soc != null">soc = #{soc},</if>
<if test="soh != null">soh = #{soh},</if>
<if test="dataTimestamp != null">data_timestamp = #{dataTimestamp},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="siteId != null">site_id = #{siteId},</if>
<if test="deviceId != null">device_id = #{deviceId},</if>
<if test="clusterDeviceId != null">cluster_device_id = #{clusterDeviceId},</if>
<if test="interResistance != null">inter_resistance = #{interResistance},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteEmsBatteryDataMinutesByDateDay" parameterType="Date">
delete from ems_battery_data_minutes where date_day = #{dateDay}
</delete>
<delete id="deleteEmsBatteryDataMinutesByDateDays" parameterType="String">
delete from ems_battery_data_minutes where date_day in
<foreach item="dateDay" collection="array" open="(" separator="," close=")">
#{dateDay}
</foreach>
</delete>
<insert id="insertMinutesBatteryDataList" parameterType="java.util.List">
INSERT INTO ems_battery_data_minutes (
battery_pack, battery_cluster, battery_cell_id,
voltage, temperature, soc, soh, data_timestamp,
create_by, create_time, update_by, update_time,
remark, site_id, device_id, cluster_device_id, inter_resistance
) VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.batteryPack}, #{item.batteryCluster}, #{item.batteryCellId},
#{item.voltage}, #{item.temperature}, #{item.soc}, #{item.soh}, #{item.dataTimestamp},
#{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime},
#{item.remark}, #{item.siteId}, #{item.deviceId}, #{item.clusterDeviceId},#{item.interResistance}
)
</foreach>
</insert>
<delete id="deleteByTimeBeforeOneHour" parameterType="String">
delete from ems_battery_data_minutes where data_timestamp &lt; #{oneHourAgoStr}
</delete>
</mapper>

View File

@ -0,0 +1,202 @@
<?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.xzzn.ems.mapper.EmsBatteryDataMonthMapper">
<resultMap type="EmsBatteryDataMonth" id="EmsBatteryDataMonthResult">
<result property="id" column="id" />
<result property="batteryPack" column="battery_pack" />
<result property="batteryCluster" column="battery_cluster" />
<result property="batteryCellId" column="battery_cell_id" />
<result property="voltage" column="voltage" />
<result property="temperature" column="temperature" />
<result property="soc" column="soc" />
<result property="soh" column="soh" />
<result property="dataTimestamp" column="data_timestamp" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<result property="siteId" column="site_id" />
<result property="deviceId" column="device_id" />
<result property="clusterDeviceId" column="cluster_device_id" />
<result property="interResistance" column="inter_resistance" />
<result property="monthTime" column="month_time" />
</resultMap>
<sql id="selectEmsBatteryDataMonthVo">
select id, battery_pack, battery_cluster, battery_cell_id, voltage, temperature, soc, soh, data_timestamp, create_by, create_time, update_by, update_time, remark, site_id, device_id, cluster_device_id, inter_resistance, month_time from ems_battery_data_month
</sql>
<select id="selectEmsBatteryDataMonthList" parameterType="EmsBatteryDataMonth" resultMap="EmsBatteryDataMonthResult">
<include refid="selectEmsBatteryDataMonthVo"/>
<where>
<if test="batteryPack != null and batteryPack != ''"> and battery_pack = #{batteryPack}</if>
<if test="batteryCluster != null and batteryCluster != ''"> and battery_cluster = #{batteryCluster}</if>
<if test="batteryCellId != null and batteryCellId != ''"> and battery_cell_id = #{batteryCellId}</if>
<if test="voltage != null "> and voltage = #{voltage}</if>
<if test="temperature != null "> and temperature = #{temperature}</if>
<if test="soc != null "> and soc = #{soc}</if>
<if test="soh != null "> and soh = #{soh}</if>
<if test="dataTimestamp != null "> and data_timestamp = #{dataTimestamp}</if>
<if test="siteId != null and siteId != ''"> and site_id = #{siteId}</if>
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
<if test="clusterDeviceId != null and clusterDeviceId != ''"> and cluster_device_id = #{clusterDeviceId}</if>
<if test="interResistance != null "> and inter_resistance = #{interResistance}</if>
<if test="monthTime != null "> and month_time = #{monthTime}</if>
</where>
</select>
<select id="selectEmsBatteryDataMonthById" parameterType="Long" resultMap="EmsBatteryDataMonthResult">
<include refid="selectEmsBatteryDataMonthVo"/>
where id = #{id}
</select>
<insert id="insertEmsBatteryDataMonth" parameterType="EmsBatteryDataMonth" useGeneratedKeys="true" keyProperty="id">
insert into ems_battery_data_month
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="batteryPack != null">battery_pack,</if>
<if test="batteryCluster != null">battery_cluster,</if>
<if test="batteryCellId != null">battery_cell_id,</if>
<if test="voltage != null">voltage,</if>
<if test="temperature != null">temperature,</if>
<if test="soc != null">soc,</if>
<if test="soh != null">soh,</if>
<if test="dataTimestamp != null">data_timestamp,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
<if test="siteId != null">site_id,</if>
<if test="deviceId != null">device_id,</if>
<if test="clusterDeviceId != null">cluster_device_id,</if>
<if test="interResistance != null">inter_resistance,</if>
<if test="monthTime != null">month_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="batteryPack != null">#{batteryPack},</if>
<if test="batteryCluster != null">#{batteryCluster},</if>
<if test="batteryCellId != null">#{batteryCellId},</if>
<if test="voltage != null">#{voltage},</if>
<if test="temperature != null">#{temperature},</if>
<if test="soc != null">#{soc},</if>
<if test="soh != null">#{soh},</if>
<if test="dataTimestamp != null">#{dataTimestamp},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
<if test="siteId != null">#{siteId},</if>
<if test="deviceId != null">#{deviceId},</if>
<if test="clusterDeviceId != null">#{clusterDeviceId},</if>
<if test="interResistance != null">#{interResistance},</if>
<if test="monthTime != null">#{monthTime},</if>
</trim>
</insert>
<update id="updateEmsBatteryDataMonth" parameterType="EmsBatteryDataMonth">
update ems_battery_data_month
<trim prefix="SET" suffixOverrides=",">
<if test="batteryPack != null">battery_pack = #{batteryPack},</if>
<if test="batteryCluster != null">battery_cluster = #{batteryCluster},</if>
<if test="batteryCellId != null">battery_cell_id = #{batteryCellId},</if>
<if test="voltage != null">voltage = #{voltage},</if>
<if test="temperature != null">temperature = #{temperature},</if>
<if test="soc != null">soc = #{soc},</if>
<if test="soh != null">soh = #{soh},</if>
<if test="dataTimestamp != null">data_timestamp = #{dataTimestamp},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="siteId != null">site_id = #{siteId},</if>
<if test="deviceId != null">device_id = #{deviceId},</if>
<if test="clusterDeviceId != null">cluster_device_id = #{clusterDeviceId},</if>
<if test="interResistance != null">inter_resistance = #{interResistance},</if>
<if test="monthTime != null">month_time = #{monthTime},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteEmsBatteryDataMonthById" parameterType="Long">
delete from ems_battery_data_month where id = #{id}
</delete>
<delete id="deleteEmsBatteryDataMonthByIds" parameterType="String">
delete from ems_battery_data_month where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="findMonthMaxTemp" resultMap="EmsBatteryDataMonthResult">
<include refid="selectEmsBatteryDataMonthVo"/>
WHERE site_id = #{siteId}
AND battery_pack = #{stackId}
AND cluster_device_id = #{clusterId}
AND device_id = #{batteryId}
AND month_time = #{monthStart}
LIMIT 1
</select>
<!-- 批量插入或更新-->
<insert id="batchInsertOrUpdate">
INSERT INTO ems_battery_data_month (
battery_pack,
battery_cluster,
battery_cell_id,
voltage,
temperature,
soc,
soh,
data_timestamp,
create_by,
create_time,
update_by,
update_time,
remark,
site_id,
device_id,
cluster_device_id,
inter_resistance,
month_time
) VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.batteryPack},
#{item.batteryCluster},
#{item.batteryCellId},
#{item.voltage},
#{item.temperature},
#{item.soc},
#{item.soh},
#{item.dataTimestamp},
#{item.createBy},
#{item.createTime},
#{item.updateBy},
#{item.updateTime},
#{item.remark},
#{item.siteId},
#{item.deviceId},
#{item.clusterDeviceId},
#{item.interResistance},
#{item.monthTime}
)
</foreach>
ON DUPLICATE KEY UPDATE
-- 仅当新温度高于现有温度时,才更新温度和相关字段
update_time = IF(VALUES(temperature) > temperature, NOW(), update_time),
data_timestamp = IF(VALUES(temperature) > temperature, VALUES(data_timestamp), data_timestamp),
voltage = IF(VALUES(temperature) > temperature, VALUES(voltage), voltage),
soc = IF(VALUES(temperature) > temperature, VALUES(soc), soc),
soh = IF(VALUES(temperature) > temperature, VALUES(soh), soh),
inter_resistance = IF(VALUES(temperature) > temperature, VALUES(inter_resistance), inter_resistance),
update_by = IF(VALUES(temperature) > temperature, VALUES(update_by), update_by)
temperature = IF(VALUES(temperature) > temperature, VALUES(temperature), temperature)
</insert>
</mapper>