commit 36cad670e8e84e8dc3d489e0ea5a3959b8d2265c Author: dashixiong Date: Wed Nov 19 10:50:30 2025 +0800 init diff --git a/logs/mqtt-config-api-api.log b/logs/mqtt-config-api-api.log new file mode 100644 index 0000000..d42405a --- /dev/null +++ b/logs/mqtt-config-api-api.log @@ -0,0 +1,4 @@ +2025-10-01 20:48:47.371 [http-nio-7999-exec-1] INFO c.x.m.c.ClimateThresholdController - getThresholdById id: 1, threshold: Optional.empty +2025-10-01 20:50:35.861 [http-nio-7999-exec-5] INFO c.x.m.c.ClimateThresholdController - getThresholdById id: 1, threshold: Optional[com.xzzn.movecheck.repo.ClimateThreshold@9a18622] +2025-10-01 20:56:13.467 [http-nio-7999-exec-3] INFO c.x.m.c.ClimateThresholdController - getThresholdById id: 1, threshold: Optional[com.xzzn.movecheck.repo.ClimateThreshold@54616eab] +2025-10-01 20:59:12.931 [http-nio-7999-exec-8] INFO c.x.m.c.ClimateThresholdController - getThresholdById id: 1, threshold: Optional[com.xzzn.movecheck.repo.ClimateThreshold@3ed28f8d] diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a26a45d --- /dev/null +++ b/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + com.xzzn + movecheck + 1.0.0 + jar + + + org.springframework.boot + spring-boot-starter-parent + 2.5.15 + + + + + 1.8 + 1.8 + 1.8 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.mysql + mysql-connector-j + 8.0.33 + + + + org.apache.httpcomponents + httpclient + 4.5.14 + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.5 + + + com.alibaba + fastjson + 1.2.83 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/MovecheckApplication.java b/src/main/java/com/xzzn/movecheck/MovecheckApplication.java new file mode 100644 index 0000000..b92ebf2 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/MovecheckApplication.java @@ -0,0 +1,15 @@ +package com.xzzn.movecheck; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MovecheckApplication { + + public static void main(String[] args) { + + SpringApplication.run(MovecheckApplication.class, args + ); + } + +} diff --git a/src/main/java/com/xzzn/movecheck/config/CorsConfig.java b/src/main/java/com/xzzn/movecheck/config/CorsConfig.java new file mode 100644 index 0000000..2273202 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/config/CorsConfig.java @@ -0,0 +1,25 @@ +package com.xzzn.movecheck.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig { + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(false) // 设置为false + .maxAge(3600); + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/config/HttpClientConfig.java b/src/main/java/com/xzzn/movecheck/config/HttpClientConfig.java new file mode 100644 index 0000000..ec4d196 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/config/HttpClientConfig.java @@ -0,0 +1,26 @@ +// config/HttpClientConfig.java +package com.xzzn.movecheck.config; + +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class HttpClientConfig { + + @Bean + public CloseableHttpClient closeableHttpClient() { + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(30000) // 连接超时30秒 + .setSocketTimeout(30000) // socket超时30秒 + .setConnectionRequestTimeout(30000) // 请求超时30秒 + .build(); + + return HttpClientBuilder.create() + .setDefaultRequestConfig(requestConfig) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/config/MqttAutoConfiguration.java b/src/main/java/com/xzzn/movecheck/config/MqttAutoConfiguration.java new file mode 100644 index 0000000..27fe41d --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/config/MqttAutoConfiguration.java @@ -0,0 +1,81 @@ +package com.xzzn.movecheck.config; + +import com.xzzn.movecheck.service.MqttMessageHandler; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PreDestroy; + +@Configuration +public class MqttAutoConfiguration { + + @Value("${mqtt.broker:tcp://localhost:1883}") + private String broker; + + @Value("${mqtt.clientId:springboot-mqtt-client}") + private String clientId; + + @Value("${mqtt.topics:HDYDCJ_01_DEVICE,HDYDCJ_01_UP}") + private String[] topics; + + @Value("${mqtt.qos:1,1}") + private int[] qos; + + private MqttClient mqttClient; + + @Bean + public MqttConnectOptions mqttConnectOptions() { + MqttConnectOptions options = new MqttConnectOptions(); + options.setServerURIs(new String[]{broker}); + options.setCleanSession(true); + options.setConnectionTimeout(10); + options.setKeepAliveInterval(20); + options.setAutomaticReconnect(true); + options.setMaxReconnectDelay(5000); + options.setUserName("dmbroker"); + options.setPassword("qwer1234".toCharArray()); + + return options; + } + + @Bean + public MqttClient mqttClient(MqttConnectOptions options, MqttMessageHandler messageHandler) + throws MqttException { + mqttClient = new MqttClient(broker, clientId, new MemoryPersistence()); + + // 设置回调 + mqttClient.setCallback(messageHandler); + + // 连接 + mqttClient.connect(options); + System.out.println("MQTT客户端连接成功"); + + // 订阅主题 + if (topics != null && topics.length > 0) { + mqttClient.subscribe(topics, qos); + for (String topic : topics) { + System.out.println("已订阅主题: " + topic); + } + } + + return mqttClient; + } + + @PreDestroy + public void destroy() { + if (mqttClient != null && mqttClient.isConnected()) { + try { + mqttClient.disconnect(); + mqttClient.close(); + System.out.println("MQTT客户端已断开连接"); + } catch (MqttException e) { + System.err.println("关闭MQTT客户端时出错: " + e.getMessage()); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/config/UserConfig.java b/src/main/java/com/xzzn/movecheck/config/UserConfig.java new file mode 100644 index 0000000..95f3016 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/config/UserConfig.java @@ -0,0 +1,18 @@ +// UserConfig.java +package com.xzzn.movecheck.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +public class UserConfig { + private String username = "admin"; + private String password = "123456"; + + // Getter和Setter + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/AlertData.java b/src/main/java/com/xzzn/movecheck/repo/AlertData.java new file mode 100644 index 0000000..6df89c1 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/AlertData.java @@ -0,0 +1,149 @@ +package com.xzzn.movecheck.repo; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "alert_data") +public class AlertData { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "content", nullable = false, length = 1000) + private String content; // 告警内容 + + @Column(name = "category", nullable = false) + private String category; // 告警种类 + + @Column(name = "alert_time", nullable = false) + private LocalDateTime alertTime; // 告警时间 + + @Column(name = "level", nullable = false) + private String level; // 告警级别 + + @Column(name = "action") + private String action; // 处置措施 + + @Column(name = "action_time") + private LocalDateTime actionTime; // 处置时间 + + @Column(name = "create_time") + private LocalDateTime createTime; // 创建时间 + + @Column(name = "device_id") + private String deviceId; // 设备ID(可选) + + // 构造函数 + public AlertData() { + this.createTime = LocalDateTime.now(); + } + + public AlertData(String content, String category, LocalDateTime alertTime, String level) { + this(); + this.content = content; + this.category = category; + this.alertTime = alertTime; + this.level = level; + } + + public AlertData(String content, String category, LocalDateTime alertTime, String level, + String action, LocalDateTime actionTime, String deviceId) { + this(); + this.content = content; + this.category = category; + this.alertTime = alertTime; + this.level = level; + this.action = action; + this.actionTime = actionTime; + this.deviceId = deviceId; + } + + // Getter 和 Setter + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public LocalDateTime getAlertTime() { + return alertTime; + } + + public void setAlertTime(LocalDateTime alertTime) { + this.alertTime = alertTime; + } + + public String getLevel() { + return level; + } + + public void setLevel(String level) { + this.level = level; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public LocalDateTime getActionTime() { + return actionTime; + } + + public void setActionTime(LocalDateTime actionTime) { + this.actionTime = actionTime; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + @Override + public String toString() { + return "AlertData{" + + "id=" + id + + ", content='" + content + '\'' + + ", category='" + category + '\'' + + ", alertTime=" + alertTime + + ", level='" + level + '\'' + + ", action='" + action + '\'' + + ", actionTime=" + actionTime + + ", deviceId='" + deviceId + '\'' + + ", createTime=" + createTime + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/AlertDataRepository.java b/src/main/java/com/xzzn/movecheck/repo/AlertDataRepository.java new file mode 100644 index 0000000..6ad2a59 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/AlertDataRepository.java @@ -0,0 +1,22 @@ +package com.xzzn.movecheck.repo; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface AlertDataRepository extends JpaRepository { + + // 按创建时间倒序获取最新数据 + List findTop10ByOrderByCreateTimeDesc(); + + // 分页查询 + Page findAllByOrderByCreateTimeDesc(Pageable pageable); + // AlertDataRepository.java + Page findByCreateTimeBetween(LocalDateTime startTime, LocalDateTime endTime, Pageable pageable); + Page findByCreateTimeAfter(LocalDateTime startTime, Pageable pageable); + Page findByCreateTimeBefore(LocalDateTime endTime, Pageable pageable);} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/ClimateThreshold.java b/src/main/java/com/xzzn/movecheck/repo/ClimateThreshold.java new file mode 100644 index 0000000..fbdeb45 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/ClimateThreshold.java @@ -0,0 +1,90 @@ +// ClimateThreshold.java +package com.xzzn.movecheck.repo; + +import javax.persistence.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Entity +@Table(name = "climate_threshold") // 确保表名正确 +public class ClimateThreshold { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "setting_name", unique = true, nullable = false) + private String settingName; + + @Column(name = "min_temperature", precision = 5, scale = 2, nullable = false) + private BigDecimal minTemperature; + + @Column(name = "max_temperature", precision = 5, scale = 2, nullable = false) + private BigDecimal maxTemperature; + + @Column(name = "min_humidity", precision = 5, scale = 2, nullable = false) + private BigDecimal minHumidity; + + @Column(name = "max_humidity", precision = 5, scale = 2, nullable = false) + private BigDecimal maxHumidity; + + @Column(length = 500) + private String description; + + @Column(name = "created_time") + private LocalDateTime createdTime; + + @Column(name = "updated_time") + private LocalDateTime updatedTime; + + // 构造方法 + public ClimateThreshold() { + this.createdTime = LocalDateTime.now(); + this.updatedTime = LocalDateTime.now(); + } + + public ClimateThreshold(String settingName, BigDecimal minTemperature, + BigDecimal maxTemperature, BigDecimal minHumidity, + BigDecimal maxHumidity, String description) { + this(); + this.settingName = settingName; + this.minTemperature = minTemperature; + this.maxTemperature = maxTemperature; + this.minHumidity = minHumidity; + this.maxHumidity = maxHumidity; + this.description = description; + } + + // Getter和Setter方法 + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getSettingName() { return settingName; } + public void setSettingName(String settingName) { this.settingName = settingName; } + + public BigDecimal getMinTemperature() { return minTemperature; } + public void setMinTemperature(BigDecimal minTemperature) { this.minTemperature = minTemperature; } + + public BigDecimal getMaxTemperature() { return maxTemperature; } + public void setMaxTemperature(BigDecimal maxTemperature) { this.maxTemperature = maxTemperature; } + + public BigDecimal getMinHumidity() { return minHumidity; } + public void setMinHumidity(BigDecimal minHumidity) { this.minHumidity = minHumidity; } + + public BigDecimal getMaxHumidity() { return maxHumidity; } + public void setMaxHumidity(BigDecimal maxHumidity) { this.maxHumidity = maxHumidity; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + + public LocalDateTime getCreatedTime() { return createdTime; } + public void setCreatedTime(LocalDateTime createdTime) { this.createdTime = createdTime; } + + public LocalDateTime getUpdatedTime() { return updatedTime; } + public void setUpdatedTime(LocalDateTime updatedTime) { this.updatedTime = updatedTime; } + + @PreUpdate + public void preUpdate() { + this.updatedTime = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/ClimateThresholdRepository.java b/src/main/java/com/xzzn/movecheck/repo/ClimateThresholdRepository.java new file mode 100644 index 0000000..f90020f --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/ClimateThresholdRepository.java @@ -0,0 +1,27 @@ +// ClimateThresholdRepository.java +package com.xzzn.movecheck.repo; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; + +@Repository +public interface ClimateThresholdRepository extends JpaRepository { + + // 根据设置名称查找 + Optional findBySettingName(String settingName); + + // 检查设置名称是否存在 + boolean existsBySettingName(String settingName); + + // 根据温度范围查找 + List findByMinTemperatureLessThanEqualAndMaxTemperatureGreaterThanEqual(BigDecimal temperature, BigDecimal temperature2); + + // 自定义查询示例 + @Query("SELECT ct FROM ClimateThreshold ct WHERE ct.minTemperature <= :temp AND ct.maxTemperature >= :temp") + List findSuitableThresholdsByTemperature(BigDecimal temp); +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/EmqxClientInfo.java b/src/main/java/com/xzzn/movecheck/repo/EmqxClientInfo.java new file mode 100644 index 0000000..35c087d --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/EmqxClientInfo.java @@ -0,0 +1,46 @@ +// EmqxClientInfo.java +package com.xzzn.movecheck.repo; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class EmqxClientInfo { + @JsonProperty("clientid") + private String clientId; + + private String username; + private Boolean connected; + private Long connectedAt; + private String ipAddress; + private Integer port; + private Integer keepalive; + private Integer protoVer; + private String protoName; + + // Getter和Setter + public String getClientId() { return clientId; } + public void setClientId(String clientId) { this.clientId = clientId; } + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public Boolean getConnected() { return connected; } + public void setConnected(Boolean connected) { this.connected = connected; } + + public Long getConnectedAt() { return connectedAt; } + public void setConnectedAt(Long connectedAt) { this.connectedAt = connectedAt; } + + public String getIpAddress() { return ipAddress; } + public void setIpAddress(String ipAddress) { this.ipAddress = ipAddress; } + + public Integer getPort() { return port; } + public void setPort(Integer port) { this.port = port; } + + public Integer getKeepalive() { return keepalive; } + public void setKeepalive(Integer keepalive) { this.keepalive = keepalive; } + + public Integer getProtoVer() { return protoVer; } + public void setProtoVer(Integer protoVer) { this.protoVer = protoVer; } + + public String getProtoName() { return protoName; } + public void setProtoName(String protoName) { this.protoName = protoName; } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/EventData.java b/src/main/java/com/xzzn/movecheck/repo/EventData.java new file mode 100644 index 0000000..73fde09 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/EventData.java @@ -0,0 +1,121 @@ +package com.xzzn.movecheck.repo; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "event_data") +public class EventData { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "event_type", nullable = false) + private String eventType; // 事件类型 + + @Column(name = "event_time", nullable = false) + private LocalDateTime eventTime; // 事件时间 + + @Column(name = "status", nullable = false) + private String status; // 状态 + + @Column(name = "description") + private String description; // 事件描述 + + @Column(name = "create_time") + private LocalDateTime createTime; // 创建时间 + + @Column(name = "device_id") + private String deviceId; // 设备ID(可选) + + // 构造函数 + public EventData() { + this.createTime = LocalDateTime.now(); + } + + public EventData(String eventType, LocalDateTime eventTime, String status) { + this(); + this.eventType = eventType; + this.eventTime = eventTime; + this.status = status; + } + + public EventData(String eventType, LocalDateTime eventTime, String status, String description, String deviceId) { + this(); + this.eventType = eventType; + this.eventTime = eventTime; + this.status = status; + this.description = description; + this.deviceId = deviceId; + } + + // Getter 和 Setter + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEventType() { + return eventType; + } + + public void setEventType(String eventType) { + this.eventType = eventType; + } + + public LocalDateTime getEventTime() { + return eventTime; + } + + public void setEventTime(LocalDateTime eventTime) { + this.eventTime = eventTime; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + @Override + public String toString() { + return "EventData{" + + "id=" + id + + ", eventType='" + eventType + '\'' + + ", eventTime=" + eventTime + + ", status='" + status + '\'' + + ", description='" + description + '\'' + + ", deviceId='" + deviceId + '\'' + + ", createTime=" + createTime + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/EventDataRepository.java b/src/main/java/com/xzzn/movecheck/repo/EventDataRepository.java new file mode 100644 index 0000000..11bdc4d --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/EventDataRepository.java @@ -0,0 +1,25 @@ +package com.xzzn.movecheck.repo; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface EventDataRepository extends JpaRepository { + + // 按创建时间倒序获取最新数据 + List findTop10ByOrderByCreateTimeDesc(); + + // 分页查询 + Page findAllByOrderByCreateTimeDesc(Pageable pageable); + // EventDataRepository.java + Page findByCreateTimeBetween(LocalDateTime startTime, LocalDateTime endTime, Pageable pageable); + Page findByCreateTimeAfter(LocalDateTime startTime, Pageable pageable); + Page findByCreateTimeBefore(LocalDateTime endTime, Pageable pageable); + + +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/HealthDevice.java b/src/main/java/com/xzzn/movecheck/repo/HealthDevice.java new file mode 100644 index 0000000..e58f35d --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/HealthDevice.java @@ -0,0 +1,99 @@ +// entity/HealthDevice.java +package com.xzzn.movecheck.repo; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "health_device") +public class HealthDevice { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(name = "ac", length = 1) + private String ac; + + @Column(name = "wsd", length = 1) + private String wsd; + + @Column(name = "pm25", length = 1) + private String pm25; + + @Column(name = "created_time") + private LocalDateTime createdTime; + + @Column(name = "updated_time") + private LocalDateTime updatedTime; + + // 构造方法 + public HealthDevice() { + } + + public HealthDevice(String ac, String wsd, String pm25) { + this.ac = ac; + this.wsd = wsd; + this.pm25 = pm25; + } + + // getters and setters + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getAc() { + return ac; + } + + public void setAc(String ac) { + this.ac = ac; + } + + public String getWsd() { + return wsd; + } + + public void setWsd(String wsd) { + this.wsd = wsd; + } + + public String getPm25() { + return pm25; + } + + public void setPm25(String pm25) { + this.pm25 = pm25; + } + + public LocalDateTime getCreatedTime() { + return createdTime; + } + + public void setCreatedTime(LocalDateTime createdTime) { + this.createdTime = createdTime; + } + + public LocalDateTime getUpdatedTime() { + return updatedTime; + } + + public void setUpdatedTime(LocalDateTime updatedTime) { + this.updatedTime = updatedTime; + } + + @Override + public String toString() { + return "HealthDevice{" + + "id=" + id + + ", ac='" + ac + '\'' + + ", wsd='" + wsd + '\'' + + ", pm25='" + pm25 + '\'' + + ", createdTime=" + createdTime + + ", updatedTime=" + updatedTime + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/HealthDeviceRepository.java b/src/main/java/com/xzzn/movecheck/repo/HealthDeviceRepository.java new file mode 100644 index 0000000..2283619 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/HealthDeviceRepository.java @@ -0,0 +1,19 @@ +// repository/HealthDeviceRepository.java +package com.xzzn.movecheck.repo; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface HealthDeviceRepository extends JpaRepository { + + // 自定义查询:查找最新的记录 + @Query(value = "SELECT * FROM health_device ORDER BY created_time DESC LIMIT 1", nativeQuery = true) + List findLatestRecord(); + +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/SensorData.java b/src/main/java/com/xzzn/movecheck/repo/SensorData.java new file mode 100644 index 0000000..fa9d1e0 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/SensorData.java @@ -0,0 +1,79 @@ +package com.xzzn.movecheck.repo; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "sensor_data") +public class SensorData { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "sd_value") + private Double sd; + + @Column(name = "wd_value") + private Double wd; + @Column(name = "pm_value") + private Double pm; + + + + @Column(name = "create_time") + private LocalDateTime createTime; + + // 构造函数 + public SensorData() { + this.createTime = LocalDateTime.now(); + } + + public SensorData(Double sd, Double wd, Double pm) { + this(); + this.sd = sd; + this.wd = wd; + this.pm = pm; + + } + + // getter 和 setter 方法 + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Double getSd() { + return sd; + } + + public void setSd(Double sd) { + this.sd = sd; + } + + public Double getWd() { + return wd; + } + + public void setWd(Double wd) { + this.wd = wd; + } + + public Double getPm() { + return pm; + } + + public void setPm(Double pm) { + this.pm = pm; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/SensorDataRepository.java b/src/main/java/com/xzzn/movecheck/repo/SensorDataRepository.java new file mode 100644 index 0000000..ec52d07 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/SensorDataRepository.java @@ -0,0 +1,18 @@ +package com.xzzn.movecheck.repo; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface SensorDataRepository extends JpaRepository { + + // 查找最新的数据 + List findTop10ByOrderByCreateTimeDesc(); + + // 根据时间范围查询 + List findByCreateTimeBetween(LocalDateTime start, LocalDateTime end); +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/TemperatureHumidityData.java b/src/main/java/com/xzzn/movecheck/repo/TemperatureHumidityData.java new file mode 100644 index 0000000..3ab1756 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/TemperatureHumidityData.java @@ -0,0 +1,95 @@ +package com.xzzn.movecheck.repo; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "temperature_humidity_data") +public class TemperatureHumidityData { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "temperature", nullable = false) + private Double temperature; // 温度 + + @Column(name = "humidity", nullable = false) + private Double humidity; // 湿度 + + @Column(name = "device_id") + private String deviceId; // 设备ID + + @Column(name = "create_time") + private LocalDateTime createTime; // 创建时间 + + // 构造函数 + public TemperatureHumidityData() { + this.createTime = LocalDateTime.now(); + } + + public TemperatureHumidityData(Double temperature, Double humidity) { + this(); + this.temperature = temperature; + this.humidity = humidity; + } + + public TemperatureHumidityData(Double temperature, Double humidity, String deviceId) { + this(); + this.temperature = temperature; + this.humidity = humidity; + this.deviceId = deviceId; + } + + // Getter 和 Setter + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Double getTemperature() { + return temperature; + } + + public void setTemperature(Double temperature) { + this.temperature = temperature; + } + + public Double getHumidity() { + return humidity; + } + + public void setHumidity(Double humidity) { + this.humidity = humidity; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + @Override + public String toString() { + return "TemperatureHumidityData{" + + "id=" + id + + ", temperature=" + temperature + + ", humidity=" + humidity + + ", deviceId='" + deviceId + '\'' + + ", createTime=" + createTime + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/TemperatureHumidityRepository.java b/src/main/java/com/xzzn/movecheck/repo/TemperatureHumidityRepository.java new file mode 100644 index 0000000..6230bcc --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/TemperatureHumidityRepository.java @@ -0,0 +1,74 @@ +package com.xzzn.movecheck.repo; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Repository +public interface TemperatureHumidityRepository extends JpaRepository { + + /** + * 获取最新的温湿度数据 - 使用原生SQL查询 + */ + @Query(value = "SELECT * FROM temperature_humidity_data ORDER BY create_time DESC LIMIT 1", nativeQuery = true) + Optional findLatestData(); + + /** + * 根据设备ID获取最新数据 - 使用原生SQL查询 + */ + @Query(value = "SELECT * FROM temperature_humidity_data WHERE device_id = :deviceId ORDER BY create_time DESC LIMIT 1", nativeQuery = true) + Optional findLatestByDeviceId(@Param("deviceId") String deviceId); + + /** + * 获取最新的N条数据 - 使用Spring Data JPA方法名查询 + */ + List findTop10ByOrderByCreateTimeDesc(); + + /** + * 根据设备ID获取最新的N条数据 - 使用Spring Data JPA方法名查询 + */ + List findTop10ByDeviceIdOrderByCreateTimeDesc(String deviceId); + + /** + * 分页查询所有数据 + */ + Page findAllByOrderByCreateTimeDesc(Pageable pageable); + + /** + * 根据设备ID分页查询 + */ + Page findByDeviceIdOrderByCreateTimeDesc(String deviceId, Pageable pageable); + + /** + * 根据时间范围查询 + */ + List findByCreateTimeBetweenOrderByCreateTimeDesc(LocalDateTime startTime, LocalDateTime endTime); + + /** + * 根据设备ID和时间范围查询 + */ + List findByDeviceIdAndCreateTimeBetweenOrderByCreateTimeDesc(String deviceId, LocalDateTime startTime, LocalDateTime endTime); + + /** + * 获取所有设备ID列表 + */ + @Query("SELECT DISTINCT t.deviceId FROM TemperatureHumidityData t WHERE t.deviceId IS NOT NULL") + List findAllDeviceIds(); + + /** + * 获取第一条记录(按创建时间升序) + */ + Optional findFirstByOrderByCreateTimeAsc(); + + /** + * 获取最后一条记录(按创建时间降序) + */ + Optional findFirstByOrderByCreateTimeDesc(); +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/User.java b/src/main/java/com/xzzn/movecheck/repo/User.java new file mode 100644 index 0000000..716ec8f --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/User.java @@ -0,0 +1,71 @@ +// User.java +package com.xzzn.movecheck.repo; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "system_user") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true, nullable = false, length = 50) + private String username; + + @Column(nullable = false, length = 100) + private String password; + + @Column(length = 100) + private String nickname; + + private Boolean enabled = true; + + @Column(name = "created_time") + private LocalDateTime createdTime; + + @Column(name = "updated_time") + private LocalDateTime updatedTime; + + // 构造方法 + public User() { + this.createdTime = LocalDateTime.now(); + this.updatedTime = LocalDateTime.now(); + } + + public User(String username, String password, String nickname) { + this(); + this.username = username; + this.password = password; + this.nickname = nickname; + } + + // Getter和Setter + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + + public String getNickname() { return nickname; } + public void setNickname(String nickname) { this.nickname = nickname; } + + public Boolean getEnabled() { return enabled; } + public void setEnabled(Boolean enabled) { this.enabled = enabled; } + + public LocalDateTime getCreatedTime() { return createdTime; } + public void setCreatedTime(LocalDateTime createdTime) { this.createdTime = createdTime; } + + public LocalDateTime getUpdatedTime() { return updatedTime; } + public void setUpdatedTime(LocalDateTime updatedTime) { this.updatedTime = updatedTime; } + + @PreUpdate + public void preUpdate() { + this.updatedTime = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/repo/UserRepository.java b/src/main/java/com/xzzn/movecheck/repo/UserRepository.java new file mode 100644 index 0000000..12ae2e2 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/repo/UserRepository.java @@ -0,0 +1,23 @@ +// UserRepository.java +package com.xzzn.movecheck.repo; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + + // 根据用户名查找用户 + Optional findByUsername(String username); + + // 根据用户名和密码查找用户 + @Query("SELECT u FROM User u WHERE u.username = :username AND u.password = :password AND u.enabled = true") + Optional findByUsernameAndPassword(@Param("username") String username, @Param("password") String password); + + // 检查用户名是否存在 + boolean existsByUsername(String username); +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/service/AuthService.java b/src/main/java/com/xzzn/movecheck/service/AuthService.java new file mode 100644 index 0000000..ae414bb --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/service/AuthService.java @@ -0,0 +1,110 @@ +// AuthService.java +package com.xzzn.movecheck.service; + +import com.xzzn.movecheck.repo.User; +import com.xzzn.movecheck.repo.UserRepository; +import com.xzzn.movecheck.util.SimpleTokenUtil; +import com.xzzn.movecheck.wsd.LoginRequest; +import com.xzzn.movecheck.wsd.LoginResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Service +public class AuthService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private SimpleTokenUtil tokenUtil; + + // 内存token存储(生产环境建议用Redis) + private Map tokenStore = new HashMap<>(); + + public LoginResponse login(LoginRequest loginRequest) { + // 从数据库验证用户名密码 + Optional userOpt = userRepository.findByUsernameAndPassword( + loginRequest.getUsername(), + loginRequest.getPassword() + ); + + if (userOpt.isPresent()) { + User user = userOpt.get(); + + // 检查用户是否被禁用 + if (!user.getEnabled()) { + return new LoginResponse(false, "用户已被禁用"); + } + + // 生成token + String token = tokenUtil.generateToken(user.getUsername()); + + // 存储token + tokenStore.put(token, user.getUsername()); + + // 使用正确的构造函数 + return new LoginResponse( + true, + "登录成功", + token, + user.getUsername(), + user.getNickname() != null ? user.getNickname() : user.getUsername() // 确保nickname不为null + ); + } else { + return new LoginResponse(false, "用户名或密码错误"); + } + } + + public boolean validateToken(String token) { + if (token == null || !tokenStore.containsKey(token)) { + return false; + } + return tokenUtil.validateToken(token); + } + + public String getUsernameFromToken(String token) { + return tokenStore.get(token); + } + + // 获取当前登录用户信息 + public Optional getCurrentUser(String token) { + String username = getUsernameFromToken(token); + if (username != null) { + return userRepository.findByUsername(username); + } + return Optional.empty(); + } + + // 登出 + public void logout(String token) { + tokenStore.remove(token); + } + + // 获取所有用户(管理功能) + public java.util.List getAllUsers() { + return userRepository.findAll(); + } + + // 创建用户(管理功能) + public User createUser(User user) { + if (userRepository.existsByUsername(user.getUsername())) { + throw new RuntimeException("用户名已存在"); + } + return userRepository.save(user); + } + + // 更新用户(管理功能) + public User updateUser(Long id, User userDetails) { + User user = userRepository.findById(id) + .orElseThrow(() -> new RuntimeException("用户不存在")); + + user.setNickname(userDetails.getNickname()); + user.setEnabled(userDetails.getEnabled()); + + return userRepository.save(user); + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/service/ClimateThresholdService.java b/src/main/java/com/xzzn/movecheck/service/ClimateThresholdService.java new file mode 100644 index 0000000..113272d --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/service/ClimateThresholdService.java @@ -0,0 +1,96 @@ +// ClimateThresholdService.java +package com.xzzn.movecheck.service; + +import com.xzzn.movecheck.repo.ClimateThreshold; +import com.xzzn.movecheck.repo.ClimateThresholdRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; + +@Service +@Transactional +public class ClimateThresholdService { + + @Autowired + private ClimateThresholdRepository thresholdRepository; + + // 保存或更新阈值设置 + public ClimateThreshold saveThreshold(ClimateThreshold threshold) { + validateThreshold(threshold); + return thresholdRepository.save(threshold); + } + + // 根据ID查找 + public Optional findById(Long id) { + return thresholdRepository.findById(id); + } + + // 根据名称查找 + public Optional findBySettingName(String settingName) { + return thresholdRepository.findBySettingName(settingName); + } + + // 获取所有设置 + public List findAll() { + return thresholdRepository.findAll(); + } + + // 删除设置 + public void deleteById(Long id) { + thresholdRepository.deleteById(id); + } + + // 验证阈值范围 + private void validateThreshold(ClimateThreshold threshold) { + if (threshold.getMinTemperature().compareTo(threshold.getMaxTemperature()) >= 0) { + throw new IllegalArgumentException("最小温度不能大于等于最大温度"); + } + + if (threshold.getMinHumidity().compareTo(threshold.getMaxHumidity()) >= 0) { + throw new IllegalArgumentException("最小湿度不能大于等于最大湿度"); + } + + if (threshold.getMinHumidity().compareTo(BigDecimal.ZERO) < 0 || + threshold.getMaxHumidity().compareTo(new BigDecimal("100")) > 0) { + throw new IllegalArgumentException("湿度范围应在0-100之间"); + } + } + + // 检查当前环境状态 + public String checkEnvironmentStatus(String settingName, BigDecimal currentTemp, BigDecimal currentHumidity) { + Optional thresholdOpt = thresholdRepository.findBySettingName(settingName); + + if (thresholdOpt == null) { + return "未找到对应的阈值设置: " + settingName; + } + + ClimateThreshold threshold = thresholdOpt.get(); + StringBuilder status = new StringBuilder(); + + // 检查温度 + if (currentTemp.compareTo(threshold.getMinTemperature()) < 0) { + status.append("温度过低"); + } else if (currentTemp.compareTo(threshold.getMaxTemperature()) > 0) { + status.append("温度过高"); + } else { + status.append("温度正常"); + } + + status.append(" | "); + + // 检查湿度 + if (currentHumidity.compareTo(threshold.getMinHumidity()) < 0) { + status.append("湿度过低"); + } else if (currentHumidity.compareTo(threshold.getMaxHumidity()) > 0) { + status.append("湿度过高"); + } else { + status.append("湿度正常"); + } + + return status.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/service/DataProcessingService.java b/src/main/java/com/xzzn/movecheck/service/DataProcessingService.java new file mode 100644 index 0000000..cbd61b0 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/service/DataProcessingService.java @@ -0,0 +1,125 @@ +// DataProcessingService.java +package com.xzzn.movecheck.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.xzzn.movecheck.repo.HealthDevice; +import com.xzzn.movecheck.repo.HealthDeviceRepository; +import com.xzzn.movecheck.repo.SensorData; +import com.xzzn.movecheck.repo.SensorDataRepository; +import org.eclipse.paho.client.mqttv3.IMqttMessageListener; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +@Service +public class DataProcessingService implements IMqttMessageListener { + private static final Logger logger = LoggerFactory.getLogger(DataProcessingService.class); + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @Autowired + private HealthDeviceRepository healthDeviceRepository; + + @Autowired + private SensorDataRepository sensorDataRepository; + + @Override + public void messageArrived(String topic, MqttMessage message) { + String payload = new String(message.getPayload()); + logger.info("收到MQTT消息 - Topic: {}, QoS: {}", topic, message.getQos()); + + try { + switch (topic) { + case "HDYDCJ_01_DEVICE": + processDeviceHealthData(payload); + break; + case "HDYDCJ_01_UP": + processSensorData(payload); + break; + default: + logger.warn("未知的Topic: {}", topic); + } + } catch (Exception e) { + logger.error("处理MQTT消息失败 - Topic: {}, Error: {}", topic, e.getMessage()); + // 可以根据需要在这里添加重试逻辑或错误处理 + } + } + + /** + * 处理设备健康数据 HDYDCJ_01_DEVICE + */ + private void processDeviceHealthData(String payload) { + logger.debug("开始处理设备健康数据: {}", payload); + + JSONObject deviceHealthData = JSON.parseObject(payload); + String wsd = String.valueOf(deviceHealthData.get("WSD")); + String pm25 = String.valueOf(deviceHealthData.get("PM25")); + String ac = String.valueOf(deviceHealthData.get("AC")); + + HealthDevice healthDevice = new HealthDevice(); + healthDevice.setId(1); + healthDevice.setAc(ac); + healthDevice.setWsd(wsd); + healthDevice.setPm25(pm25); + healthDeviceRepository.save(healthDevice); + } + + /** + * 处理传感器数据 HDYDCJ_01_UP + */ + private void processSensorData(String payload) { + logger.debug("开始处理传感器数据: {}", payload); + + // 注意:这里payload是数组格式 + try { + JsonNode rootNode = objectMapper.readTree(payload); + + // 读取顶层字段 + String device = rootNode.get("Device").asText(); + long timestamp = rootNode.get("timestamp").asLong(); + + // 读取Data对象中的字段 + JsonNode dataNode = rootNode.get("Data"); + double pm10 = dataNode.get("PM10").asDouble(); + double pm25 = dataNode.get("PM25").asDouble(); + double wd = dataNode.get("WD").asDouble(); + double sd = dataNode.get("SD").asDouble(); + + System.out.println("Device: " + device); + System.out.println("Timestamp: " + timestamp); + System.out.println("PM10: " + pm10); + System.out.println("PM25: " + pm25); + System.out.println("WD: " + wd); + System.out.println("SD: " + sd); + + List list = sensorDataRepository.findTop10ByOrderByCreateTimeDesc(); + SensorData entity = new SensorData(); + entity.setWd(wd); + entity.setPm(pm25); + entity.setSd(sd); + if (BigDecimal.valueOf(wd).compareTo(BigDecimal.ZERO) == 0) { + entity.setWd(list.get(0).getWd()); + } + if (BigDecimal.valueOf(pm25).compareTo(BigDecimal.ZERO) == 0) { + entity.setPm(list.get(0).getPm()); + } + if (BigDecimal.valueOf(sd).compareTo(BigDecimal.ZERO) == 0) { + entity.setSd(list.get(0).getSd()); + } + sensorDataRepository.save(entity); + + } catch (Exception e) { + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/service/EmqxClientService.java b/src/main/java/com/xzzn/movecheck/service/EmqxClientService.java new file mode 100644 index 0000000..8a7bd9e --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/service/EmqxClientService.java @@ -0,0 +1,193 @@ +// service/EmqxClientService.java +package com.xzzn.movecheck.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.xzzn.movecheck.wsd.ClientInfo; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.io.IOException; +import java.util.Base64; +import java.util.Date; + +@Service +public class EmqxClientService { + + private static final Logger logger = LoggerFactory.getLogger(EmqxClientService.class); + + private String username = "f080a5e8e41e10bf"; + + private String password="ynAnM9BXWDqgwmx672D2MxHNaal6ZmpcCEgpnPx0ludL"; + + private String baseUrl="http://122.51.194.184:18083"; + + private final CloseableHttpClient httpClient; + private final ObjectMapper objectMapper; + + public EmqxClientService(CloseableHttpClient httpClient, ObjectMapper objectMapper) { + this.httpClient = httpClient; + this.objectMapper = objectMapper; + + } + + @PostConstruct + public void init() { + logger.info("EMQX API Service initialized with base URL: {}", baseUrl); + } + + @PreDestroy + public void destroy() { + try { + if (httpClient != null) { + httpClient.close(); + } + } catch (IOException e) { + logger.error("Error closing HTTP client", e); + } + } + + /** + * 获取指定客户端信息 + */ + public ClientInfo getClientInfo(String clientId) { + String url = baseUrl + "/api/v5/clients/" + clientId; + logger.info("Requesting client info from: {}", url); + + String auth = username + ":" + password; + String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes()); + String authHeader = "Basic " + encodedAuth; + + HttpGet request = new HttpGet(url); + request.setHeader("Authorization", authHeader); + try { + HttpResponse response = httpClient.execute(request); + int statusCode = response.getStatusLine().getStatusCode(); + String responseBody = EntityUtils.toString(response.getEntity()); + + logger.debug("Response status: {}, body: {}", statusCode, responseBody); + + if (statusCode == 200) { + return objectMapper.readValue(responseBody, ClientInfo.class); + } else if (statusCode == 404) { + throw new RuntimeException("客户端不存在: " + clientId); + } else { + throw new RuntimeException("API 请求失败,状态码: " + statusCode + ", 响应: " + responseBody); + } + + } catch (Exception e) { + logger.error("获取客户端信息失败: {}", e.getMessage(), e); + throw new RuntimeException("获取客户端信息失败: " + e.getMessage(), e); + } finally { + request.releaseConnection(); + } + } + + /** + * 获取所有客户端列表 + */ + public String getAllClients() { + String url = baseUrl + "/api/v5/clients"; + logger.info("Requesting all clients from: {}", url); + + + String auth = username + ":" + password; + String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes()); + String authHeader = "Basic " + encodedAuth; + + HttpGet request = new HttpGet(url); + request.setHeader("Authorization", authHeader); + + try { + HttpResponse response = httpClient.execute(request); + int statusCode = response.getStatusLine().getStatusCode(); + String responseBody = EntityUtils.toString(response.getEntity()); + + if (statusCode == 200) { + return responseBody; + } else { + throw new RuntimeException("获取客户端列表失败,状态码: " + statusCode + ", 响应: " + responseBody); + } + + } catch (Exception e) { + logger.error("获取客户端列表失败: {}", e.getMessage(), e); + throw new RuntimeException("获取客户端列表失败: " + e.getMessage(), e); + } finally { + request.releaseConnection(); + } + } + + /** + * 检查客户端是否在线 + */ + public boolean isClientOnline(String clientId) { + try { + ClientInfo clientInfo = getClientInfo(clientId); + return clientInfo.getConnected() != null && clientInfo.getConnected(); + } catch (Exception e) { + logger.warn("检查客户端在线状态失败: {}", e.getMessage()); + return false; + } + } + + /** + * 获取客户端连接状态详情 + */ + public ClientStatusResponse getClientStatus(String clientId) { + try { + ClientInfo clientInfo = getClientInfo(clientId); + return new ClientStatusResponse(clientId, + clientInfo.getConnected() != null && clientInfo.getConnected(), + clientInfo.getIpAddress(), + clientInfo.getConnectedAt(), + clientInfo.getDisconnectedAt()); + } catch (Exception e) { + throw new RuntimeException("获取客户端状态失败: " + e.getMessage(), e); + } + } + + // 内部状态响应类 + public static class ClientStatusResponse { + private String clientId; + private boolean online; + private String ipAddress; + private Date connectedAt; + private Date disconnectedAt; + + public ClientStatusResponse(String clientId, boolean online, String ipAddress, + Date connectedAt, Date disconnectedAt) { + this.clientId = clientId; + this.online = online; + this.ipAddress = ipAddress; + this.connectedAt = connectedAt; + this.disconnectedAt = disconnectedAt; + } + + // getters and setters + public String getClientId() { return clientId; } + public void setClientId(String clientId) { this.clientId = clientId; } + + public boolean isOnline() { return online; } + public void setOnline(boolean online) { this.online = online; } + + public String getIpAddress() { return ipAddress; } + public void setIpAddress(String ipAddress) { this.ipAddress = ipAddress; } + + public Date getConnectedAt() { return connectedAt; } + public void setConnectedAt(Date connectedAt) { this.connectedAt = connectedAt; } + + public Date getDisconnectedAt() { return disconnectedAt; } + public void setDisconnectedAt(Date disconnectedAt) { this.disconnectedAt = disconnectedAt; } + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/service/EventAlertService.java b/src/main/java/com/xzzn/movecheck/service/EventAlertService.java new file mode 100644 index 0000000..a37a986 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/service/EventAlertService.java @@ -0,0 +1,152 @@ +package com.xzzn.movecheck.service; + +import com.xzzn.movecheck.repo.AlertData; +import com.xzzn.movecheck.repo.AlertDataRepository; +import com.xzzn.movecheck.repo.EventData; +import com.xzzn.movecheck.repo.EventDataRepository; +import com.xzzn.movecheck.wsd.AlertRequest; +import com.xzzn.movecheck.wsd.EventRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class EventAlertService { + + @Autowired + private EventDataRepository eventDataRepository; + + @Autowired + private AlertDataRepository alertDataRepository; + + // 事件相关方法 + + /** + * 保存事件数据 + */ + public Map saveEvent(EventRequest request) { + try { + EventData eventData = new EventData( + request.getEventType(), + request.getEventTime(), + request.getStatus(), + request.getDescription(), + request.getDeviceId() + ); + + EventData saved = eventDataRepository.save(eventData); + + Map result = new HashMap<>(); + result.put("status", "success"); + result.put("message", "事件保存成功"); + result.put("id", saved.getId()); + return result; + + } catch (Exception e) { + Map result = new HashMap<>(); + result.put("status", "error"); + result.put("message", "事件保存失败: " + e.getMessage()); + return result; + } + } + + /** + * 获取最新10条事件 + */ + public List getLatestEvents() { + return eventDataRepository.findTop10ByOrderByCreateTimeDesc(); + } + + /** + * 分页获取事件 + */ + public Page getEventsByPage(int page, int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createTime")); + return eventDataRepository.findAllByOrderByCreateTimeDesc(pageable); + } + + // 告警相关方法 + + /** + * 保存告警数据 + */ + public Map saveAlert(AlertRequest request) { + try { + AlertData alertData = new AlertData( + request.getContent(), + request.getCategory(), + request.getAlertTime(), + request.getLevel(), + request.getAction(), + request.getActionTime(), + request.getDeviceId() + ); + + AlertData saved = alertDataRepository.save(alertData); + + Map result = new HashMap<>(); + result.put("status", "success"); + result.put("message", "告警保存成功"); + result.put("id", saved.getId()); + return result; + + } catch (Exception e) { + Map result = new HashMap<>(); + result.put("status", "error"); + result.put("message", "告警保存失败: " + e.getMessage()); + return result; + } + } + // 在 EventAlertService 类中需要添加的方法签名 + + public Page getEventsByCreateTime(LocalDateTime startTime, LocalDateTime endTime, int page, int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createTime")); + + if (startTime != null && endTime != null) { + return eventDataRepository.findByCreateTimeBetween(startTime, endTime, pageable); + } else if (startTime != null) { + return eventDataRepository.findByCreateTimeAfter(startTime, pageable); + } else if (endTime != null) { + return eventDataRepository.findByCreateTimeBefore(endTime, pageable); + } else { + return eventDataRepository.findAllByOrderByCreateTimeDesc(pageable); + } + } + + public Page getAlertsByCreateTime(LocalDateTime startTime, LocalDateTime endTime, int page, int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createTime")); + + if (startTime != null && endTime != null) { + return alertDataRepository.findByCreateTimeBetween(startTime, endTime, pageable); + } else if (startTime != null) { + return alertDataRepository.findByCreateTimeAfter(startTime, pageable); + } else if (endTime != null) { + return alertDataRepository.findByCreateTimeBefore(endTime, pageable); + } else { + return alertDataRepository.findAllByOrderByCreateTimeDesc(pageable); + } + } + + /** + * 获取最新10条告警 + */ + public List getLatestAlerts() { + return alertDataRepository.findTop10ByOrderByCreateTimeDesc(); + } + + /** + * 分页获取告警 + */ + public Page getAlertsByPage(int page, int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createTime")); + return alertDataRepository.findAllByOrderByCreateTimeDesc(pageable); + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/service/HealthDeviceService.java b/src/main/java/com/xzzn/movecheck/service/HealthDeviceService.java new file mode 100644 index 0000000..fdc5e7c --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/service/HealthDeviceService.java @@ -0,0 +1,24 @@ +// service/HealthDeviceService.java +package com.xzzn.movecheck.service; + + +import com.xzzn.movecheck.repo.HealthDevice; +import com.xzzn.movecheck.repo.HealthDeviceRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Service +public class HealthDeviceService { + + @Autowired + private HealthDeviceRepository healthDeviceRepository; + + // 获取最新记录 + public List findLatestRecord() { + return healthDeviceRepository.findLatestRecord(); + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/service/MqttMessageHandler.java b/src/main/java/com/xzzn/movecheck/service/MqttMessageHandler.java new file mode 100644 index 0000000..b2f9ef2 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/service/MqttMessageHandler.java @@ -0,0 +1,143 @@ +package com.xzzn.movecheck.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.xzzn.movecheck.repo.HealthDevice; +import com.xzzn.movecheck.repo.HealthDeviceRepository; +import com.xzzn.movecheck.repo.SensorData; +import com.xzzn.movecheck.repo.SensorDataRepository; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; + +@Component +public class MqttMessageHandler implements MqttCallback { + private static final Logger logger = LoggerFactory.getLogger(DataProcessingService.class); + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + + @Autowired + private HealthDeviceRepository healthDeviceRepository; + + @Autowired + private SensorDataRepository sensorDataRepository; + /** + * 接收到消息时的回调 + */ + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + String payload = new String(message.getPayload()); + System.out.println("收到消息 - Topic: " + topic + ", QoS: " + message.getQos()); + + try { + if ("HDYDCJ_01_UP".equals(topic)) { + processSensorData(payload); + } else if ("HDYDCJ_01_DEVICE".equals(topic)) { + processDeviceData(payload); + } + } catch (Exception e) { + System.err.println("处理MQTT消息失败: " + e.getMessage()); + } + } + + /** + * 连接丢失时的回调 + */ + @Override + public void connectionLost(Throwable cause) { + System.err.println("MQTT连接丢失: " + cause.getMessage()); + // 可以在这里添加重连逻辑 + } + + /** + * 消息传递完成时的回调 + */ + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + // 用于发布消息时的回调,订阅场景下一般不需要处理 + System.out.println("消息传递完成"); + } + + private void processSensorData(String payload) throws Exception { + logger.debug("开始处理数据: {}", payload); + JSONArray jsonArray = JSONArray.parseArray(payload); + + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject item = jsonArray.getJSONObject(i); + + // 获取Device类型 + String device = item.getString("Device"); + Long timestamp = item.getLong("timestamp"); + + // 如果是PM25设备 + if ("PM25".equals(device)) { + + List list = sensorDataRepository.findTop10ByOrderByCreateTimeDesc(); + + JSONObject data = item.getJSONObject("Data"); + + // 获取PM25值,如果为null则转为0 + Double pm25Value = data.getDouble("PM25"); + if (pm25Value == null) { + pm25Value = list.get(0).getPm(); + } + + // 获取SD值,如果为null则转为0.0 + Double sdValue = data.getDouble("SD"); + if (sdValue == null) { + sdValue = list.get(0).getSd(); + } + + // 获取WD值,如果为null则转为0.0 + Double wdValue = data.getDouble("WD"); + if (wdValue == null) { + wdValue = list.get(0).getWd(); + } + + SensorData entity = new SensorData(); + entity.setWd(wdValue); + entity.setPm(pm25Value); + entity.setSd(sdValue); + + sensorDataRepository.save(entity); + } + } + } + + private void processDeviceData(String payload) { + logger.debug("开始处理设备健康数据: {}", payload); + + JSONObject deviceHealthData = JSON.parseObject(payload); + JSONObject wsdObject = deviceHealthData.getJSONObject("WSD"); + String wsdHealth = String.valueOf(wsdObject.getInteger("health")); + + JSONObject acObject = deviceHealthData.getJSONObject("AC"); + String acHealth = String.valueOf(acObject.getInteger("health")); + + JSONObject pm25Object = deviceHealthData.getJSONObject("PM25"); + String pm25Health = String.valueOf(pm25Object.getInteger("health")); + + HealthDevice healthDevice = new HealthDevice(); + healthDevice.setId(1); + healthDevice.setAc(acHealth); + healthDevice.setWsd(wsdHealth); + healthDevice.setPm25(pm25Health); + healthDevice.setCreatedTime(LocalDateTime.now()); + healthDevice.setUpdatedTime(LocalDateTime.now()); + healthDeviceRepository.save(healthDevice); + + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/service/TemperatureHumidityService.java b/src/main/java/com/xzzn/movecheck/service/TemperatureHumidityService.java new file mode 100644 index 0000000..d89405c --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/service/TemperatureHumidityService.java @@ -0,0 +1,148 @@ +package com.xzzn.movecheck.service; + +import com.xzzn.movecheck.repo.TemperatureHumidityData; +import com.xzzn.movecheck.repo.TemperatureHumidityRepository; +import com.xzzn.movecheck.wsd.TemperatureHumidityRequest; +import com.xzzn.movecheck.wsd.TemperatureHumidityResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class TemperatureHumidityService { + + @Autowired + private TemperatureHumidityRepository repository; + + /** + * 保存温湿度数据 + */ + public TemperatureHumidityResponse saveData(TemperatureHumidityRequest request) { + try { + TemperatureHumidityData data = new TemperatureHumidityData( + request.getTemperature(), + request.getHumidity(), + request.getDeviceId() + ); + + TemperatureHumidityData saved = repository.save(data); + + return TemperatureHumidityResponse.success("温湿度数据保存成功"); + + } catch (Exception e) { + return TemperatureHumidityResponse.error("温湿度数据保存失败: " + e.getMessage()); + } + } + + /** + * 获取最新温湿度数据 + */ + public TemperatureHumidityResponse getLatestData() { + try { + Optional dataOpt = repository.findLatestData(); + + if (dataOpt.isPresent()) { + return TemperatureHumidityResponse.fromEntity(dataOpt.get()); + } else { + return TemperatureHumidityResponse.error("暂无温湿度数据"); + } + + } catch (Exception e) { + return TemperatureHumidityResponse.error("获取温湿度数据失败: " + e.getMessage()); + } + } + + /** + * 根据设备ID获取最新数据 + */ + public TemperatureHumidityResponse getLatestByDevice(String deviceId) { + try { + Optional dataOpt = repository.findLatestByDeviceId(deviceId); + + if (dataOpt.isPresent()) { + return TemperatureHumidityResponse.fromEntity(dataOpt.get()); + } else { + return TemperatureHumidityResponse.error("未找到设备 " + deviceId + " 的温湿度数据"); + } + + } catch (Exception e) { + return TemperatureHumidityResponse.error("获取设备温湿度数据失败: " + e.getMessage()); + } + } + + /** + * 获取最新10条数据 + */ + public TemperatureHumidityResponse getLatest10Data() { + try { + List dataList = repository.findTop10ByOrderByCreateTimeDesc(); + + TemperatureHumidityResponse response = new TemperatureHumidityResponse(); + response.setStatus("success"); + response.setMessage("获取成功"); + response.setDataList(dataList); + response.setTotal((long) dataList.size()); + + return response; + + } catch (Exception e) { + return TemperatureHumidityResponse.error("获取温湿度数据列表失败: " + e.getMessage()); + } + } + + /** + * 根据设备ID获取最新10条数据 + */ + public TemperatureHumidityResponse getLatest10ByDevice(String deviceId) { + try { + List dataList = repository.findTop10ByDeviceIdOrderByCreateTimeDesc(deviceId); + + TemperatureHumidityResponse response = new TemperatureHumidityResponse(); + response.setStatus("success"); + response.setMessage("获取成功"); + response.setDataList(dataList); + response.setTotal((long) dataList.size()); + + return response; + + } catch (Exception e) { + return TemperatureHumidityResponse.error("获取设备温湿度数据列表失败: " + e.getMessage()); + } + } + + /** + * 分页查询数据 + */ + public Page getDataByPage(int page, int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createTime")); + return repository.findAllByOrderByCreateTimeDesc(pageable); + } + + /** + * 根据设备ID分页查询 + */ + public Page getDataByDeviceAndPage(String deviceId, int page, int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createTime")); + return repository.findByDeviceIdOrderByCreateTimeDesc(deviceId, pageable); + } + + /** + * 获取所有设备ID列表 + */ + public List getAllDeviceIds() { + return repository.findAllDeviceIds(); + } + + /** + * 获取数据总数 + */ + public long getTotalCount() { + return repository.count(); + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/util/SimpleTokenUtil.java b/src/main/java/com/xzzn/movecheck/util/SimpleTokenUtil.java new file mode 100644 index 0000000..140e6f7 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/util/SimpleTokenUtil.java @@ -0,0 +1,39 @@ +// SimpleTokenUtil.java +package com.xzzn.movecheck.util; + +import org.springframework.stereotype.Component; + +import java.util.Base64; +import java.util.UUID; + +@Component +public class SimpleTokenUtil { + + // 生成简单的token(实际项目中应该更复杂) + public String generateToken(String username) { + String rawToken = username + ":" + System.currentTimeMillis() + ":" + UUID.randomUUID(); + return Base64.getEncoder().encodeToString(rawToken.getBytes()); + } + + // 验证token(这里简单验证,实际项目需要更严格的验证) + public boolean validateToken(String token) { + try { + String decoded = new String(Base64.getDecoder().decode(token)); + String[] parts = decoded.split(":"); + return parts.length == 3; // 简单的格式验证 + } catch (Exception e) { + return false; + } + } + + // 从token中获取用户名 + public String getUsernameFromToken(String token) { + try { + String decoded = new String(Base64.getDecoder().decode(token)); + String[] parts = decoded.split(":"); + return parts.length >= 1 ? parts[0] : null; + } catch (Exception e) { + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/AlertRequest.java b/src/main/java/com/xzzn/movecheck/wsd/AlertRequest.java new file mode 100644 index 0000000..75c19ef --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/AlertRequest.java @@ -0,0 +1,81 @@ +package com.xzzn.movecheck.wsd; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.time.LocalDateTime; + +public class AlertRequest { + + private String content; + + private String category; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime alertTime; + + private String level; + + private String action; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime actionTime; + + private String deviceId; + + // Getter 和 Setter + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public LocalDateTime getAlertTime() { + return alertTime; + } + + public void setAlertTime(LocalDateTime alertTime) { + this.alertTime = alertTime; + } + + public String getLevel() { + return level; + } + + public void setLevel(String level) { + this.level = level; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public LocalDateTime getActionTime() { + return actionTime; + } + + public void setActionTime(LocalDateTime actionTime) { + this.actionTime = actionTime; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/AuthController.java b/src/main/java/com/xzzn/movecheck/wsd/AuthController.java new file mode 100644 index 0000000..ea0a79d --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/AuthController.java @@ -0,0 +1,69 @@ +// AuthController.java +package com.xzzn.movecheck.wsd; + +import com.xzzn.movecheck.repo.User; +import com.xzzn.movecheck.service.AuthService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/auth") +@CrossOrigin(origins = "*", maxAge = 3600) +public class AuthController { + + @Autowired + private AuthService authService; + + @PostMapping("/login") + public LoginResponse login(@RequestBody LoginRequest loginRequest) { + return authService.login(loginRequest); + } + + @PostMapping("/validate") + public LoginResponse validateToken(@RequestHeader(value = "Authorization", required = false) String authHeader) { + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return new LoginResponse(false, "缺少有效的token"); + } + + String token = authHeader.substring(7); + boolean isValid = authService.validateToken(token); + + if (isValid) { + Optional userOpt = authService.getCurrentUser(token); + String nickname = userOpt.map(User::getNickname).orElse(""); + return new LoginResponse(true, "Token有效", token, userOpt.map(User::getUsername).orElse(""), nickname); + } else { + return new LoginResponse(false, "Token无效或已过期"); + } + } + + @PostMapping("/logout") + public LoginResponse logout(@RequestHeader(value = "Authorization", required = false) String authHeader) { + if (authHeader != null && authHeader.startsWith("Bearer ")) { + String token = authHeader.substring(7); + authService.logout(token); + } + return new LoginResponse(true, "登出成功"); + } + + // 获取当前用户信息 + @GetMapping("/me") + public LoginResponse getCurrentUser(@RequestHeader(value = "Authorization", required = false) String authHeader) { + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return new LoginResponse(false, "未登录"); + } + + String token = authHeader.substring(7); + Optional userOpt = authService.getCurrentUser(token); + + if (userOpt.isPresent()) { + User user = userOpt.get(); + return new LoginResponse(true, "获取成功", token, user.getUsername(), user.getNickname()); + } else { + return new LoginResponse(false, "用户不存在"); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/ClientController.java b/src/main/java/com/xzzn/movecheck/wsd/ClientController.java new file mode 100644 index 0000000..7223e70 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/ClientController.java @@ -0,0 +1,149 @@ +// controller/ClientController.java +package com.xzzn.movecheck.wsd; + +import com.xzzn.movecheck.service.EmqxClientService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/emqx") +@CrossOrigin(origins = "*") // 允许跨域访问 +public class ClientController { + + private static final Logger logger = LoggerFactory.getLogger(ClientController.class); + + private final EmqxClientService emqxClientService; + + public ClientController(EmqxClientService emqxClientService) { + this.emqxClientService = emqxClientService; + } + + /** + * 获取客户端详细信息 + * GET /api/emqx/clients/{clientId} + */ + @GetMapping("/clients/{clientId}") + public ResponseEntity getClientInfo(@PathVariable String clientId) { + try { + logger.info("收到获取客户端信息请求: {}", clientId); + ClientInfo clientInfo = emqxClientService.getClientInfo(clientId); + return ResponseEntity.ok(clientInfo); + } catch (Exception e) { + logger.error("获取客户端信息失败: {}", e.getMessage(), e); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ErrorResponse("GET_CLIENT_INFO_FAILED", e.getMessage())); + } + } + + /** + * 检查客户端在线状态 + * GET /api/emqx/clients/{clientId}/status + */ + @GetMapping("/clients/{clientId}/status") + public ResponseEntity getClientStatus(@PathVariable String clientId) { + try { + logger.info("收到检查客户端状态请求: {}", clientId); + boolean isOnline = emqxClientService.isClientOnline(clientId); + return ResponseEntity.ok(new StatusResponse(clientId, isOnline)); + } catch (Exception e) { + logger.error("检查客户端状态失败: {}", e.getMessage(), e); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ErrorResponse("CHECK_CLIENT_STATUS_FAILED", e.getMessage())); + } + } + + /** + * 获取客户端详细状态 + * GET /api/emqx/clients/{clientId}/detail-status + */ + @GetMapping("/clients/{clientId}/detail-status") + public ResponseEntity getClientDetailStatus(@PathVariable String clientId) { + try { + logger.info("收到获取客户端详细状态请求: {}", clientId); + EmqxClientService.ClientStatusResponse status = emqxClientService.getClientStatus(clientId); + return ResponseEntity.ok(status); + } catch (Exception e) { + logger.error("获取客户端详细状态失败: {}", e.getMessage(), e); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ErrorResponse("GET_CLIENT_DETAIL_STATUS_FAILED", e.getMessage())); + } + } + + /** + * 获取所有客户端列表 + * GET /api/emqx/clients + */ + @GetMapping("/clients") + public ResponseEntity getAllClients() { + try { + logger.info("收到获取所有客户端列表请求"); + String clients = emqxClientService.getAllClients(); + return ResponseEntity.ok(clients); + } catch (Exception e) { + logger.error("获取客户端列表失败: {}", e.getMessage(), e); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ErrorResponse("GET_ALL_CLIENTS_FAILED", e.getMessage())); + } + } + + /** + * 健康检查接口 + * GET /api/emqx/health + */ + @GetMapping("/health") + public ResponseEntity healthCheck() { + return ResponseEntity.ok(new HealthResponse("OK", "EMQX API Service is running")); + } + + // 响应包装类 + public static class StatusResponse { + private String clientId; + private boolean online; + + public StatusResponse(String clientId, boolean online) { + this.clientId = clientId; + this.online = online; + } + + public String getClientId() { return clientId; } + public void setClientId(String clientId) { this.clientId = clientId; } + + public boolean isOnline() { return online; } + public void setOnline(boolean online) { this.online = online; } + } + + public static class ErrorResponse { + private String error; + private String message; + + public ErrorResponse(String error, String message) { + this.error = error; + this.message = message; + } + + public String getError() { return error; } + public void setError(String error) { this.error = error; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + } + + public static class HealthResponse { + private String status; + private String message; + + public HealthResponse(String status, String message) { + this.status = status; + this.message = message; + } + + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/ClientInfo.java b/src/main/java/com/xzzn/movecheck/wsd/ClientInfo.java new file mode 100644 index 0000000..1ef8e42 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/ClientInfo.java @@ -0,0 +1,57 @@ +// model/ClientInfo.java +package com.xzzn.movecheck.wsd; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Date; + +public class ClientInfo { + @JsonProperty("clientid") + private String clientId; + + @JsonProperty("username") + private String username; + + @JsonProperty("connected") + private Boolean connected; + + @JsonProperty("ip_address") + private String ipAddress; + + @JsonProperty("connected_at") + private Date connectedAt; + + @JsonProperty("disconnected_at") + private Date disconnectedAt; + + // getters and setters + public String getClientId() { return clientId; } + public void setClientId(String clientId) { this.clientId = clientId; } + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public Boolean getConnected() { return connected; } + public void setConnected(Boolean connected) { this.connected = connected; } + + public String getIpAddress() { return ipAddress; } + public void setIpAddress(String ipAddress) { this.ipAddress = ipAddress; } + + public Date getConnectedAt() { return connectedAt; } + public void setConnectedAt(Date connectedAt) { this.connectedAt = connectedAt; } + + public Date getDisconnectedAt() { return disconnectedAt; } + public void setDisconnectedAt(Date disconnectedAt) { this.disconnectedAt = disconnectedAt; } + + @Override + public String toString() { + return "ClientInfo{" + + "clientId='" + clientId + '\'' + + ", username='" + username + '\'' + + ", connected=" + connected + + ", ipAddress='" + ipAddress + '\'' + + ", connectedAt=" + connectedAt + + ", disconnectedAt=" + disconnectedAt + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/ClimateThresholdController.java b/src/main/java/com/xzzn/movecheck/wsd/ClimateThresholdController.java new file mode 100644 index 0000000..5fb66d6 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/ClimateThresholdController.java @@ -0,0 +1,88 @@ +package com.xzzn.movecheck.wsd;// ClimateThresholdController.java + +import com.xzzn.movecheck.repo.ClimateThreshold; +import com.xzzn.movecheck.service.ClimateThresholdService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/wsd") +public class ClimateThresholdController { + + private static final Logger logger = LoggerFactory.getLogger(ClimateThresholdController.class); + + @Autowired + private ClimateThresholdService thresholdService; + + // 创建新的阈值设置 + @PostMapping + public ResponseEntity createThreshold(@RequestBody ClimateThreshold threshold) { + try { + ClimateThreshold saved = thresholdService.saveThreshold(threshold); + return ResponseEntity.ok(saved); + } catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + + // 获取所有阈值设置 + @GetMapping + public List getAllThresholds() { + return thresholdService.findAll(); + } + + // 根据ID获取阈值设置 + @GetMapping("/{id}") + public ResponseEntity getThresholdById(@PathVariable Long id) { + Optional threshold = thresholdService.findById(id); + + logger.info("getThresholdById id: {}, threshold: {}", id, threshold); + return threshold.map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + // 根据名称获取阈值设置 + @GetMapping("/name/{settingName}") + public ResponseEntity getThresholdByName(@PathVariable String settingName) { + Optional threshold = thresholdService.findBySettingName(settingName); + return threshold.map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + // 更新阈值设置 + @PutMapping("/{id}") + public ResponseEntity updateThreshold(@PathVariable Long id, @RequestBody ClimateThreshold threshold) { + threshold.setId(id); + try { + ClimateThreshold updated = thresholdService.saveThreshold(threshold); + return ResponseEntity.ok(updated); + } catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + + // 删除阈值设置 + @DeleteMapping("/{id}") + public ResponseEntity deleteThreshold(@PathVariable Long id) { + thresholdService.deleteById(id); + return ResponseEntity.ok().build(); + } + + // 检查环境状态 + @GetMapping("/check-status") + public ResponseEntity checkStatus( + @RequestParam String settingName, + @RequestParam BigDecimal temperature, + @RequestParam BigDecimal humidity) { + + String status = thresholdService.checkEnvironmentStatus(settingName, temperature, humidity); + return ResponseEntity.ok(status); + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/ClimateThresholdRequest.java b/src/main/java/com/xzzn/movecheck/wsd/ClimateThresholdRequest.java new file mode 100644 index 0000000..6829713 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/ClimateThresholdRequest.java @@ -0,0 +1,44 @@ +package com.xzzn.movecheck.wsd; + + +public class ClimateThresholdRequest { + private Double wdmin; + + private Double wdmax; + + private Double sdmin; + + private Double sdmax; + + public Double getWdmin() { + return wdmin; + } + + public void setWdmin(Double wdmin) { + this.wdmin = wdmin; + } + + public Double getWdmax() { + return wdmax; + } + + public void setWdmax(Double wdmax) { + this.wdmax = wdmax; + } + + public Double getSdmin() { + return sdmin; + } + + public void setSdmin(Double sdmin) { + this.sdmin = sdmin; + } + + public Double getSdmax() { + return sdmax; + } + + public void setSdmax(Double sdmax) { + this.sdmax = sdmax; + } +} diff --git a/src/main/java/com/xzzn/movecheck/wsd/DataController.java b/src/main/java/com/xzzn/movecheck/wsd/DataController.java new file mode 100644 index 0000000..93ef642 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/DataController.java @@ -0,0 +1,71 @@ +package com.xzzn.movecheck.wsd; + +import com.xzzn.movecheck.repo.SensorData; +import com.xzzn.movecheck.repo.SensorDataRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/data") +@CrossOrigin(origins = "*") +public class DataController { + + @Autowired + private SensorDataRepository sensorDataRepository; + + // 获取最新数据 + @GetMapping("/latest") + public ResponseEntity> getLatestData() { + List dataList = sensorDataRepository.findTop10ByOrderByCreateTimeDesc(); + + Map response = new HashMap<>(); + if (!dataList.isEmpty()) { + SensorData latest = dataList.get(0); + response.put("sd", latest.getSd()); + response.put("wd", latest.getWd()); + response.put("pm", latest.getPm()); + response.put("timestamp", latest.getCreateTime()); + response.put("status", "success"); + } else { + response.put("status", "no_data"); + response.put("message", "暂无数据"); + } + + return ResponseEntity.ok(response); + } + + // 获取历史数据 + @PostMapping("/history") + public ResponseEntity> getHistoryData(@RequestBody TimeRangeRequest request) { + List dataList = sensorDataRepository.findByCreateTimeBetween(request.getStartTime(), request.getEndTime()); + return ResponseEntity.ok(dataList); + } + + // 获取指定数据 + @GetMapping("/current") + public ResponseEntity> getCurrentData() { + List dataList = sensorDataRepository.findTop10ByOrderByCreateTimeDesc(); + + Map response = new HashMap<>(); + if (!dataList.isEmpty()) { + SensorData current = dataList.get(0); + response.put("sd", current.getSd()); + response.put("wd", current.getWd()); + response.put("pm", current.getWd()); + + } else { + response.put("sd", 0); + response.put("wd", 0); + response.put("pm", 0); + + } + + return ResponseEntity.ok(response); + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/EmqxApiResponse.java b/src/main/java/com/xzzn/movecheck/wsd/EmqxApiResponse.java new file mode 100644 index 0000000..2be08fb --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/EmqxApiResponse.java @@ -0,0 +1,22 @@ +// EmqxApiResponse.java +package com.xzzn.movecheck.wsd; + +import com.xzzn.movecheck.repo.EmqxClientInfo; + +import java.util.List; + +public class EmqxApiResponse { + private Integer code; + private String message; + private List data; + + // Getter和Setter + public Integer getCode() { return code; } + public void setCode(Integer code) { this.code = code; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + + public List getData() { return data; } + public void setData(List data) { this.data = data; } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/EventAlertController.java b/src/main/java/com/xzzn/movecheck/wsd/EventAlertController.java new file mode 100644 index 0000000..184585a --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/EventAlertController.java @@ -0,0 +1,190 @@ +package com.xzzn.movecheck.wsd; + +import com.xzzn.movecheck.service.EventAlertService; +import com.xzzn.movecheck.repo.AlertData; +import com.xzzn.movecheck.repo.EventData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +@RestController +@RequestMapping("/api") +@CrossOrigin(origins = "*") +@Validated +public class EventAlertController { + + @Autowired + private EventAlertService eventAlertService; + + // ==================== 事件相关API ==================== + + /** + * 保存事件数据 + */ + @PostMapping("/events") + public ResponseEntity> saveEvent( @RequestBody EventRequest request) { + Map result = eventAlertService.saveEvent(request); + return ResponseEntity.ok(result); + } + + /** + * 获取最新10条事件 + */ + @GetMapping("/events/latest") + public ResponseEntity> getLatestEvents() { + List events = eventAlertService.getLatestEvents(); + + Map response = new HashMap<>(); + response.put("status", "success"); + response.put("data", events); + response.put("total", events.size()); + return ResponseEntity.ok(response); + } + + /** + * 分页获取事件数据 + */ + @GetMapping("/events") + public ResponseEntity> getEventsByPage( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + + Page eventPage = eventAlertService.getEventsByPage(page, size); + PageResponse response = PageResponse.success(eventPage); + return ResponseEntity.ok(response); + } + + // ==================== 告警相关API ==================== + + /** + * 保存告警数据 + */ + @PostMapping("/alerts") + public ResponseEntity> saveAlert( @RequestBody AlertRequest request) { + Map result = eventAlertService.saveAlert(request); + return ResponseEntity.ok(result); + } + + /** + * 获取最新10条告警 + */ + @GetMapping("/alerts/latest") + public ResponseEntity> getLatestAlerts() { + List alerts = eventAlertService.getLatestAlerts(); + + Map response = new HashMap<>(); + response.put("status", "success"); + response.put("data", alerts); + response.put("total", alerts.size()); + return ResponseEntity.ok(response); + } + + /** + * 分页获取告警数据 + */ + @GetMapping("/alerts") + public ResponseEntity> getAlertsByPage( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + + Page alertPage = eventAlertService.getAlertsByPage(page, size); + PageResponse response = PageResponse.success(alertPage); + return ResponseEntity.ok(response); + } + + /** + * 根据创建时间范围查询事件数据 + */ + @GetMapping("/events/byCreateTime") + public ResponseEntity> getEventsByCreateTime( + @RequestParam(required = false) String startTime, + @RequestParam(required = false) String endTime, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { +// 将字符串时间转换为 LocalDateTime + LocalDateTime start = null; + LocalDateTime end = null; + + if (startTime != null && !startTime.isEmpty()) { + start = parseTimeString(startTime); + } + + if (endTime != null && !endTime.isEmpty()) { + end = parseTimeString(endTime); + } + Page eventPage = eventAlertService.getEventsByCreateTime(start, end, page, size); + Map response = new HashMap<>(); + response.put("status", "success"); + response.put("data", eventPage.getContent()); + response.put("total", eventPage.getTotalElements()); + response.put("currentPage", eventPage.getNumber()); + response.put("totalPages", eventPage.getTotalPages()); + + return ResponseEntity.ok(response); + } + + /** + * 根据创建时间范围查询告警数据 + */ + @GetMapping("/alerts/byCreateTime") + public ResponseEntity> getAlertsByCreateTime( + @RequestParam(required = false) String startTime, + @RequestParam(required = false) String endTime, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + // 将字符串时间转换为 LocalDateTime + LocalDateTime start = null; + LocalDateTime end = null; + + if (startTime != null && !startTime.isEmpty()) { + start = parseTimeString(startTime); + } + + if (endTime != null && !endTime.isEmpty()) { + end = parseTimeString(endTime); + } + Page alertPage = eventAlertService.getAlertsByCreateTime(start, end, page, size); + Map response = new HashMap<>(); + response.put("status", "success"); + response.put("data", alertPage.getContent()); + response.put("total", alertPage.getTotalElements()); + response.put("currentPage", alertPage.getNumber()); + response.put("totalPages", alertPage.getTotalPages()); + + return ResponseEntity.ok(response); + } + + /** + * 解析时间字符串为 LocalDateTime + */ + private LocalDateTime parseTimeString(String timeStr) { + try { + // 支持多种时间格式 + if (timeStr.contains("T")) { + // ISO 格式: 2025-10-10T10:00:00 + return LocalDateTime.parse(timeStr); + } else if (timeStr.contains(":")) { + // 格式: 2025-10-10 10:00:00 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + return LocalDateTime.parse(timeStr, formatter); + } else { + // 日期格式: 2025-10-10 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDate date = LocalDate.parse(timeStr, formatter); + return date.atStartOfDay(); + } + } catch (Exception e) { + throw new IllegalArgumentException("无效的时间格式: " + timeStr); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/EventRequest.java b/src/main/java/com/xzzn/movecheck/wsd/EventRequest.java new file mode 100644 index 0000000..c04f9a7 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/EventRequest.java @@ -0,0 +1,60 @@ +package com.xzzn.movecheck.wsd; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.time.LocalDateTime; + +public class EventRequest { + + private String eventType; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime eventTime; + + private String status; + + private String description; + + private String deviceId; + + // Getter 和 Setter + public String getEventType() { + return eventType; + } + + public void setEventType(String eventType) { + this.eventType = eventType; + } + + public LocalDateTime getEventTime() { + return eventTime; + } + + public void setEventTime(LocalDateTime eventTime) { + this.eventTime = eventTime; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/HealthDeviceController.java b/src/main/java/com/xzzn/movecheck/wsd/HealthDeviceController.java new file mode 100644 index 0000000..bcfdd5f --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/HealthDeviceController.java @@ -0,0 +1,38 @@ +package com.xzzn.movecheck.wsd; + +import com.xzzn.movecheck.repo.HealthDevice; +import com.xzzn.movecheck.service.HealthDeviceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Optional; + +// 在Controller中使用 +@RestController +@RequestMapping("/api/health") +public class HealthDeviceController { + + @Autowired + private HealthDeviceService healthDeviceService; + + /** + * 获取最新的设备数据 + */ + @GetMapping("/latest") + public ResponseEntity getLatestRecord() { + List latestRecord = healthDeviceService.findLatestRecord(); + + if (latestRecord != null && !latestRecord.isEmpty()) { + return ResponseEntity.ok(latestRecord.get(0)); + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body("未找到设备数据"); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/LoginRequest.java b/src/main/java/com/xzzn/movecheck/wsd/LoginRequest.java new file mode 100644 index 0000000..fbf015b --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/LoginRequest.java @@ -0,0 +1,21 @@ +// LoginRequest.java +package com.xzzn.movecheck.wsd; + +public class LoginRequest { + private String username; + private String password; + + public LoginRequest() {} + + public LoginRequest(String username, String password) { + this.username = username; + this.password = password; + } + + // Getter和Setter + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/LoginResponse.java b/src/main/java/com/xzzn/movecheck/wsd/LoginResponse.java new file mode 100644 index 0000000..345be8a --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/LoginResponse.java @@ -0,0 +1,44 @@ +// LoginResponse.java +package com.xzzn.movecheck.wsd; + +public class LoginResponse { + private boolean success; + private String message; + private String token; + private String username; + private String nickname; + + // 无参构造 + public LoginResponse() {} + + // 只有success和message的构造 + public LoginResponse(boolean success, String message) { + this.success = success; + this.message = message; + } + + // 包含所有字段的构造 + public LoginResponse(boolean success, String message, String token, String username, String nickname) { + this.success = success; + this.message = message; + this.token = token; + this.username = username; + this.nickname = nickname; + } + + // Getter和Setter + public boolean isSuccess() { return success; } + public void setSuccess(boolean success) { this.success = success; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + + public String getToken() { return token; } + public void setToken(String token) { this.token = token; } + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getNickname() { return nickname; } + public void setNickname(String nickname) { this.nickname = nickname; } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/PageResponse.java b/src/main/java/com/xzzn/movecheck/wsd/PageResponse.java new file mode 100644 index 0000000..26f674f --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/PageResponse.java @@ -0,0 +1,116 @@ +package com.xzzn.movecheck.wsd; + +import org.springframework.data.domain.Page; +import java.util.List; + +public class PageResponse { + private String status; + private String message; + private List data; + private int currentPage; + private int pageSize; + private long totalElements; + private int totalPages; + private boolean hasNext; + private boolean hasPrevious; + + // 构造函数 + public PageResponse() {} + + public PageResponse(String status, String message) { + this.status = status; + this.message = message; + } + + // 从Spring Page对象创建响应 + public static PageResponse success(Page page) { + PageResponse response = new PageResponse<>(); + response.status = "success"; + response.message = "查询成功"; + response.data = page.getContent(); + response.currentPage = page.getNumber(); + response.pageSize = page.getSize(); + response.totalElements = page.getTotalElements(); + response.totalPages = page.getTotalPages(); + response.hasNext = page.hasNext(); + response.hasPrevious = page.hasPrevious(); + return response; + } + + public static PageResponse error(String message) { + return new PageResponse<>("error", message); + } + + // Getter 和 Setter + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public int getCurrentPage() { + return currentPage; + } + + public void setCurrentPage(int currentPage) { + this.currentPage = currentPage; + } + + public int getPageSize() { + return pageSize; + } + + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } + + public long getTotalElements() { + return totalElements; + } + + public void setTotalElements(long totalElements) { + this.totalElements = totalElements; + } + + public int getTotalPages() { + return totalPages; + } + + public void setTotalPages(int totalPages) { + this.totalPages = totalPages; + } + + public boolean isHasNext() { + return hasNext; + } + + public void setHasNext(boolean hasNext) { + this.hasNext = hasNext; + } + + public boolean isHasPrevious() { + return hasPrevious; + } + + public void setHasPrevious(boolean hasPrevious) { + this.hasPrevious = hasPrevious; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/SubscriptionStatus.java b/src/main/java/com/xzzn/movecheck/wsd/SubscriptionStatus.java new file mode 100644 index 0000000..04532b8 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/SubscriptionStatus.java @@ -0,0 +1,52 @@ +package com.xzzn.movecheck.wsd; + +// 订阅状态数据模型 +public class SubscriptionStatus { + private String topic; + private boolean subscribed; + private int qos; + private long subscribeTime; + private String errorMessage; + + // getters and setters + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public boolean isSubscribed() { + return subscribed; + } + + public void setSubscribed(boolean subscribed) { + this.subscribed = subscribed; + } + + public int getQos() { + return qos; + } + + public void setQos(int qos) { + this.qos = qos; + } + + public long getSubscribeTime() { + return subscribeTime; + } + + public void setSubscribeTime(long subscribeTime) { + this.subscribeTime = subscribeTime; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/TemperatureHumidityController.java b/src/main/java/com/xzzn/movecheck/wsd/TemperatureHumidityController.java new file mode 100644 index 0000000..1ce8007 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/TemperatureHumidityController.java @@ -0,0 +1,154 @@ +package com.xzzn.movecheck.wsd; + +import com.xzzn.movecheck.service.TemperatureHumidityService; +import com.xzzn.movecheck.repo.TemperatureHumidityData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/ac") +@CrossOrigin(origins = "*") +@Validated +public class TemperatureHumidityController { + + @Autowired + private TemperatureHumidityService temperatureHumidityService; + + /** + * 保存温湿度数据 + */ + @PostMapping("/data") + public ResponseEntity saveTemperatureHumidity( + @RequestBody TemperatureHumidityRequest request) { + + TemperatureHumidityResponse response = temperatureHumidityService.saveData(request); + return ResponseEntity.ok(response); + } + + /** + * 获取最新温湿度数据 + */ + @GetMapping("/data/latest") + public ResponseEntity getLatestTemperatureHumidity() { + TemperatureHumidityResponse response = temperatureHumidityService.getLatestData(); + return ResponseEntity.ok(response); + } + + /** + * 根据设备ID获取最新数据 + */ + @GetMapping("/data/latest/device/{deviceId}") + public ResponseEntity getLatestTemperatureHumidityByDevice( + @PathVariable String deviceId) { + + TemperatureHumidityResponse response = temperatureHumidityService.getLatestByDevice(deviceId); + return ResponseEntity.ok(response); + } + + /** + * 获取最新10条温湿度数据 + */ + @GetMapping("/data/recent") + public ResponseEntity getRecentTemperatureHumidity() { + TemperatureHumidityResponse response = temperatureHumidityService.getLatest10Data(); + return ResponseEntity.ok(response); + } + + /** + * 根据设备ID获取最新10条数据 + */ + @GetMapping("/data/recent/device/{deviceId}") + public ResponseEntity getRecentTemperatureHumidityByDevice( + @PathVariable String deviceId) { + + TemperatureHumidityResponse response = temperatureHumidityService.getLatest10ByDevice(deviceId); + return ResponseEntity.ok(response); + } + + /** + * 分页查询温湿度数据 + */ + @GetMapping("/data") + public ResponseEntity> getTemperatureHumidityByPage( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + + Page dataPage = temperatureHumidityService.getDataByPage(page, size); + + Map response = new HashMap<>(); + response.put("status", "success"); + response.put("data", dataPage.getContent()); + response.put("currentPage", dataPage.getNumber()); + response.put("pageSize", dataPage.getSize()); + response.put("totalElements", dataPage.getTotalElements()); + response.put("totalPages", dataPage.getTotalPages()); + response.put("hasNext", dataPage.hasNext()); + response.put("hasPrevious", dataPage.hasPrevious()); + + return ResponseEntity.ok(response); + } + + /** + * 根据设备ID分页查询 + */ + @GetMapping("/data/device/{deviceId}") + public ResponseEntity> getTemperatureHumidityByDeviceAndPage( + @PathVariable String deviceId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + + Page dataPage = temperatureHumidityService.getDataByDeviceAndPage(deviceId, page, size); + + Map response = new HashMap<>(); + response.put("status", "success"); + response.put("deviceId", deviceId); + response.put("data", dataPage.getContent()); + response.put("currentPage", dataPage.getNumber()); + response.put("pageSize", dataPage.getSize()); + response.put("totalElements", dataPage.getTotalElements()); + response.put("totalPages", dataPage.getTotalPages()); + response.put("hasNext", dataPage.hasNext()); + response.put("hasPrevious", dataPage.hasPrevious()); + + return ResponseEntity.ok(response); + } + + /** + * 获取所有设备ID列表 + */ + @GetMapping("/devices") + public ResponseEntity> getAllDevices() { + List deviceIds = temperatureHumidityService.getAllDeviceIds(); + + Map response = new HashMap<>(); + response.put("status", "success"); + response.put("devices", deviceIds); + response.put("total", deviceIds.size()); + + return ResponseEntity.ok(response); + } + + /** + * 获取数据统计 + */ + @GetMapping("/stats") + public ResponseEntity> getStats() { + long totalCount = temperatureHumidityService.getTotalCount(); + List deviceIds = temperatureHumidityService.getAllDeviceIds(); + + Map response = new HashMap<>(); + response.put("status", "success"); + response.put("totalRecords", totalCount); + response.put("totalDevices", deviceIds.size()); + response.put("devices", deviceIds); + + return ResponseEntity.ok(response); + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/TemperatureHumidityRequest.java b/src/main/java/com/xzzn/movecheck/wsd/TemperatureHumidityRequest.java new file mode 100644 index 0000000..dabb278 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/TemperatureHumidityRequest.java @@ -0,0 +1,36 @@ +package com.xzzn.movecheck.wsd; + + +public class TemperatureHumidityRequest { + + private Double temperature; + + private Double humidity; + + private String deviceId; + + // Getter 和 Setter + public Double getTemperature() { + return temperature; + } + + public void setTemperature(Double temperature) { + this.temperature = temperature; + } + + public Double getHumidity() { + return humidity; + } + + public void setHumidity(Double humidity) { + this.humidity = humidity; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/TemperatureHumidityResponse.java b/src/main/java/com/xzzn/movecheck/wsd/TemperatureHumidityResponse.java new file mode 100644 index 0000000..e300c46 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/TemperatureHumidityResponse.java @@ -0,0 +1,112 @@ +package com.xzzn.movecheck.wsd; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.xzzn.movecheck.repo.TemperatureHumidityData; + +import java.time.LocalDateTime; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TemperatureHumidityResponse { + private String status; + private String message; + private Double temperature; + private Double humidity; + private String deviceId; + private LocalDateTime createTime; + private List dataList; + private Long total; + + // 构造函数 + public TemperatureHumidityResponse() {} + + public TemperatureHumidityResponse(String status, String message) { + this.status = status; + this.message = message; + } + + // 从实体创建响应 + public static TemperatureHumidityResponse fromEntity(TemperatureHumidityData entity) { + TemperatureHumidityResponse response = new TemperatureHumidityResponse(); + response.status = "success"; + response.message = "获取成功"; + response.temperature = entity.getTemperature(); + response.humidity = entity.getHumidity(); + response.deviceId = entity.getDeviceId(); + response.createTime = entity.getCreateTime(); + return response; + } + + public static TemperatureHumidityResponse success(String message) { + return new TemperatureHumidityResponse("success", message); + } + + public static TemperatureHumidityResponse error(String message) { + return new TemperatureHumidityResponse("error", message); + } + + // Getter 和 Setter + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Double getTemperature() { + return temperature; + } + + public void setTemperature(Double temperature) { + this.temperature = temperature; + } + + public Double getHumidity() { + return humidity; + } + + public void setHumidity(Double humidity) { + this.humidity = humidity; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public List getDataList() { + return dataList; + } + + public void setDataList(List dataList) { + this.dataList = dataList; + } + + public Long getTotal() { + return total; + } + + public void setTotal(Long total) { + this.total = total; + } +} \ No newline at end of file diff --git a/src/main/java/com/xzzn/movecheck/wsd/TimeRangeRequest.java b/src/main/java/com/xzzn/movecheck/wsd/TimeRangeRequest.java new file mode 100644 index 0000000..9c970f7 --- /dev/null +++ b/src/main/java/com/xzzn/movecheck/wsd/TimeRangeRequest.java @@ -0,0 +1,64 @@ +package com.xzzn.movecheck.wsd; + +import com.fasterxml.jackson.annotation.JsonFormat; +import java.time.LocalDateTime; + +public class TimeRangeRequest { + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + private String deviceId; + + // 构造函数 + public TimeRangeRequest() {} + + public TimeRangeRequest(LocalDateTime startTime, LocalDateTime endTime) { + this.startTime = startTime; + this.endTime = endTime; + } + + public TimeRangeRequest(LocalDateTime startTime, LocalDateTime endTime, String deviceId) { + this.startTime = startTime; + this.endTime = endTime; + this.deviceId = deviceId; + } + + // Getter 和 Setter + public LocalDateTime getStartTime() { + return startTime; + } + + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + // 验证时间范围是否有效 + public boolean isValid() { + return startTime != null && endTime != null && !startTime.isAfter(endTime); + } + + // 获取时间范围描述 + public String getTimeRangeDescription() { + return String.format("%s 至 %s", startTime, endTime); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..881881f --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,23 @@ +spring: + datasource: + url: jdbc:mysql://122.51.194.184:13306/movecheck?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai + username: movecheck + password: 8a7c97e5c31c + + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL5Dialect + +mqtt: + broker: tcp://122.51.194.184:1883 + clientId: movecheck-client + topics: HDYDCJ_01_DEVICE,HDYDCJ_01_UP + username: dmbroker + password: qwer1234 + qos: 1,1 +server: + port: 7999 \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..ae2a2ee --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + ${CONSOLE_PATTERN} + UTF-8 + + + + + + ${LOG_HOME}/${APP_NAME}-info.log + + ${LOG_HOME}/archive/${APP_NAME}-info-%d{yyyy-MM-dd}.%i.log.gz + 10MB + 30 + 1GB + + + ${FILE_PATTERN} + UTF-8 + + + INFO + ACCEPT + DENY + + + + + + ${LOG_HOME}/${APP_NAME}-error.log + + ${LOG_HOME}/archive/${APP_NAME}-error-%d{yyyy-MM-dd}.%i.log.gz + 10MB + 30 + 1GB + + + ${FILE_PATTERN} + UTF-8 + + + ERROR + + + + + + ${LOG_HOME}/${APP_NAME}-api.log + + ${LOG_HOME}/archive/${APP_NAME}-api-%d{yyyy-MM-dd}.%i.log.gz + 10MB + 7 + 500MB + + + ${FILE_PATTERN} + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file