Compare commits

..

4 Commits

Author SHA1 Message Date
da894c26d1 Merge remote-tracking branch 'origin/dev' into dev 2025-06-27 12:50:58 +08:00
f439228432 mqtt配置 2025-06-27 12:49:14 +08:00
3609b03deb 单体电池-框架 2025-06-27 10:14:15 +08:00
0544929d07 Merge pull request 'work init' (#1) from main into dev
Reviewed-on: #1
2025-06-26 08:22:37 +00:00
18 changed files with 593 additions and 5 deletions

View File

@ -2,6 +2,7 @@ package com.xzzn.web.controller.ems;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.ems.service.IEmsSiteService;
import com.xzzn.ems.service.ISingleSiteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@ -17,6 +18,8 @@ public class EmsSiteMonitorController extends BaseController{
@Autowired
private ISingleSiteService iSingleSiteService;
@Autowired
private IEmsSiteService iEmsSiteService;
/**
* 获取单站首页数据
@ -78,16 +81,16 @@ public class EmsSiteMonitorController extends BaseController{
@GetMapping("/getStackNameList")
public AjaxResult getStackNameList(@RequestParam Long siteId)
{
return success(iSingleSiteService.getBMSBatteryCluster(siteId));
return success(iEmsSiteService.getAllStackInfo(siteId));
}
/**
* 获取所有电池簇
*/
@GetMapping("/getClusterNameList")
public AjaxResult getClusterNameList(@RequestParam Long siteId)
public AjaxResult getClusterNameList(@RequestParam Long stackDeviceId)
{
return success(iSingleSiteService.getBMSBatteryCluster(siteId));
return success(iEmsSiteService.getAllClusterInfo(stackDeviceId));
}
/**
@ -98,4 +101,13 @@ public class EmsSiteMonitorController extends BaseController{
{
return success(iSingleSiteService.getCoolingDataList(siteId));
}
/**
* 获取电池簇下面的单体电池数据
*/
@GetMapping("/getClusterDataInfoList")
public AjaxResult getClusterDataInfoList(@RequestParam Long clusterDeviceId)
{
return success(iEmsSiteService.getClusterDataInfoList(clusterDeviceId));
}
}

View File

@ -0,0 +1,79 @@
package com.xzzn.web.controller.ems;
import com.xzzn.framework.manager.MqttLifecycleManager;
import com.xzzn.framework.web.service.MqttPublisher;
import com.xzzn.framework.web.service.MqttSubscriber;
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
@Service
public class MqttMessageController implements MqttPublisher, MqttSubscriber {
private final MqttLifecycleManager mqttLifecycleManager;
@Autowired
public MqttMessageController(MqttLifecycleManager mqttLifecycleManager) {
this.mqttLifecycleManager = mqttLifecycleManager;
}
@PostConstruct
public void init() {
// 订阅系统状态主题
subscribe("system/status", 1, this::handleSystemStatus);
// 订阅设备数据主题
subscribe("devices/data", 0, this::handleDeviceData);
}
// 处理系统状态消息
private void handleSystemStatus(String topic, MqttMessage message) {
String payload = new String(message.getPayload());
System.out.println("[SYSTEM] Status update: " + payload);
// 业务处理逻辑
}
// 处理设备数据
private void handleDeviceData(String topic, MqttMessage message) {
String payload = new String(message.getPayload());
System.out.println("[DEVICE] data: " + payload);
// 业务处理逻辑
}
@Override
public void publish(String topic, String message) throws MqttException {
mqttLifecycleManager.publish(topic, message, 0);
}
@Override
public void publish(String topic, String message, int qos) throws MqttException {
mqttLifecycleManager.publish(topic, message, qos);
}
@Override
public void subscribe(String topic, int qos, IMqttMessageListener listener) {
mqttLifecycleManager.subscribe(topic, qos, listener);
}
// 发送设备控制命令
public void sendDeviceCommand(String deviceId, String command) {
try {
String topic = "devices/" + deviceId + "/commands";
publish(topic, command, 1);
} catch (MqttException e) {
System.err.println("Failed to send command to device " + deviceId);
}
}
}

View File

@ -127,3 +127,12 @@ xss:
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
mqtt:
broker.url: tcp://122.51.194.184:1883
client.id: ems-cloud
username: admin
password: pass123
connection-timeout: 15
keep-alive-interval: 30
automatic-reconnect: true

View File

@ -46,7 +46,10 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
</dependency>
<!-- 获取系统信息 -->
<dependency>
<groupId>com.github.oshi</groupId>
@ -58,6 +61,11 @@
<groupId>com.xzzn</groupId>
<artifactId>ems-system</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

View File

@ -0,0 +1,31 @@
package com.xzzn.framework.config;
import com.xzzn.framework.config.properties.MqttProperties;
import org.eclipse.paho.client.mqttv3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
@Configuration
public class MqttConfig {
private static final Logger logger = LoggerFactory.getLogger(MqttConfig.class);
@Resource
private MqttProperties mqttProperties;
@Bean
public MqttConnectOptions mqttConnectOptions() {
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[]{mqttProperties.getBrokerUrl()});
if (!mqttProperties.getUsername().isEmpty()) options.setUserName(mqttProperties.getUsername());
if (!mqttProperties.getPassword().isEmpty()) options.setPassword(mqttProperties.getPassword().toCharArray());
options.setConnectionTimeout(mqttProperties.getConnectionTimeout());
options.setKeepAliveInterval(mqttProperties.getKeepAliveInterval());
options.setAutomaticReconnect(mqttProperties.isAutomaticReconnect());
options.setCleanSession(true);
return options;
}
}

View File

@ -0,0 +1,31 @@
package com.xzzn.framework.config.properties;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
public class MqttProperties {
@Value("${mqtt.broker.url}")
private String brokerUrl;
@Value("${mqtt.client.id:}")
private String clientId;
@Value("${mqtt.username:}")
private String username;
@Value("${mqtt.password:}")
private String password;
@Value("${mqtt.connection-timeout:10}")
private int connectionTimeout;
@Value("${mqtt.keep-alive-interval:60}")
private int keepAliveInterval;
@Value("${mqtt.automatic-reconnect:true}")
private boolean automaticReconnect;
}

View File

@ -0,0 +1,155 @@
package com.xzzn.framework.manager;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle, MqttCallback {
private final MqttConnectOptions connectOptions;
private MqttClient mqttClient;
private volatile boolean running = false;
// 存储订阅关系: topic -> (listener, qos)
private final ConcurrentHashMap<String, SubscriptionInfo> subscriptions = new ConcurrentHashMap<>();
@Autowired
public MqttLifecycleManager(MqttConnectOptions connectOptions) {
this.connectOptions = connectOptions;
}
// Spring Boot 启动完成后执行
@Override
public void run(ApplicationArguments args) throws Exception {
start();
}
@Override
public void start() {
if (running) return;
try {
String clientId = connectOptions.getUserName() + "-" + System.currentTimeMillis();
mqttClient = new MqttClient(
connectOptions.getServerURIs()[0],
clientId,
new MemoryPersistence()
);
mqttClient.setCallback(this);
mqttClient.connect(connectOptions);
// 重连后自动重新订阅
resubscribeAll();
running = true;
System.out.println("MQTT client connected to: " + connectOptions.getServerURIs()[0]);
} catch (MqttException e) {
System.err.println("MQTT connection failed: " + e.getMessage());
// 添加重试逻辑
}
}
@Override
public void stop() {
if (mqttClient != null && mqttClient.isConnected()) {
try {
mqttClient.disconnect();
mqttClient.close();
} catch (MqttException e) {
System.err.println("Error disconnecting MQTT client: " + e.getMessage());
}
}
running = false;
}
@Override
public boolean isRunning() {
return running;
}
// MQTT 回调方法
@Override
public void connectionLost(Throwable cause) {
System.err.println("MQTT connection lost: " + cause.getMessage());
running = false;
// 自动重连由 MqttConnectOptions 处理
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
SubscriptionInfo info = subscriptions.get(topic);
if (info != null && info.getListener() != null) {
info.getListener().messageArrived(topic, message);
}
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
// 消息发布完成处理
}
// 订阅方法
public void subscribe(String topic, int qos, IMqttMessageListener listener) {
try {
if (mqttClient != null && mqttClient.isConnected()) {
mqttClient.subscribe(topic, qos);
}
subscriptions.put(topic, new SubscriptionInfo(listener, qos));
} catch (MqttException e) {
System.err.println("Subscribe failed: " + e.getMessage());
}
}
// 发布方法
public void publish(String topic, String payload, int qos) throws MqttException {
if (mqttClient != null && mqttClient.isConnected()) {
MqttMessage message = new MqttMessage(payload.getBytes());
message.setQos(qos);
mqttClient.publish(topic, message);
} else {
throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
}
}
// 重新订阅所有主题
private void resubscribeAll() {
if (mqttClient == null || !mqttClient.isConnected()) return;
subscriptions.forEach((topic, info) -> {
try {
mqttClient.subscribe(topic, info.getQos());
} catch (MqttException e) {
System.err.println("Resubscribe failed for topic: " + topic);
}
});
}
// 订阅信息内部类
private static class SubscriptionInfo {
private final IMqttMessageListener listener;
private final int qos;
public SubscriptionInfo(IMqttMessageListener listener, int qos) {
this.listener = listener;
this.qos = qos;
}
public IMqttMessageListener getListener() {
return listener;
}
public int getQos() {
return qos;
}
}
}

View File

@ -0,0 +1,8 @@
package com.xzzn.framework.web.service;
import org.eclipse.paho.client.mqttv3.MqttException;
public interface MqttPublisher {
void publish(String topic, String message) throws MqttException;
void publish(String topic, String message, int qos) throws MqttException;
}

View File

@ -0,0 +1,7 @@
package com.xzzn.framework.web.service;
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
public interface MqttSubscriber {
void subscribe(String topic, int qos, IMqttMessageListener listener);
}

View File

@ -0,0 +1,61 @@
package com.xzzn.ems.domain.vo;
import java.util.List;
/**
* 单站监控-单体电池数据
*
*/
public class BatteryDataStatsListVo {
/**
* 电压List
*/
private List<BatteryDataStatsTemplate> voltageDataList;
/**
* 温度List
*/
private List<BatteryDataStatsTemplate> tempDataList;
/**
* SOCList
*/
private List<BatteryDataStatsTemplate> socDataList;
/**
* SOHList
*/
private List<BatteryDataStatsTemplate> sohDataList;
public List<BatteryDataStatsTemplate> getVoltageDataList() {
return voltageDataList;
}
public void setVoltageDataList(List<BatteryDataStatsTemplate> voltageDataList) {
this.voltageDataList = voltageDataList;
}
public List<BatteryDataStatsTemplate> getTempDataList() {
return tempDataList;
}
public void setTempDataList(List<BatteryDataStatsTemplate> tempDataList) {
this.tempDataList = tempDataList;
}
public List<BatteryDataStatsTemplate> getSocDataList() {
return socDataList;
}
public void setSocDataList(List<BatteryDataStatsTemplate> socDataList) {
this.socDataList = socDataList;
}
public List<BatteryDataStatsTemplate> getSohDataList() {
return sohDataList;
}
public void setSohDataList(List<BatteryDataStatsTemplate> sohDataList) {
this.sohDataList = sohDataList;
}
}

View File

@ -0,0 +1,48 @@
package com.xzzn.ems.domain.vo;
import java.math.BigDecimal;
/**
* 单体电池-数据统计模板
*
*/
public class BatteryDataStatsTemplate {
/**
* 月份
*/
private String dataMonth;
/**
* 数据一
*/
private BigDecimal dataOne;
/**
* 数据二
*/
private BigDecimal dataTwo;
public String getDataMonth() {
return dataMonth;
}
public void setDataMonth(String dataMonth) {
this.dataMonth = dataMonth;
}
public BigDecimal getDataOne() {
return dataOne;
}
public void setDataOne(BigDecimal dataOne) {
this.dataOne = dataOne;
}
public BigDecimal getDataTwo() {
return dataTwo;
}
public void setDataTwo(BigDecimal dataTwo) {
this.dataTwo = dataTwo;
}
}

View File

@ -1,6 +1,8 @@
package com.xzzn.ems.mapper;
import java.util.List;
import java.util.Map;
import com.xzzn.ems.domain.EmsDevicesSetting;
/**
@ -58,4 +60,18 @@ public interface EmsDevicesSettingMapper
* @return 结果
*/
public int deleteEmsDevicesSettingByIds(Long[] ids);
/**
* 根据site_id获取所有电池堆
* @param siteId
* @return
*/
public List<Map<String, Object>> getAllStackInfoBySiteId(Long siteId);
/**
* 根据电池堆id获取所有电池簇
* @param stackDeviceId
* @return
*/
public List<Map<String, Object>> getAllClusterInfoByStackId(Long stackDeviceId);
}

View File

@ -1,9 +1,11 @@
package com.xzzn.ems.service;
import com.xzzn.ems.domain.EmsSiteSetting;
import com.xzzn.ems.domain.vo.BatteryDataStatsListVo;
import com.xzzn.ems.domain.vo.SiteTotalInfoVo;
import java.util.List;
import java.util.Map;
/**
* 站点信息 服务层
@ -16,4 +18,10 @@ public interface IEmsSiteService
public SiteTotalInfoVo getSiteTotalInfo();
public List<Map<String,Object>> getAllStackInfo(Long siteId);
public List<Map<String,Object>> getAllClusterInfo(Long stackDeviceId);
public BatteryDataStatsListVo getClusterDataInfoList(Long clusterDeviceId);
}

View File

@ -1,13 +1,16 @@
package com.xzzn.ems.service.impl;
import com.xzzn.ems.domain.EmsSiteSetting;
import com.xzzn.ems.domain.vo.BatteryDataStatsListVo;
import com.xzzn.ems.domain.vo.SiteTotalInfoVo;
import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.ems.mapper.EmsSiteSettingMapper;
import com.xzzn.ems.service.IEmsSiteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
* 站点信息 服务层实现
@ -19,6 +22,8 @@ public class EmsSiteServiceImpl implements IEmsSiteService
@Autowired
private EmsSiteSettingMapper emsSiteMapper;
@Autowired
private EmsDevicesSettingMapper emsDevicesMapper;
@Override
public List<EmsSiteSetting> getAllSites() {
@ -30,4 +35,28 @@ public class EmsSiteServiceImpl implements IEmsSiteService
return emsSiteMapper.getSiteTotalInfo();
}
/**
* 根据site_id获取所有电池堆
* @param siteId
* @return
*/
@Override
public List<Map<String, Object>> getAllStackInfo(Long siteId) {
return emsDevicesMapper.getAllStackInfoBySiteId(siteId);
}
@Override
public List<Map<String, Object>> getAllClusterInfo(Long stackDeviceId) {
return emsDevicesMapper.getAllClusterInfoByStackId(stackDeviceId);
}
/**
* 根据电池堆获取单体柱状数据
* @param clusterDeviceId
* @return
*/
@Override
public BatteryDataStatsListVo getClusterDataInfoList(Long clusterDeviceId) {
return null;
}
}

View File

@ -157,4 +157,16 @@
and t.device_id = tmp.device_id)
order by tmp.device_id
</select>
<select id="getBmsBatteryData" resultType="com.xzzn.ems.domain.vo.BMSBatteryDataList">
select t.cluster_voltage as clusterVoltage,t.cluster_current as clusterCurrent,
t.current_soc as currentSoc,MAX(tb.voltage) as maxVoltage,MIN(tb.voltage) as minVoltage,
MAX(tb.temperature) as maxTemperature,MIN(tb.temperature) as minTemperature,
tb.site_id as siteId,tb.cluster_device_id as clusterId,tb.device_id as stackDeviceId
from ems_battery_cluster t left join ems_battery_data tb on t.device_id = tb.cluster_device_id
where t.site_id = #{siteId} and t.stack_device_id = #{stackDeviceId}
and t.update_time = (select MAX(update_time) FROM ems_battery_cluster where site_id = t.site_id
and device_id = t.device_id and stack_device_id = t.stack_device_id)
group by t.cluster_voltage,t.cluster_current,t.current_soc,tb.site_id,tb.cluster_device_id,tb.device_id
</select>
</mapper>

View File

@ -21,10 +21,11 @@
<result property="remark" column="remark" />
<result property="siteId" column="site_id" />
<result property="deviceId" column="device_id" />
<result property="clusterDeviceId" column="cluster_device_id" />
</resultMap>
<sql id="selectEmsBatteryDataVo">
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 from ems_battery_data
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 from ems_battery_data
</sql>
<select id="selectEmsBatteryDataList" parameterType="EmsBatteryData" resultMap="EmsBatteryDataResult">
@ -40,6 +41,7 @@
<if test="dataTimestamp != null "> and data_timestamp = #{dataTimestamp}</if>
<if test="siteId != null "> and site_id = #{siteId}</if>
<if test="deviceId != null "> and device_id = #{deviceId}</if>
<if test="clusterDeviceId != null "> and cluster_device_id = #{clusterDeviceId}</if>
</where>
</select>
@ -66,6 +68,7 @@
<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>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="batteryPack != null">#{batteryPack},</if>
@ -83,6 +86,7 @@
<if test="remark != null">#{remark},</if>
<if test="siteId != null">#{siteId},</if>
<if test="deviceId != null">#{deviceId},</if>
<if test="clusterDeviceId != null">#{clusterDeviceId},</if>
</trim>
</insert>
@ -104,6 +108,7 @@
<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>
</trim>
where id = #{id}
</update>
@ -124,4 +129,51 @@
where site_id = #{siteId}
and DATE(data_timestamp) = DATE(NOW())
</select>
<select id="getBatteryDataByClusterId" resultType="com.xzzn.ems.domain.vo.BatteryClusterDataDetailVo">
select t.site_id as siteId,t.cluster_device_id as clusterId,
AVG(t.voltage) as avgVoltage,MAX(t.voltage) as maxVoltage,MIN(t.voltage) as minVoltage,
AVG(t.temperature) as avgTemp,MAX(t.temperature) as maxTemp,MIN(t.temperature) as minTemp,
AVG(t.soc) as avgSoc,MAX(t.soc) as maxSoc,MIN(t.soc) as minSoc
from ems_battery_data t
where t.site_id = #{siteId}
and t.cluster_device_id = #{clusterDeviceId}
and t.update_time = (select MAX(update_time) FROM ems_battery_data where site_id = t.site_id
and device_id = t.device_id and cluster_device_id = t.cluster_device_id)
group by t.site_id,t.cluster_device_id
</select>
<select id="getDataIdsMap" parameterType="com.xzzn.ems.domain.vo.BatteryClusterDataDetailVo" resultType="java.util.Map">
<if test="maxVoltage != null">
select 'maxVoltageId' as type, t.device_id from ems_battery_data t
where t.site_id = #{siteId} and t.cluster_device_id = #{clusterId} and t.voltage = #{maxVoltage}
union all
</if>
<if test="minVoltage != null">
select 'minVoltageId' as type, t.device_id from ems_battery_data t
where t.site_id = #{siteId} and t.cluster_device_id = #{clusterId} and t.voltage = #{minVoltage}
union all
</if>
<if test="maxTemp != null">
select 'maxTempId' as type, t.device_id from ems_battery_data t
where t.site_id = #{siteId} and t.cluster_device_id = #{clusterId} and t.temperature = #{maxTemp}
union all
</if>
<if test="minTemp != null">
select 'minTempId' as type, t.device_id from ems_battery_data t
where t.site_id = #{siteId} and t.cluster_device_id = #{clusterId} and t.temperature = #{minTemp}
union all
</if>
<if test="maxSoc != null">
select 'maxSocId' as type, t.device_id from ems_battery_data t
where t.site_id = #{siteId} and t.cluster_device_id = #{clusterId} and t.soc = #{maxSoc}
union all
</if>
<if test="minSoc != null">
select 'minSocId' as type, t.device_id from ems_battery_data t
where t.site_id = #{siteId} and t.cluster_device_id = #{clusterId} and t.soc = #{minSoc}
union all
</if>
SELECT NULL AS type, NULL AS device_id FROM DUAL WHERE 1=0
</select>
</mapper>

View File

@ -133,4 +133,16 @@
#{id}
</foreach>
</delete>
<select id="getAllStackInfoBySiteId" parameterType="Long" resultType="java.util.Map">
select id,device_name as deviceName from ems_devices_setting
where id in (
select distinct eb.device_id from ems_battery_stack eb where eb.site_id = #{siteId})
</select>
<select id="getAllClusterInfoByStackId" parameterType="Long" resultType="java.util.Map">
select id,device_name as deviceName
from ems_devices_setting
where id in (select distinct eb.device_id from ems_battery_cluster eb where eb.stack_device_id = #{stackDeviceId})
</select>
</mapper>

10
pom.xml
View File

@ -154,6 +154,16 @@
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
<!-- -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version> <!-- 检查最新版本 -->
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<!-- velocity代码生成使用模板 -->
<dependency>