接口代码修改 #45

Merged
hnu202326010106 merged 1 commits from wanglei_branch into develop 1 month ago

@ -1,214 +0,0 @@
package com.campus.water.config;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.Properties;
/**
* MyBatis
* MyBatisSpring BootMapper
*
* 1.
* 2. MyBatisSqlSessionFactory
* 3. MapperMyBatis
* 4.
*
* - @Configuration:
* - @MapperScan: Mapper
* - @EnableTransactionManagement:
*/
@Configuration
@MapperScan(basePackages = "com.campus.water.mapper") // 扫描Mapper接口
@EnableTransactionManagement // 启用事务管理
public class MyBatisConfig {
// ========== 数据源配置 ==========
/**
* HikariCP
* @return
*
*
* - HikariCP
* - application.yml
* -
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari") // 绑定配置前缀
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
// 设置连接池监控配置可在application.yml中覆盖
dataSource.setPoolName("CampusWaterHikariPool");
dataSource.setMaximumPoolSize(20); // 最大连接数
dataSource.setMinimumIdle(5); // 最小空闲连接
dataSource.setIdleTimeout(600000); // 空闲连接超时时间10分钟
dataSource.setConnectionTimeout(30000); // 连接超时时间30秒
dataSource.setMaxLifetime(1800000); // 连接最大生命周期30分钟
// 连接测试配置
dataSource.setConnectionTestQuery("SELECT 1"); // MySQL测试语句
dataSource.setValidationTimeout(5000); // 验证超时时间
// 连接泄漏检测
dataSource.setLeakDetectionThreshold(60000); // 60秒泄漏检测阈值
return dataSource;
}
// ========== MyBatis核心配置 ==========
/**
* SqlSessionFactory
* @param dataSource
* @return SqlSessionFactory
* MyBatisSqlSession
*
* -
* - Mapper XML
* -
* -
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 1. 设置数据源
sessionFactory.setDataSource(dataSource);
// 2. 设置Mapper XML文件位置支持Ant风格路径
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactory.setMapperLocations(
resolver.getResources("classpath:mapper/**/*.xml")
);
// 3. 设置类型别名包扫描路径(实体类包)
sessionFactory.setTypeAliasesPackage("com.campus.water.entity");
// 4. 创建Configuration对象设置全局配置
org.apache.ibatis.session.Configuration configuration =
new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true); // 下划线转驼峰
configuration.setUseGeneratedKeys(true); // 使用JDBC的getGeneratedKeys获取主键
configuration.setUseColumnLabel(true); // 使用列标签代替列名
configuration.setCacheEnabled(true); // 启用二级缓存
// 5. 设置日志实现
configuration.setLogImpl(org.apache.ibatis.logging.stdout.StdOutImpl.class);
// 6. 设置默认的执行器类型
configuration.setDefaultExecutorType(org.apache.ibatis.session.ExecutorType.SIMPLE);
// 7. 设置配置
sessionFactory.setConfiguration(configuration);
// 8. 设置插件(分页插件、性能监控插件等)
sessionFactory.setPlugins(
// 分页插件
pageInterceptor(),
// 性能分析插件(开发环境使用)
// performanceInterceptor()
);
return sessionFactory.getObject();
}
/**
*
* @return
* MySQLOracle
*
* -
* -
* - 线
*/
@Bean
public com.github.pagehelper.PageInterceptor pageInterceptor() {
com.github.pagehelper.PageInterceptor pageInterceptor =
new com.github.pagehelper.PageInterceptor();
Properties properties = new Properties();
// 1. 分页合理化pageNum<=0时查询第一页pageNum>总页数时查询最后一页
properties.setProperty("reasonable", "true");
// 2. 支持通过Mapper接口参数传递分页参数
properties.setProperty("supportMethodsArguments", "true");
// 3. 自动检测数据库类型
properties.setProperty("autoRuntimeDialect", "true");
// 4. 分页参数默认值
properties.setProperty("pageSizeZero", "true"); // pageSize=0时返回全部结果
properties.setProperty("params", "count=countSql");
// 5. 开启分页返回对象中的统计信息
properties.setProperty("rowBoundsWithCount", "true");
// 6. 总是返回PageInfo对象
properties.setProperty("returnPageInfo", "always");
pageInterceptor.setProperties(properties);
return pageInterceptor;
}
/**
* 使
* @return
* SQL
*
*/
/*
@Bean
@Profile({"dev", "test"}) // 只在开发测试环境启用
public com.github.pagehelper.sql.SqlStatsInterceptor performanceInterceptor() {
com.github.pagehelper.sql.SqlStatsInterceptor interceptor =
new com.github.pagehelper.sql.SqlStatsInterceptor();
Properties properties = new Properties();
properties.setProperty("maxTime", "1000"); // 最大执行时间阈值(毫秒)
properties.setProperty("format", "true"); // 格式化SQL输出
interceptor.setProperties(properties);
return interceptor;
}
*/
// ========== 事务管理配置 ==========
/**
*
* @param dataSource
* @return
*
* @Transactional
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// ========== 其他配置 ==========
/**
* MyBatis
* @return MyBatis
* MyBatis
*/
@Bean
@ConfigurationProperties(prefix = "mybatis.configuration")
public org.apache.ibatis.session.Configuration mybatisConfiguration() {
return new org.apache.ibatis.session.Configuration();
}
}

@ -1,410 +0,0 @@
package com.campus.water.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import java.util.Arrays;
import java.util.List;
/**
* Swagger/OpenAPI
* API
*
* 1. API
* 2. 线API
* 3. API
* 4. JWT token
* 访
* - Swagger UI: http://localhost:8080/swagger-ui/index.html
* - OpenAPI JSON: http://localhost:8080/v3/api-docs
*
* - @Configuration:
* - @Profile: /
*/
@Configuration
@Profile({"dev", "test"}) // 只在开发测试环境启用,生产环境关闭
public class SwaggerConfig {
@Value("${spring.application.name:校园直饮矿化水物联网运维平台}")
private String applicationName;
@Value("${server.port:8080}")
private String serverPort;
/**
* OpenAPI
* @return OpenAPI
* API
*
* - info: API
* - servers: API
* - components:
* - security:
*/
@Bean
public OpenAPI campusWaterOpenAPI() {
return new OpenAPI()
// 1. API基本信息
.info(buildApiInfo())
// 2. API服务器配置
.servers(buildServers())
// 3. 安全方案配置JWT Bearer Token
.components(buildComponents())
.addSecurityItem(buildSecurityRequirement())
// 4. 全局标签可在此定义也可以在Controller上使用@Tag
// .tags(buildTags())
// 5. 外部文档链接
.externalDocs(new io.swagger.v3.oas.models.ExternalDocumentation()
.description("项目GitHub仓库")
.url("https://github.com/campus-water/water-management-system"));
}
/**
* API
* @return Info
* API
*/
private Info buildApiInfo() {
return new Info()
.title("校园直饮矿化水物联网运维平台 API")
.version("v1.0.0")
.description("""
##
##
### 1.
- 线/线/
-
-
### 2.
- //
-
- 使
-
### 3.
-
-
-
### 4.
-
-
-
### 5.
- //
-
-
##
###
```json
{
"code": 200, // 状态码
"msg": "success", // 消息
"data": {} // 数据
}
```
###
- 200:
- 400:
- 401:
- 403: 访
- 404:
- 500:
""")
.termsOfService("https://campus-water.com/terms")
.contact(buildContact())
.license(buildLicense());
}
/**
*
* @return Contact
*
*/
private Contact buildContact() {
return new Contact()
.name("开发团队")
.url("https://campus-water.com")
.email("dev@campus-water.com")
.extensions(new java.util.HashMap<>() {{
put("技术支持", "support@campus-water.com");
put("产品反馈", "feedback@campus-water.com");
}});
}
/**
*
* @return License
* API使
*/
private License buildLicense() {
return new License()
.name("Apache 2.0")
.url("http://www.apache.org/licenses/LICENSE-2.0.html")
.identifier("Apache-2.0");
}
/**
*
* @return
* API访
*/
private List<Server> buildServers() {
return Arrays.asList(
// 本地开发环境
new Server()
.url("http://localhost:" + serverPort)
.description("本地开发环境")
.variables(new java.util.HashMap<>() {{
put("port", new io.swagger.v3.oas.models.servers.ServerVariable()
._default(serverPort)
.description("服务器端口"));
}}),
// 测试环境
new Server()
.url("https://test.campus-water.com")
.description("测试环境")
.variables(new java.util.HashMap<>() {{
put("env", new io.swagger.v3.oas.models.servers.ServerVariable()
._default("test")
.description("环境标识")
._enum(Arrays.asList("test", "staging")));
}}),
// 生产环境
new Server()
.url("https://api.campus-water.com")
.description("生产环境")
.variables(new java.util.HashMap<>() {{
put("version", new io.swagger.v3.oas.models.servers.ServerVariable()
._default("v1")
.description("API版本")
._enum(Arrays.asList("v1", "v2")));
}})
);
}
/**
*
* @return Components
*
*/
private Components buildComponents() {
return new Components()
// 1. JWT Bearer Token安全方案
.addSecuritySchemes("Bearer Token", buildJwtSecurityScheme())
// 2. API Key安全方案备用
.addSecuritySchemes("API Key", buildApiKeySecurityScheme())
// 3. 通用响应模型
.addSchemas("ResultVO", buildResultVOSchema())
.addSchemas("PageResult", buildPageResultSchema())
// 4. 通用请求头
.addParameters("X-User-Id", buildUserIdHeader())
.addParameters("X-User-Type", buildUserTypeHeader());
}
/**
* JWT
* @return SecurityScheme
* JWT Bearer Token
*/
private SecurityScheme buildJwtSecurityScheme() {
return new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("""
JWT Bearer Token
### Token
1. token
2. Authorization: Bearer {token}
### Token
```json
{
"sub": "用户ID",
"username": "用户名",
"userType": "用户类型",
"iat": ,
"exp":
}
```
""")
.in(SecurityScheme.In.HEADER)
.name("Authorization");
}
/**
* API Key
* @return SecurityScheme
*
*/
private SecurityScheme buildApiKeySecurityScheme() {
return new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("X-API-KEY")
.description("API Key认证用于第三方系统集成");
}
/**
*
* @return Schema
* API
*/
private io.swagger.v3.oas.models.media.Schema<?> buildResultVOSchema() {
return new io.swagger.v3.oas.models.media.Schema<>()
.type("object")
.addProperties("code", new io.swagger.v3.oas.models.media.Schema<>()
.type("integer")
.description("状态码")
.example(200))
.addProperties("msg", new io.swagger.v3.oas.models.media.Schema<>()
.type("string")
.description("消息")
.example("success"))
.addProperties("data", new io.swagger.v3.oas.models.media.Schema<>()
.type("object")
.description("数据")
.nullable(true))
.addProperties("timestamp", new io.swagger.v3.oas.models.media.Schema<>()
.type("string")
.format("date-time")
.description("时间戳")
.example("2024-01-01T12:00:00Z"))
.description("通用响应格式")
.required(Arrays.asList("code", "msg", "timestamp"));
}
/**
*
* @return Schema
*
*/
private io.swagger.v3.oas.models.media.Schema<?> buildPageResultSchema() {
return new io.swagger.v3.oas.models.media.Schema<>()
.type("object")
.addProperties("total", new io.swagger.v3.oas.models.media.Schema<>()
.type("integer")
.description("总记录数")
.example(100))
.addProperties("pages", new io.swagger.v3.oas.models.media.Schema<>()
.type("integer")
.description("总页数")
.example(10))
.addProperties("pageNum", new io.swagger.v3.oas.models.media.Schema<>()
.type("integer")
.description("当前页码")
.example(1))
.addProperties("pageSize", new io.swagger.v3.oas.models.media.Schema<>()
.type("integer")
.description("每页大小")
.example(10))
.addProperties("list", new io.swagger.v3.oas.models.media.Schema<>()
.type("array")
.description("数据列表")
.items(new io.swagger.v3.oas.models.media.Schema<>()))
.description("分页响应格式")
.required(Arrays.asList("total", "pages", "pageNum", "pageSize", "list"));
}
/**
* ID
* @return Parameter
* ID
*/
private io.swagger.v3.oas.models.parameters.Parameter buildUserIdHeader() {
return new io.swagger.v3.oas.models.parameters.Parameter()
.name("X-User-Id")
.in(io.swagger.v3.oas.models.parameters.Parameter.In.HEADER.toString())
.description("用户ID登录后由系统分配")
.required(false)
.schema(new io.swagger.v3.oas.models.media.Schema<>()
.type("string")
.example("STU20240001"));
}
/**
*
* @return Parameter
*
*/
private io.swagger.v3.oas.models.parameters.Parameter buildUserTypeHeader() {
return new io.swagger.v3.oas.models.parameters.Parameter()
.name("X-User-Type")
.in(io.swagger.v3.oas.models.parameters.Parameter.In.HEADER.toString())
.description("用户类型student/repairer/admin")
.required(false)
.schema(new io.swagger.v3.oas.models.media.Schema<>()
.type("string")
.example("student"));
}
/**
*
* @return SecurityRequirement
*
*/
private SecurityRequirement buildSecurityRequirement() {
return new SecurityRequirement()
.addList("Bearer Token");
}
/**
* API
* @return
* API
*/
/*
private List<io.swagger.v3.oas.models.tags.Tag> buildTags() {
return Arrays.asList(
new io.swagger.v3.oas.models.tags.Tag()
.name("用户管理")
.description("用户认证、注册、个人信息等接口"),
new io.swagger.v3.oas.models.tags.Tag()
.name("设备管理")
.description("设备状态、信息、区域分配等接口"),
new io.swagger.v3.oas.models.tags.Tag()
.name("数据统计")
.description("用水量、告警、设备状态等统计接口"),
new io.swagger.v3.oas.models.tags.Tag()
.name("告警管理")
.description("告警创建、处理、查询等接口"),
new io.swagger.v3.oas.models.tags.Tag()
.name("工单管理")
.description("维修工单创建、分配、处理等接口"),
new io.swagger.v3.oas.models.tags.Tag()
.name("水质管理")
.description("水质数据查询、分析等接口")
);
}
*/
}

@ -1,84 +0,0 @@
/**
* App
*
*
*
* -
* - ID
* -
*
* 1. GET /personal-water-usage: //
* 2. GET /device-status-overview:
* Spring MVCHeader
*/
package com.campus.water.controller.app;
import com.campus.water.entity.dto.request.StatisticsQueryRequest;
import com.campus.water.entity.vo.StatisticsVO;
import com.campus.water.service.StatisticsService;
import com.campus.water.util.ResultVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.Map;
@RestController
@RequestMapping("/api/app/statistics")
@RequiredArgsConstructor
@Tag(name = "App统计接口", description = "移动端简化统计接口")
public class AppStatisticsController {
private final StatisticsService statisticsService;
@GetMapping("/personal-water-usage")
@Operation(summary = "个人用水统计", description = "获取当前用户的用水统计")
public ResponseEntity<ResultVO<StatisticsVO>> getPersonalWaterUsage(
@RequestParam(required = false) String period, // today/week/month
@RequestHeader("X-User-Id") String userId) {
try {
StatisticsQueryRequest request = new StatisticsQueryRequest();
request.setStatType("by_time");
LocalDate now = LocalDate.now();
if ("today".equals(period)) {
request.setPeriod("day");
request.setStartDate(now);
request.setEndDate(now);
} else if ("week".equals(period)) {
request.setPeriod("day");
request.setStartDate(now.minusDays(7));
request.setEndDate(now);
} else if ("month".equals(period)) {
request.setPeriod("day");
request.setStartDate(now.withDayOfMonth(1));
request.setEndDate(now);
} else {
request.setPeriod("day");
request.setStartDate(now.minusDays(30));
request.setEndDate(now);
}
// 这里需要根据userId过滤数据实际实现需要调整
StatisticsVO result = statisticsService.getWaterUsageStatistics(request);
return ResponseEntity.ok(ResultVO.success(result));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "获取个人用水统计失败: " + e.getMessage()));
}
}
@GetMapping("/device-status-overview")
@Operation(summary = "设备状态概览", description = "获取设备状态概览信息")
public ResponseEntity<ResultVO<Map<String, Object>>> getDeviceStatusOverview(
@RequestParam(required = false) String areaId) {
try {
Map<String, Object> result = statisticsService.getDeviceStatusStatistics(areaId, null);
return ResponseEntity.ok(ResultVO.success(result));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "获取设备状态概览失败: " + e.getMessage()));
}
}
}

@ -15,7 +15,7 @@ package com.campus.water.controller.web;
import com.campus.water.entity.Device;
import com.campus.water.entity.dto.request.DeviceStatusUpdateRequest;
import com.campus.water.service.DeviceService;
import com.campus.water.service.DeviceStatusService;
import com.campus.water.util.ResultVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -33,14 +33,14 @@ import java.util.Map;
@Tag(name = "设备状态管理接口", description = "Web管理端设备状态管理接口")
public class DeviceStatusController {
private final DeviceService deviceService;
private final DeviceStatusService deviceStatusService;
@PostMapping("/update")
@Operation(summary = "更新设备状态", description = "手动更新设备状态(在线/离线/故障)")
public ResponseEntity<ResultVO<Boolean>> updateDeviceStatus(
@Valid @RequestBody DeviceStatusUpdateRequest request) {
try {
boolean result = deviceService.updateDeviceStatus(request);
boolean result = deviceStatusService.updateDeviceStatus(request);
return ResponseEntity.ok(ResultVO.success(result, "设备状态更新成功"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "设备状态更新失败: " + e.getMessage()));
@ -51,7 +51,7 @@ public class DeviceStatusController {
@Operation(summary = "标记设备在线", description = "将设备标记为在线状态")
public ResponseEntity<ResultVO<Boolean>> markDeviceOnline(@PathVariable String deviceId) {
try {
boolean result = deviceService.markDeviceOnline(deviceId);
boolean result = deviceStatusService.markDeviceOnline(deviceId);
return ResponseEntity.ok(ResultVO.success(result, "设备已标记为在线"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "标记设备在线失败: " + e.getMessage()));
@ -64,7 +64,7 @@ public class DeviceStatusController {
@PathVariable String deviceId,
@RequestParam(required = false) String reason) {
try {
boolean result = deviceService.markDeviceOffline(deviceId, reason);
boolean result = deviceStatusService.markDeviceOffline(deviceId, reason);
return ResponseEntity.ok(ResultVO.success(result, "设备已标记为离线"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "标记设备离线失败: " + e.getMessage()));
@ -78,7 +78,7 @@ public class DeviceStatusController {
@RequestParam String faultType,
@RequestParam String description) {
try {
boolean result = deviceService.markDeviceFault(deviceId, faultType, description);
boolean result = deviceStatusService.markDeviceFault(deviceId, faultType, description);
return ResponseEntity.ok(ResultVO.success(result, "设备已标记为故障"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "标记设备故障失败: " + e.getMessage()));
@ -92,7 +92,7 @@ public class DeviceStatusController {
@RequestParam String status,
@RequestParam(required = false) String remark) {
try {
boolean result = deviceService.batchUpdateDeviceStatus(deviceIds, status, remark);
boolean result = deviceStatusService.batchUpdateDeviceStatus(deviceIds, status, remark);
return ResponseEntity.ok(ResultVO.success(result, "批量更新设备状态成功"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "批量更新设备状态失败: " + e.getMessage()));
@ -106,7 +106,7 @@ public class DeviceStatusController {
@RequestParam(required = false) String areaId,
@RequestParam(required = false) String deviceType) {
try {
List<Device> devices = deviceService.getDevicesByStatus(status, areaId, deviceType);
List<Device> devices = deviceStatusService.getDevicesByStatus(status, areaId, deviceType);
return ResponseEntity.ok(ResultVO.success(devices));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "查询设备失败: " + e.getMessage()));
@ -119,7 +119,7 @@ public class DeviceStatusController {
@RequestParam(required = false) String areaId,
@RequestParam(required = false) String deviceType) {
try {
Map<String, Object> result = deviceService.getDeviceStatusCount(areaId, deviceType);
Map<String, Object> result = deviceStatusService.getDeviceStatusCount(areaId, deviceType);
return ResponseEntity.ok(ResultVO.success(result));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "设备状态统计失败: " + e.getMessage()));
@ -132,7 +132,7 @@ public class DeviceStatusController {
@RequestParam(defaultValue = "30") Integer thresholdMinutes,
@RequestParam(required = false) String areaId) {
try {
List<Device> devices = deviceService.getOfflineDevicesExceedThreshold(thresholdMinutes, areaId);
List<Device> devices = deviceStatusService.getOfflineDevicesExceedThreshold(thresholdMinutes, areaId);
return ResponseEntity.ok(ResultVO.success(devices));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "离线设备检测失败: " + e.getMessage()));
@ -144,7 +144,7 @@ public class DeviceStatusController {
public ResponseEntity<ResultVO<String>> autoDetectOfflineDevices(
@RequestParam(defaultValue = "30") Integer thresholdMinutes) {
try {
deviceService.autoDetectOfflineDevices(thresholdMinutes);
deviceStatusService.autoDetectOfflineDevices(thresholdMinutes);
return ResponseEntity.ok(ResultVO.success("离线设备检测完成"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "自动检测离线设备失败: " + e.getMessage()));

@ -12,9 +12,9 @@
*/
package com.campus.water.controller.web;
import com.campus.water.entity.dto.request.StatisticsQueryRequest;
import com.campus.water.entity.vo.AlarmStatisticsVO;
import com.campus.water.entity.vo.StatisticsVO;
import com.campus.water.entity.dto.request.StatisticsQueryRequest;
import com.campus.water.service.StatisticsService;
import com.campus.water.util.ResultVO;
import io.swagger.v3.oas.annotations.Operation;

@ -0,0 +1,52 @@
/**
*
* admin
*
*/
package com.campus.water.entity;
import lombok.Data;
import jakarta.persistence.Column;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "admin")
public class Admin {
@Id
@Column(name = "admin_id", length = 50)
private String adminId;
@Column(name = "admin_name", length = 50)
private String adminName;
@Column(name = "password", length = 200)
private String password;
@Column(name = "phone", length = 20)
private String phone;
@Enumerated(EnumType.STRING)
@Column(name = "role", length = 50)
private AdminRole role = AdminRole.area_admin;
@Enumerated(EnumType.STRING)
@Column(name = "status", length = 50)
private AdminStatus status = AdminStatus.active;
@Column(name = "created_time")
private LocalDateTime createdTime = LocalDateTime.now();
@Column(name = "updated_time")
private LocalDateTime updatedTime = LocalDateTime.now();
public enum AdminRole {
super_admin, area_admin, viewer
}
public enum AdminStatus {
active, inactive
}
}

@ -1,27 +1,18 @@
/**
* DTO
*
*
*
* - deviceId:
* - status: online/offline/fault/maintenance
* - remark:
*
*/
// 路径com/campus/water/entity/dto/request/DeviceStatusUpdateRequest.java
package com.campus.water.entity.dto.request;
import lombok.Data;
import com.campus.water.entity.Device;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class DeviceStatusUpdateRequest {
@NotBlank(message = "设备ID不能为空")
private String deviceId;
@NotBlank(message = "设备状态不能为空")
private String status; // online/offline/fault/maintenance
private String remark; // 状态变更备注
}
private Device.DeviceStatus status;
private String remark; // 状态变更备注
private String faultType; // 故障类型状态为fault时必填
private String faultDescription; // 故障描述状态为fault时必填
}

@ -1,35 +1,17 @@
/**
* DTO
*
*
*
* - statType: water_usage/alarm/device_usage
* - period: day/week/month/year/custom
* - startDate/endDate:
* - deviceId/areaId: /
*/
// com/campus/water/entity/dto/request/StatisticsQueryRequest.java
package com.campus.water.entity.dto.request;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import java.time.LocalDate;
@Data
@Data // Lombok注解自动生成getter/setter
public class StatisticsQueryRequest {
@NotBlank(message = "统计类型不能为空")
private String statType; // water_usage/alarm/device_usage
private String period; // day/week/month/year/custom
private LocalDate startDate;
private LocalDate endDate;
private String deviceId; // 设备ID可选
private String areaId; // 区域ID可选
private String deviceType; // water_maker/water_supply
private Integer limit = 10; // 返回条数限制
private String statType; // 统计类型如by_device、by_area等
private LocalDate startDate; // 开始日期
private LocalDate endDate; // 结束日期
private String areaId; // 区域ID
private String deviceType; // 设备类型
private String terminalId; // 补充终端ID字段解决getTerminalId()错误)
private Integer limit; // 限制数量
// 若有其他字段可继续补充
}

@ -0,0 +1,25 @@
package com.campus.water.entity.po;
import lombok.Data;
import jakarta.persistence.*;
import java.time.LocalDateTime;
/**
*
*
*/
@Data
@Entity
@Table(name = "device")
public class DevicePO {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 设备ID
private String deviceId; // 设备唯一标识
private String areaId; // 所属区域ID
private String status; // 设备状态online在线、offline离线
private LocalDateTime lastActiveTime; // 最后活动时间(用于判断在线状态)
private String deviceType; // 设备类型
private LocalDateTime createTime; // 创建时间
private LocalDateTime updateTime; // 更新时间
}

@ -1,40 +1,13 @@
/**
* VO
*
*
*
* - AlarmStatisticsVO:
* - DeviceAlarmStatVO:
*/
// com/campus/water/entity/vo/AlarmStatisticsVO.java
package com.campus.water.entity.vo;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class AlarmStatisticsVO {
private Integer totalAlarms; // 总告警数
private Integer pendingAlarms; // 未处理告警数
private Integer resolvedAlarms; // 已处理告警数
private Double averageResponseTime; // 平均响应时间(小时)
// 按级别统计
private Map<String, Integer> alarmLevelCount;
// 按类型统计
private Map<String, Integer> alarmTypeCount;
// 按设备统计
private List<DeviceAlarmStatVO> deviceAlarmStats;
@Data
public static class DeviceAlarmStatVO {
private String deviceId;
private String deviceName;
private Integer totalAlarms;
private Integer pendingAlarms;
private Integer resolvedAlarms;
private String mostCommonAlarmType; // 最常见告警类型
}
private Map<String, Long> levelCount; // 告警级别统计解决setLevelCount()错误)
private Map<String, Long> statusCount; // 告警状态统计解决setStatusCount()错误)
private double handleRate; // 处理率解决setHandleRate()错误)
private long totalAlarms; // 总告警数
}

@ -1,45 +1,18 @@
/**
* VO
*
*
*
* - StatisticsVO:
* - StatItemVO: //
* - TimeSeriesVO:
*/
// com/campus/water/entity/vo/StatisticsVO.java
package com.campus.water.entity.vo;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Data
@Data // Lombok注解自动生成getter/setter
public class StatisticsVO {
private Integer totalCount; // 总数量
private Double totalAmount; // 总用水量/总金额
private Double avgAmount; // 平均值
private String period; // 统计周期day/week/month/year
private LocalDate startDate; // 统计开始日期
private LocalDate endDate; // 统计结束日期
// 明细数据
private List<StatItemVO> items;
@Data
public static class StatItemVO {
private String dimensionKey; // 维度键设备ID/区域ID/时间
private String dimensionValue; // 维度值:设备名称/区域名称/时间标签
private Integer count; // 数量
private Double amount; // 用水量/金额
private Double percentage; // 占比百分比
}
// 时间序列数据
@Data
public static class TimeSeriesVO {
private List<String> timeLabels; // 时间标签
private List<Double> values; // 对应数值
}
private String type; // 统计类型解决setType()错误)
private String areaId; // 区域ID解决setAreaId()错误)
private List<String> dates; // 日期列表解决setDates()错误)
private List<Double> waterUsage; // 用水量列表解决setWaterUsage()错误)
private double totalUsage; // 总用水量解决setTotalUsage()错误)
private double avgDailyUsage; // 日均用水量解决setAvgDailyUsage()错误)
private List<Map<String, Object>> deviceStats; // 设备统计详情
// 其他需要的字段
}

@ -1,301 +0,0 @@
package com.campus.water.mapper;
import com.campus.water.entity.Device;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* 访MyBatis Mapper
* CRUD
* 访
*
* - CRUD
* -
* -
* XMLDeviceMapper.xmlSQL
*/
@Mapper
public interface DeviceMapper {
// ========== 设备基本信息CRUD操作 ==========
/**
* ID
* @param deviceId ID
* @return
*/
Device findById(@Param("deviceId") String deviceId);
/**
*
* @return
*/
List<Device> findAll();
/**
*
* @param deviceName
* @return
*/
List<Device> findByDeviceNameLike(@Param("deviceName") String deviceName);
/**
*
* @param device
* @return
*/
int insert(Device device);
/**
*
* @param device
* @return
*/
int update(Device device);
/**
*
* @param deviceId ID
* @return
*/
int delete(@Param("deviceId") String deviceId);
// ========== 设备状态管理相关操作(新增) ==========
/**
*
* @param deviceId ID
* @param status online/offline/fault/maintenance
* @param remark
* @return
*/
int updateDeviceStatus(@Param("deviceId") String deviceId,
@Param("status") String status,
@Param("remark") String remark);
/**
* 线
* @param deviceId ID
* @return
*/
int markDeviceOnline(@Param("deviceId") String deviceId);
/**
* 线
* @param deviceId ID
* @param reason 线
* @return
*/
int markDeviceOffline(@Param("deviceId") String deviceId,
@Param("reason") String reason);
/**
*
* @param deviceId ID
* @param faultType
* @param description
* @return
*/
int markDeviceFault(@Param("deviceId") String deviceId,
@Param("faultType") String faultType,
@Param("description") String description);
/**
*
* @param deviceIds ID
* @param status
* @param remark
* @return
*/
int batchUpdateDeviceStatus(@Param("deviceIds") List<String> deviceIds,
@Param("status") String status,
@Param("remark") String remark);
// ========== 设备统计和查询操作 ==========
/**
*
* @param status online/offline/fault/maintenance
* @param areaId ID
* @param deviceType water_maker/water_supply
* @return
*/
List<Device> findByStatus(@Param("status") String status,
@Param("areaId") String areaId,
@Param("deviceType") String deviceType);
/**
*
* @param areaId ID
* @param deviceType
* @return statuscount
*/
List<Map<String, Object>> countByStatus(@Param("areaId") String areaId,
@Param("deviceType") String deviceType);
/**
* ID
* @param areaId ID
* @return
*/
List<Device> findByAreaId(@Param("areaId") String areaId);
/**
*
* @param deviceType water_maker/water_supply
* @return
*/
List<Device> findByDeviceType(@Param("deviceType") String deviceType);
/**
*
* @param location
* @return
*/
List<Device> findByInstallLocationContaining(@Param("location") String location);
/**
*
* @param startDate
* @param endDate
* @return
*/
List<Device> findByInstallDateBetween(@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
// ========== 设备监控相关操作 ==========
/**
* 线
* @param deviceId ID
* @return updated_time
*/
Device getDeviceLastOnlineTime(@Param("deviceId") String deviceId);
/**
* 线
* @param thresholdMinutes 线
* @param areaId ID
* @return 线
*/
List<Device> findOfflineDevicesExceedThreshold(@Param("thresholdMinutes") Integer thresholdMinutes,
@Param("areaId") String areaId);
/**
*
* @param deviceId ID
* @return
*/
int updateLastCommunicationTime(@Param("deviceId") String deviceId);
/**
*
* @param currentDate
* @return
*/
List<Device> findDevicesNeedMaintenance(@Param("currentDate") LocalDate currentDate);
// ========== 设备统计报表相关操作 ==========
/**
*
* @return areaId, areaName, deviceCount
*/
List<Map<String, Object>> countDevicesByArea();
/**
*
* @return deviceType, deviceCount
*/
List<Map<String, Object>> countDevicesByType();
/**
* 线
* @return 线areaId, areaName, totalDevices, onlineDevices, onlineRate
*/
List<Map<String, Object>> getOnlineRateByArea();
/**
*
* @param startDate
* @param endDate
* @return deviceId, deviceName, totalOnlineHours
*/
List<Map<String, Object>> getDeviceRuntimeStats(@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
// ========== 批量操作 ==========
/**
*
* @param devices
* @return
*/
int batchInsert(@Param("devices") List<Device> devices);
/**
*
* @param devices
* @return
*/
int batchUpdate(@Param("devices") List<Device> devices);
// ========== 设备关联查询 ==========
/**
* ID
* @param deviceId ID
* @return
*/
List<Map<String, Object>> findTerminalsByDeviceId(@Param("deviceId") String deviceId);
/**
* ID
* @param deviceId ID
* @param limit
* @return
*/
List<Map<String, Object>> findRecentAlertsByDeviceId(@Param("deviceId") String deviceId,
@Param("limit") Integer limit);
/**
* ID
* @param deviceId ID
* @param limit
* @return
*/
List<Map<String, Object>> findRecentWorkOrdersByDeviceId(@Param("deviceId") String deviceId,
@Param("limit") Integer limit);
// ========== 设备高级搜索 ==========
/**
*
* @param deviceName
* @param deviceType
* @param status
* @param areaId ID
* @param startDate
* @param endDate
* @return
*/
List<Device> searchDevices(@Param("deviceName") String deviceName,
@Param("deviceType") String deviceType,
@Param("status") String status,
@Param("areaId") String areaId,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
// ========== 设备导出相关 ==========
/**
* Excel
* @param conditions
* @return
*/
List<Map<String, Object>> getDeviceExportData(@Param("conditions") Map<String, Object> conditions);
}

@ -1,383 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
设备数据访问SQL映射配置文件
功能定义设备相关的SQL语句和结果映射
用途支持设备信息的CRUD操作、状态管理、统计查询等
对应Java接口com.campus.water.mapper.DeviceMapper
核心功能:
1. 设备基本信息管理
2. 设备状态更新和查询
3. 设备统计和报表生成
4. 设备监控和告警关联
-->
<mapper namespace="com.campus.water.mapper.DeviceMapper">
<!-- 基础字段映射 -->
<resultMap id="BaseResultMap" type="com.campus.water.entity.Device">
<id property="deviceId" column="device_id" />
<result property="deviceName" column="device_name" />
<result property="deviceType" column="device_type" />
<result property="areaId" column="area_id" />
<result property="installLocation" column="install_location" />
<result property="installDate" column="install_date" />
<result property="status" column="status" />
<result property="createTime" column="create_time" />
<result property="updatedTime" column="updated_time" />
<result property="remark" column="remark" />
</resultMap>
<!-- ========== 设备基本信息CRUD操作 ========== -->
<select id="findById" resultMap="BaseResultMap">
SELECT * FROM device WHERE device_id = #{deviceId}
</select>
<select id="findAll" resultMap="BaseResultMap">
SELECT * FROM device ORDER BY create_time DESC
</select>
<select id="findByDeviceNameLike" resultMap="BaseResultMap">
SELECT * FROM device
WHERE device_name LIKE CONCAT('%', #{deviceName}, '%')
ORDER BY device_name
</select>
<insert id="insert" parameterType="com.campus.water.entity.Device">
INSERT INTO device (
device_id, device_name, device_type, area_id,
install_location, install_date, status,
create_time, remark
) VALUES (
#{deviceId}, #{deviceName}, #{deviceType}, #{areaId},
#{installLocation}, #{installDate}, #{status},
#{createTime}, #{remark}
)
</insert>
<update id="update" parameterType="com.campus.water.entity.Device">
UPDATE device
SET device_name = #{deviceName},
device_type = #{deviceType},
area_id = #{areaId},
install_location = #{installLocation},
install_date = #{installDate},
status = #{status},
updated_time = NOW(),
remark = #{remark}
WHERE device_id = #{deviceId}
</update>
<delete id="delete">
DELETE FROM device WHERE device_id = #{deviceId}
</delete>
<!-- ========== 设备状态管理相关操作 ========== -->
<update id="updateDeviceStatus">
UPDATE device
SET status = #{status},
updated_time = NOW(),
remark = CONCAT(IFNULL(remark, ''), ';', #{remark})
WHERE device_id = #{deviceId}
</update>
<update id="markDeviceOnline">
UPDATE device
SET status = 'online',
updated_time = NOW(),
remark = CONCAT(IFNULL(remark, ''), ';标记为在线:', DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'))
WHERE device_id = #{deviceId}
</update>
<update id="markDeviceOffline">
UPDATE device
SET status = 'offline',
updated_time = NOW(),
remark = CONCAT(IFNULL(remark, ''), ';标记为离线:', DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'), ';原因:', #{reason})
WHERE device_id = #{deviceId}
</update>
<update id="markDeviceFault">
UPDATE device
SET status = 'fault',
updated_time = NOW(),
remark = CONCAT(IFNULL(remark, ''), ';标记为故障:', DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'),
';故障类型:', #{faultType}, ';描述:', #{description})
WHERE device_id = #{deviceId}
</update>
<update id="batchUpdateDeviceStatus">
UPDATE device
SET status = #{status},
updated_time = NOW(),
remark = CONCAT(IFNULL(remark, ''), ';批量更新:', #{remark}, ';时间:', DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'))
WHERE device_id IN
<foreach collection="deviceIds" item="deviceId" open="(" separator="," close=")">
#{deviceId}
</foreach>
</update>
<!-- ========== 设备统计和查询操作 ========== -->
<select id="findByStatus" resultMap="BaseResultMap">
SELECT * FROM device
WHERE status = #{status}
<if test="areaId != null and areaId != ''">
AND area_id = #{areaId}
</if>
<if test="deviceType != null and deviceType != ''">
AND device_type = #{deviceType}
</if>
ORDER BY updated_time DESC
</select>
<select id="countByStatus" resultType="java.util.Map">
SELECT
status,
COUNT(*) as count
FROM device
WHERE 1=1
<if test="areaId != null and areaId != ''">
AND area_id = #{areaId}
</if>
<if test="deviceType != null and deviceType != ''">
AND device_type = #{deviceType}
</if>
GROUP BY status
ORDER BY count DESC
</select>
<select id="findByAreaId" resultMap="BaseResultMap">
SELECT * FROM device
WHERE area_id = #{areaId}
ORDER BY device_name
</select>
<select id="findByDeviceType" resultMap="BaseResultMap">
SELECT * FROM device
WHERE device_type = #{deviceType}
ORDER BY device_name
</select>
<select id="findByInstallLocationContaining" resultMap="BaseResultMap">
SELECT * FROM device
WHERE install_location LIKE CONCAT('%', #{location}, '%')
ORDER BY install_location
</select>
<select id="findByInstallDateBetween" resultMap="BaseResultMap">
SELECT * FROM device
WHERE install_date BETWEEN #{startDate} AND #{endDate}
ORDER BY install_date DESC
</select>
<!-- ========== 设备监控相关操作 ========== -->
<select id="getDeviceLastOnlineTime" resultMap="BaseResultMap">
SELECT device_id, updated_time
FROM device
WHERE device_id = #{deviceId}
</select>
<select id="findOfflineDevicesExceedThreshold" resultMap="BaseResultMap">
SELECT d.*
FROM device d
WHERE d.status = 'offline'
AND d.updated_time &lt; DATE_SUB(NOW(), INTERVAL #{thresholdMinutes} MINUTE)
<if test="areaId != null and areaId != ''">
AND d.area_id = #{areaId}
</if>
ORDER BY d.updated_time ASC
</select>
<update id="updateLastCommunicationTime">
UPDATE device
SET updated_time = NOW(),
status = 'online'
WHERE device_id = #{deviceId}
</update>
<select id="findDevicesNeedMaintenance" resultMap="BaseResultMap">
SELECT d.*
FROM device d
LEFT JOIN maintenance_plan mp ON d.device_id = mp.device_id
WHERE mp.next_maintenance_date &lt;= #{currentDate}
AND mp.plan_status = 'active'
ORDER BY mp.next_maintenance_date ASC
</select>
<!-- ========== 设备统计报表相关操作 ========== -->
<select id="countDevicesByArea" resultType="java.util.Map">
SELECT
a.area_id,
a.area_name,
COUNT(d.device_id) as device_count
FROM area a
LEFT JOIN device d ON a.area_id = d.area_id
GROUP BY a.area_id, a.area_name
ORDER BY device_count DESC
</select>
<select id="countDevicesByType" resultType="java.util.Map">
SELECT
device_type,
COUNT(*) as device_count
FROM device
GROUP BY device_type
ORDER BY device_count DESC
</select>
<select id="getOnlineRateByArea" resultType="java.util.Map">
SELECT
a.area_id,
a.area_name,
COUNT(d.device_id) as total_devices,
SUM(CASE WHEN d.status = 'online' THEN 1 ELSE 0 END) as online_devices,
ROUND(SUM(CASE WHEN d.status = 'online' THEN 1 ELSE 0 END) * 100.0 / COUNT(d.device_id), 2) as online_rate
FROM area a
LEFT JOIN device d ON a.area_id = d.area_id
GROUP BY a.area_id, a.area_name
ORDER BY online_rate DESC
</select>
<select id="getDeviceRuntimeStats" resultType="java.util.Map">
SELECT
d.device_id,
d.device_name,
COUNT(DISTINCT DATE(wmd.record_time)) as online_days,
SUM(TIMESTAMPDIFF(HOUR,
CASE WHEN wmd.status = 'online' THEN wmd.record_time ELSE NULL END,
LEAD(wmd.record_time) OVER (PARTITION BY d.device_id ORDER BY wmd.record_time)
)) as total_online_hours
FROM device d
LEFT JOIN water_maker_realtime_data wmd ON d.device_id = wmd.device_id
WHERE DATE(wmd.record_time) BETWEEN #{startDate} AND #{endDate}
GROUP BY d.device_id, d.device_name
ORDER BY total_online_hours DESC
</select>
<!-- ========== 批量操作 ========== -->
<insert id="batchInsert" parameterType="list">
INSERT INTO device (
device_id, device_name, device_type, area_id,
install_location, install_date, status,
create_time, remark
) VALUES
<foreach collection="devices" item="device" separator=",">
(
#{device.deviceId}, #{device.deviceName}, #{device.deviceType}, #{device.areaId},
#{device.installLocation}, #{device.installDate}, #{device.status},
#{device.createTime}, #{device.remark}
)
</foreach>
</insert>
<!-- ========== 设备关联查询 ========== -->
<select id="findTerminalsByDeviceId" resultType="java.util.Map">
SELECT
dtm.terminal_id,
dtm.terminal_name,
dtm.terminal_status,
dtm.install_date
FROM device_terminal_mapping dtm
WHERE dtm.device_id = #{deviceId}
ORDER BY dtm.terminal_name
</select>
<select id="findRecentAlertsByDeviceId" resultType="java.util.Map">
SELECT
a.alert_id,
a.alert_type,
a.alert_level,
a.alert_message,
a.timestamp,
a.status
FROM alert a
WHERE a.device_id = #{deviceId}
ORDER BY a.timestamp DESC
LIMIT #{limit}
</select>
<select id="findRecentWorkOrdersByDeviceId" resultType="java.util.Map">
SELECT
wo.order_id,
wo.order_type,
wo.description,
wo.status,
wo.created_time,
wo.completed_time
FROM work_order wo
WHERE wo.device_id = #{deviceId}
ORDER BY wo.created_time DESC
LIMIT #{limit}
</select>
<!-- ========== 设备高级搜索 ========== -->
<select id="searchDevices" resultMap="BaseResultMap">
SELECT * FROM device
WHERE 1=1
<if test="deviceName != null and deviceName != ''">
AND device_name LIKE CONCAT('%', #{deviceName}, '%')
</if>
<if test="deviceType != null and deviceType != ''">
AND device_type = #{deviceType}
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="areaId != null and areaId != ''">
AND area_id = #{areaId}
</if>
<if test="startDate != null">
AND install_date >= #{startDate}
</if>
<if test="endDate != null">
AND install_date &lt;= #{endDate}
</if>
ORDER BY create_time DESC
</select>
<!-- ========== 设备导出相关 ========== -->
<select id="getDeviceExportData" resultType="java.util.Map">
SELECT
d.device_id as "设备ID",
d.device_name as "设备名称",
d.device_type as "设备类型",
a.area_name as "所属区域",
d.install_location as "安装位置",
d.install_date as "安装日期",
d.status as "设备状态",
d.create_time as "创建时间",
d.updated_time as "更新时间",
COUNT(DISTINCT dtm.terminal_id) as "终端数量",
COUNT(DISTINCT wo.order_id) as "工单数量",
COUNT(DISTINCT al.alert_id) as "告警数量"
FROM device d
LEFT JOIN area a ON d.area_id = a.area_id
LEFT JOIN device_terminal_mapping dtm ON d.device_id = dtm.device_id
LEFT JOIN work_order wo ON d.device_id = wo.device_id
LEFT JOIN alert al ON d.device_id = al.device_id
WHERE 1=1
<if test="conditions.areaId != null">
AND d.area_id = #{conditions.areaId}
</if>
<if test="conditions.deviceType != null">
AND d.device_type = #{conditions.deviceType}
</if>
<if test="conditions.status != null">
AND d.status = #{conditions.status}
</if>
GROUP BY d.device_id, d.device_name, d.device_type, a.area_name,
d.install_location, d.install_date, d.status,
d.create_time, d.updated_time
ORDER BY d.create_time DESC
</select>
</mapper>

@ -0,0 +1,33 @@
package com.campus.water.mapper;
import com.campus.water.entity.po.DevicePO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Repository
// 注意DevicePO的主键类型是Longid字段因此这里泛型第二参数改为Long
public interface DevicePOMapper extends JpaRepository<DevicePO, Long> {
// 根据设备唯一标识deviceId查询注意区分主键id和业务字段deviceId
DevicePO findByDeviceId(String deviceId);
// 根据状态字符串查询参数类型为String匹配DevicePO的status字段
List<DevicePO> findByStatus(String status);
// 更新设备状态状态参数类型改为String
@Modifying
@Transactional
@Query("UPDATE DevicePO d SET d.status = ?2, d.lastActiveTime = ?3 WHERE d.deviceId = ?1")
void updateDeviceStatus(String deviceId, String status, LocalDateTime lastActiveTime);
// 补充常用查询按区域ID查询设备
List<DevicePO> findByAreaId(String areaId);
// 补充:按区域和状态查询
List<DevicePO> findByAreaIdAndStatus(String areaId, String status);
}

@ -1,102 +0,0 @@
/**
* MyBatis Mapper
*
*
*
* - //
* - /
* -
* StatisticsMapper.xmlSQL
*/
package com.campus.water.mapper;
import com.campus.water.entity.dto.request.StatisticsQueryRequest;
import com.campus.water.entity.vo.StatisticsVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Mapper
public interface StatisticsMapper {
// 按设备统计用水量
List<Map<String, Object>> statWaterUsageByDevice(
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate,
@Param("areaId") String areaId,
@Param("limit") Integer limit);
// 按区域统计用水量
List<Map<String, Object>> statWaterUsageByArea(
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate,
@Param("deviceType") String deviceType,
@Param("limit") Integer limit);
// 按时间段统计用水量(日/周/月)
List<Map<String, Object>> statWaterUsageByTime(
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate,
@Param("period") String period, // day/week/month
@Param("deviceId") String deviceId,
@Param("areaId") String areaId);
// 按设备统计告警次数
List<Map<String, Object>> statAlarmCountByDevice(
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate,
@Param("areaId") String areaId,
@Param("alarmLevel") String alarmLevel,
@Param("limit") Integer limit);
// 按区域统计告警次数
List<Map<String, Object>> statAlarmCountByArea(
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate,
@Param("deviceType") String deviceType,
@Param("limit") Integer limit);
// 按时间段统计告警趋势
List<Map<String, Object>> statAlarmTrendByTime(
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate,
@Param("period") String period,
@Param("deviceId") String deviceId,
@Param("areaId") String areaId);
// 统计设备使用情况(使用次数、总用水量)
List<Map<String, Object>> statDeviceUsage(
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate,
@Param("areaId") String areaId,
@Param("deviceType") String deviceType,
@Param("limit") Integer limit);
// 统计终端使用情况
List<Map<String, Object>> statTerminalUsage(
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate,
@Param("areaId") String areaId,
@Param("limit") Integer limit);
// 统计水质达标率
List<Map<String, Object>> statWaterQualityRate(
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate,
@Param("deviceId") String deviceId,
@Param("areaId") String areaId);
// 获取设备状态统计
Map<String, Object> getDeviceStatusStatistics(
@Param("areaId") String areaId,
@Param("deviceType") String deviceType);
// 获取告警处理统计
Map<String, Object> getAlarmHandleStatistics(
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate,
@Param("areaId") String areaId);
}

@ -1,251 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.campus.water.mapper.StatisticsMapper">
<!--
统计查询SQL映射配置文件
功能定义统计数据查询的SQL语句和结果映射
用途:支持复杂的多表关联和聚合查询
核心SQL
1. 用水量统计:按设备、区域、时间段分组聚合
2. 告警统计:按设备、区域、告警级别统计
3. 设备状态统计:在线率、故障率等指标计算
4. 告警处理统计:响应时间、处理效率分析
-->
<!-- 按设备统计用水量 -->
<select id="statWaterUsageByDevice" resultType="java.util.Map">
SELECT
dr.device_id as deviceId,
d.device_name as deviceName,
COUNT(dr.record_id) as usageCount,
SUM(dr.water_consumption) as totalWaterOutput,
AVG(dr.water_consumption) as avgWaterPerUse,
MAX(dr.drink_time) as lastUsageTime
FROM drink_record dr
LEFT JOIN device d ON dr.device_id = d.device_id
WHERE 1=1
<if test="startDate != null">
AND DATE(dr.drink_time) >= #{startDate}
</if>
<if test="endDate != null">
AND DATE(dr.drink_time) <= #{endDate}
</if>
<if test="areaId != null and areaId != ''">
AND d.area_id = #{areaId}
</if>
GROUP BY dr.device_id, d.device_name
ORDER BY totalWaterOutput DESC
<if test="limit != null">
LIMIT #{limit}
</if>
</select>
<!-- 按区域统计用水量 -->
<select id="statWaterUsageByArea" resultType="java.util.Map">
SELECT
a.area_id as areaId,
a.area_name as areaName,
COUNT(dr.record_id) as usageCount,
SUM(dr.water_consumption) as totalWaterOutput,
COUNT(DISTINCT dr.device_id) as deviceCount
FROM drink_record dr
LEFT JOIN device d ON dr.device_id = d.device_id
LEFT JOIN area a ON d.area_id = a.area_id
WHERE 1=1
<if test="startDate != null">
AND DATE(dr.drink_time) >= #{startDate}
</if>
<if test="endDate != null">
AND DATE(dr.drink_time) <= #{endDate}
</if>
<if test="deviceType != null and deviceType != ''">
AND d.device_type = #{deviceType}
</if>
AND a.area_id IS NOT NULL
GROUP BY a.area_id, a.area_name
ORDER BY totalWaterOutput DESC
<if test="limit != null">
LIMIT #{limit}
</if>
</select>
<!-- 按时间段统计用水量 -->
<select id="statWaterUsageByTime" resultType="java.util.Map">
<choose>
<when test="period == 'day'">
SELECT
DATE_FORMAT(dr.drink_time, '%Y-%m-%d') as timeLabel,
COUNT(dr.record_id) as usageCount,
SUM(dr.water_consumption) as totalWaterOutput
FROM drink_record dr
WHERE 1=1
<if test="startDate != null">
AND DATE(dr.drink_time) >= #{startDate}
</if>
<if test="endDate != null">
AND DATE(dr.drink_time) <= #{endDate}
</if>
<if test="deviceId != null and deviceId != ''">
AND dr.device_id = #{deviceId}
</if>
<if test="areaId != null and areaId != ''">
AND dr.device_id IN (
SELECT device_id FROM device WHERE area_id = #{areaId}
)
</if>
GROUP BY DATE_FORMAT(dr.drink_time, '%Y-%m-%d')
ORDER BY timeLabel
</when>
<when test="period == 'week'">
SELECT
CONCAT(YEAR(dr.drink_time), '-W', WEEK(dr.drink_time, 1)) as timeLabel,
COUNT(dr.record_id) as usageCount,
SUM(dr.water_consumption) as totalWaterOutput
FROM drink_record dr
WHERE 1=1
<if test="startDate != null">
AND DATE(dr.drink_time) >= #{startDate}
</if>
<if test="endDate != null">
AND DATE(dr.drink_time) <= #{endDate}
</if>
<if test="deviceId != null and deviceId != ''">
AND dr.device_id = #{deviceId}
</if>
<if test="areaId != null and areaId != ''">
AND dr.device_id IN (
SELECT device_id FROM device WHERE area_id = #{areaId}
)
</if>
GROUP BY YEAR(dr.drink_time), WEEK(dr.drink_time, 1)
ORDER BY timeLabel
</when>
<when test="period == 'month'">
SELECT
DATE_FORMAT(dr.drink_time, '%Y-%m') as timeLabel,
COUNT(dr.record_id) as usageCount,
SUM(dr.water_consumption) as totalWaterOutput
FROM drink_record dr
WHERE 1=1
<if test="startDate != null">
AND DATE(dr.drink_time) >= #{startDate}
</if>
<if test="endDate != null">
AND DATE(dr.drink_time) <= #{endDate}
</if>
<if test="deviceId != null and deviceId != ''">
AND dr.device_id = #{deviceId}
</if>
<if test="areaId != null and areaId != ''">
AND dr.device_id IN (
SELECT device_id FROM device WHERE area_id = #{areaId}
)
</if>
GROUP BY DATE_FORMAT(dr.drink_time, '%Y-%m')
ORDER BY timeLabel
</when>
</choose>
</select>
<!-- 按设备统计告警次数 -->
<select id="statAlarmCountByDevice" resultType="java.util.Map">
SELECT
a.device_id as deviceId,
d.device_name as deviceName,
COUNT(a.alert_id) as totalAlarms,
SUM(CASE WHEN a.status = 'pending' THEN 1 ELSE 0 END) as pendingAlarms,
SUM(CASE WHEN a.status = 'resolved' THEN 1 ELSE 0 END) as resolvedAlarms,
MAX(a.timestamp) as lastAlarmTime
FROM alert a
LEFT JOIN device d ON a.device_id = d.device_id
WHERE 1=1
<if test="startDate != null">
AND DATE(a.timestamp) >= #{startDate}
</if>
<if test="endDate != null">
AND DATE(a.timestamp) <= #{endDate}
</if>
<if test="areaId != null and areaId != ''">
AND d.area_id = #{areaId}
</if>
<if test="alarmLevel != null and alarmLevel != ''">
AND a.alert_level = #{alarmLevel}
</if>
GROUP BY a.device_id, d.device_name
ORDER BY totalAlarms DESC
<if test="limit != null">
LIMIT #{limit}
</if>
</select>
<!-- 按区域统计告警次数 -->
<select id="statAlarmCountByArea" resultType="java.util.Map">
SELECT
a.area_id as areaId,
ar.area_name as areaName,
COUNT(al.alert_id) as totalAlarms,
SUM(CASE WHEN al.status = 'pending' THEN 1 ELSE 0 END) as pendingAlarms,
COUNT(DISTINCT al.device_id) as affectedDeviceCount
FROM alert al
LEFT JOIN device d ON al.device_id = d.device_id
LEFT JOIN area ar ON d.area_id = ar.area_id
WHERE 1=1
<if test="startDate != null">
AND DATE(al.timestamp) >= #{startDate}
</if>
<if test="endDate != null">
AND DATE(al.timestamp) <= #{endDate}
</if>
<if test="deviceType != null and deviceType != ''">
AND d.device_type = #{deviceType}
</if>
AND ar.area_id IS NOT NULL
GROUP BY a.area_id, ar.area_name
ORDER BY totalAlarms DESC
<if test="limit != null">
LIMIT #{limit}
</if>
</select>
<!-- 统计设备状态 -->
<select id="getDeviceStatusStatistics" resultType="java.util.Map">
SELECT
COUNT(*) as totalDevices,
SUM(CASE WHEN status = 'online' THEN 1 ELSE 0 END) as onlineDevices,
SUM(CASE WHEN status = 'offline' THEN 1 ELSE 0 END) as offlineDevices,
SUM(CASE WHEN status = 'fault' THEN 1 ELSE 0 END) as faultDevices,
SUM(CASE WHEN status = 'maintenance' THEN 1 ELSE 0 END) as maintenanceDevices,
ROUND(SUM(CASE WHEN status = 'online' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as onlineRate
FROM device
WHERE 1=1
<if test="areaId != null and areaId != ''">
AND area_id = #{areaId}
</if>
<if test="deviceType != null and deviceType != ''">
AND device_type = #{deviceType}
</if>
</select>
<!-- 统计告警处理情况 -->
<select id="getAlarmHandleStatistics" resultType="java.util.Map">
SELECT
COUNT(*) as totalAlarms,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pendingAlarms,
SUM(CASE WHEN status = 'processing' THEN 1 ELSE 0 END) as processingAlarms,
SUM(CASE WHEN status = 'resolved' THEN 1 ELSE 0 END) as resolvedAlarms,
AVG(TIMESTAMPDIFF(HOUR, timestamp, resolved_time)) as avgResponseHours,
MAX(TIMESTAMPDIFF(HOUR, timestamp, resolved_time)) as maxResponseHours
FROM alert
WHERE 1=1
<if test="startDate != null">
AND DATE(timestamp) >= #{startDate}
</if>
<if test="endDate != null">
AND DATE(timestamp) <= #{endDate}
</if>
<if test="areaId != null and areaId != ''">
AND area_id = #{areaId}
</if>
</select>
</mapper>

@ -0,0 +1,50 @@
package com.campus.water.mapper;
import com.campus.water.entity.DrinkRecord;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
/**
* 访
* JPA
*/
@Repository
public interface StatisticsRepository extends JpaRepository<DrinkRecord, Long> {
/**
*
* @param startDate
* @param endDate
* @return ID
*/
@Query("SELECT d.deviceId, SUM(d.waterConsumption) FROM DrinkRecord d " +
"WHERE d.drinkTime BETWEEN ?1 AND ?2 " +
"GROUP BY d.deviceId ORDER BY SUM(d.waterConsumption) DESC")
List<Object[]> getWaterUsageByDevice(LocalDate startDate, LocalDate endDate);
/**
*
* @param startDate
* @param endDate
* @return ID
*/
@Query("SELECT d.areaId, SUM(r.waterConsumption) FROM Device d " +
"JOIN DrinkRecord r ON d.deviceId = r.deviceId " +
"WHERE r.drinkTime BETWEEN ?1 AND ?2 " +
"GROUP BY d.areaId")
List<Object[]> getWaterUsageByArea(LocalDate startDate, LocalDate endDate);
/**
*
* @param startDate
* @param endDate
* @return
*/
@Query("SELECT DATE(r.drinkTime), SUM(r.waterConsumption) FROM DrinkRecord r " +
"WHERE r.drinkTime BETWEEN ?1 AND ?2 " +
"GROUP BY DATE(r.drinkTime) ORDER BY DATE(r.drinkTime)")
List<Object[]> getDailyWaterUsage(LocalDate startDate, LocalDate endDate);
}

@ -1,828 +0,0 @@
package com.campus.water.service;
import com.campus.water.entity.Alert;
import com.campus.water.entity.WorkOrder;
import com.campus.water.entity.Device;
import com.campus.water.entity.dto.request.AlarmStatisticsRequest;
import com.campus.water.entity.vo.AlarmStatisticsVO;
import com.campus.water.mapper.AlertRepository;
import com.campus.water.mapper.WorkOrderRepository;
import com.campus.water.mapper.DeviceRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import jakarta.persistence.criteria.Predicate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
/**
*
*
*
* 1.
* 2.
* 3.
* 4.
*
* -
* -
* -
* -
* Spring Data JPA
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class AlertService {
private final AlertRepository alertRepository;
private final WorkOrderRepository workOrderRepository;
private final DeviceRepository deviceRepository;
// ========== 告警创建相关方法 ==========
/**
*
* @param deviceId ID
* @param alertType
* @param alertLevel
* @param message
* @param areaId ID
* @return
*/
@Transactional
public Alert createAutoAlert(String deviceId, String alertType,
Alert.AlertLevel alertLevel, String message,
String areaId) {
try {
// 检查是否存在重复未处理告警
if (hasDuplicatePendingAlert(deviceId, alertType)) {
log.info("存在重复未处理告警,跳过创建: deviceId={}, alertType={}", deviceId, alertType);
return null;
}
Alert alert = new Alert();
alert.setDeviceId(deviceId);
alert.setAlertType(alertType);
alert.setAlertLevel(alertLevel);
alert.setAlertMessage(message);
alert.setAreaId(areaId);
alert.setStatus(Alert.AlertStatus.pending);
alert.setTimestamp(LocalDateTime.now());
alert.setCreatedTime(LocalDateTime.now());
Alert savedAlert = alertRepository.save(alert);
log.info("自动告警创建成功: alertId={}, deviceId={}, type={}, level={}",
savedAlert.getAlertId(), deviceId, alertType, alertLevel);
// 根据告警类型自动创建工单
if (shouldCreateWorkOrder(alertType, alertLevel)) {
createWorkOrderForAlert(savedAlert);
}
return savedAlert;
} catch (Exception e) {
log.error("自动告警创建失败: deviceId={}, error={}", deviceId, e.getMessage(), e);
throw new RuntimeException("告警创建失败: " + e.getMessage());
}
}
/**
*
* @param deviceId ID
* @param alertType
* @param alertLevel
* @param message
* @param areaId ID
* @return
*/
@Transactional
public Alert createManualAlert(String deviceId, String alertType,
String alertLevel, String message,
String areaId) {
try {
Alert.AlertLevel level;
try {
level = Alert.AlertLevel.valueOf(alertLevel.toLowerCase());
} catch (IllegalArgumentException e) {
level = Alert.AlertLevel.warning; // 默认级别
}
Alert alert = new Alert();
alert.setDeviceId(deviceId);
alert.setAlertType(alertType);
alert.setAlertLevel(level);
alert.setAlertMessage(message);
alert.setAreaId(areaId);
alert.setStatus(Alert.AlertStatus.pending);
alert.setTimestamp(LocalDateTime.now());
alert.setCreatedTime(LocalDateTime.now());
Alert savedAlert = alertRepository.save(alert);
log.info("手动告警创建成功: alertId={}, deviceId={}, type={}, level={}",
savedAlert.getAlertId(), deviceId, alertType, alertLevel);
// 手动创建的告警默认创建工单
createWorkOrderForAlert(savedAlert);
return savedAlert;
} catch (Exception e) {
log.error("手动告警创建失败: deviceId={}, error={}", deviceId, e.getMessage(), e);
throw new RuntimeException("告警创建失败: " + e.getMessage());
}
}
/**
*
* @param alerts
* @return
*/
@Transactional
public List<Alert> batchCreateAlerts(List<Alert> alerts) {
try {
List<Alert> savedAlerts = alertRepository.saveAll(alerts);
log.info("批量创建告警成功: count={}", savedAlerts.size());
// 为每个告警创建工单
for (Alert alert : savedAlerts) {
if (shouldCreateWorkOrder(alert.getAlertType(), alert.getAlertLevel())) {
createWorkOrderForAlert(alert);
}
}
return savedAlerts;
} catch (Exception e) {
log.error("批量创建告警失败: error={}", e.getMessage(), e);
throw new RuntimeException("批量创建告警失败: " + e.getMessage());
}
}
// ========== 告警处理相关方法 ==========
/**
*
* @param alertId ID
* @param repairmanId ID
* @return
*/
@Transactional
public boolean processAlert(Long alertId, String repairmanId) {
try {
Optional<Alert> alertOpt = alertRepository.findById(alertId);
if (alertOpt.isEmpty()) {
log.warn("告警不存在: alertId={}", alertId);
return false;
}
Alert alert = alertOpt.get();
if (alert.getStatus() != Alert.AlertStatus.pending) {
log.warn("告警状态不允许处理: alertId={}, currentStatus={}", alertId, alert.getStatus());
return false;
}
alert.setStatus(Alert.AlertStatus.processing);
alert.setResolvedBy(repairmanId);
alert.setUpdatedTime(LocalDateTime.now());
alertRepository.save(alert);
log.info("告警开始处理: alertId={}, repairmanId={}", alertId, repairmanId);
return true;
} catch (Exception e) {
log.error("处理告警失败: alertId={}, error={}", alertId, e.getMessage(), e);
throw new RuntimeException("处理告警失败: " + e.getMessage());
}
}
/**
*
* @param alertId ID
* @param resolvedBy
* @param resolutionNotes
* @return
*/
@Transactional
public boolean resolveAlert(Long alertId, String resolvedBy, String resolutionNotes) {
try {
Optional<Alert> alertOpt = alertRepository.findById(alertId);
if (alertOpt.isEmpty()) {
log.warn("告警不存在: alertId={}", alertId);
return false;
}
Alert alert = alertOpt.get();
if (alert.getStatus() != Alert.AlertStatus.processing &&
alert.getStatus() != Alert.AlertStatus.pending) {
log.warn("告警状态不允许解决: alertId={}, currentStatus={}", alertId, alert.getStatus());
return false;
}
alert.setStatus(Alert.AlertStatus.resolved);
alert.setResolvedBy(resolvedBy);
alert.setResolvedTime(LocalDateTime.now());
alert.setAlertMessage(alert.getAlertMessage() + " [解决方案: " + resolutionNotes + "]");
alert.setUpdatedTime(LocalDateTime.now());
alertRepository.save(alert);
log.info("告警已解决: alertId={}, resolvedBy={}", alertId, resolvedBy);
return true;
} catch (Exception e) {
log.error("解决告警失败: alertId={}, error={}", alertId, e.getMessage(), e);
throw new RuntimeException("解决告警失败: " + e.getMessage());
}
}
/**
*
* @param alertId ID
* @param closedBy
* @param closeReason
* @return
*/
@Transactional
public boolean closeAlert(Long alertId, String closedBy, String closeReason) {
try {
Optional<Alert> alertOpt = alertRepository.findById(alertId);
if (alertOpt.isEmpty()) {
log.warn("告警不存在: alertId={}", alertId);
return false;
}
Alert alert = alertOpt.get();
alert.setStatus(Alert.AlertStatus.closed);
alert.setResolvedBy(closedBy);
alert.setResolvedTime(LocalDateTime.now());
alert.setAlertMessage(alert.getAlertMessage() + " [关闭原因: " + closeReason + "]");
alert.setUpdatedTime(LocalDateTime.now());
alertRepository.save(alert);
log.info("告警已关闭: alertId={}, closedBy={}, reason={}", alertId, closedBy, closeReason);
return true;
} catch (Exception e) {
log.error("关闭告警失败: alertId={}, error={}", alertId, e.getMessage(), e);
throw new RuntimeException("关闭告警失败: " + e.getMessage());
}
}
/**
*
* @param alertIds ID
* @param status
* @param updatedBy
* @return
*/
@Transactional
public int batchUpdateAlertStatus(List<Long> alertIds, Alert.AlertStatus status, String updatedBy) {
try {
List<Alert> alerts = alertRepository.findAllById(alertIds);
for (Alert alert : alerts) {
alert.setStatus(status);
alert.setResolvedBy(updatedBy);
if (status == Alert.AlertStatus.resolved || status == Alert.AlertStatus.closed) {
alert.setResolvedTime(LocalDateTime.now());
}
alert.setUpdatedTime(LocalDateTime.now());
}
alertRepository.saveAll(alerts);
log.info("批量更新告警状态: count={}, status={}, updatedBy={}",
alerts.size(), status, updatedBy);
return alerts.size();
} catch (Exception e) {
log.error("批量更新告警状态失败: error={}", e.getMessage(), e);
throw new RuntimeException("批量更新告警状态失败: " + e.getMessage());
}
}
// ========== 告警查询相关方法 ==========
/**
* ID
* @param alertId ID
* @return Optional
*/
@Transactional(readOnly = true)
public Optional<Alert> findById(Long alertId) {
return alertRepository.findById(alertId);
}
/**
*
* @param deviceId ID
* @param alertType
* @param alertLevel
* @param status
* @param areaId ID
* @param startTime
* @param endTime
* @param pageable
* @return
*/
@Transactional(readOnly = true)
public Page<Alert> findAlertsByConditions(String deviceId, String alertType,
Alert.AlertLevel alertLevel, Alert.AlertStatus status,
String areaId, LocalDateTime startTime,
LocalDateTime endTime, Pageable pageable) {
Specification<Alert> spec = (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (StringUtils.hasText(deviceId)) {
predicates.add(criteriaBuilder.equal(root.get("deviceId"), deviceId));
}
if (StringUtils.hasText(alertType)) {
predicates.add(criteriaBuilder.equal(root.get("alertType"), alertType));
}
if (alertLevel != null) {
predicates.add(criteriaBuilder.equal(root.get("alertLevel"), alertLevel));
}
if (status != null) {
predicates.add(criteriaBuilder.equal(root.get("status"), status));
}
if (StringUtils.hasText(areaId)) {
predicates.add(criteriaBuilder.equal(root.get("areaId"), areaId));
}
if (startTime != null) {
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("timestamp"), startTime));
}
if (endTime != null) {
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("timestamp"), endTime));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
return alertRepository.findAll(spec, pageable);
}
/**
*
* @param deviceId ID
* @param limit
* @return
*/
@Transactional(readOnly = true)
public List<Alert> findRecentAlertsByDeviceId(String deviceId, int limit) {
return alertRepository.findByDeviceIdOrderByTimestampDesc(deviceId)
.stream()
.limit(limit)
.collect(Collectors.toList());
}
/**
*
* @param areaId ID
* @return
*/
@Transactional(readOnly = true)
public List<Alert> findPendingAlerts(String areaId) {
if (StringUtils.hasText(areaId)) {
return alertRepository.findByStatusAndAreaId(Alert.AlertStatus.pending, areaId);
} else {
return alertRepository.findByStatus(Alert.AlertStatus.pending);
}
}
/**
*
* @param repairmanId ID
* @return
*/
@Transactional(readOnly = true)
public List<Alert> findProcessingAlerts(String repairmanId) {
if (StringUtils.hasText(repairmanId)) {
return alertRepository.findByStatusAndResolvedBy(Alert.AlertStatus.processing, repairmanId);
} else {
return alertRepository.findByStatus(Alert.AlertStatus.processing);
}
}
// ========== 告警统计相关方法 ==========
/**
*
* @param areaId ID
* @param startTime
* @param endTime
* @return
*/
@Transactional(readOnly = true)
public Map<String, Object> getAlertOverview(String areaId, LocalDateTime startTime, LocalDateTime endTime) {
Map<String, Object> overview = new HashMap<>();
// 获取统计时间段内的告警
List<Alert> alerts;
if (startTime != null && endTime != null) {
alerts = alertRepository.findByTimestampBetween(startTime, endTime);
} else if (startTime != null) {
alerts = alertRepository.findByTimestampAfter(startTime);
} else {
alerts = alertRepository.findAll();
}
// 按区域过滤
if (StringUtils.hasText(areaId)) {
alerts = alerts.stream()
.filter(alert -> areaId.equals(alert.getAreaId()))
.collect(Collectors.toList());
}
// 计算统计指标
long totalAlerts = alerts.size();
long pendingAlerts = alerts.stream()
.filter(a -> a.getStatus() == Alert.AlertStatus.pending)
.count();
long processingAlerts = alerts.stream()
.filter(a -> a.getStatus() == Alert.AlertStatus.processing)
.count();
long resolvedAlerts = alerts.stream()
.filter(a -> a.getStatus() == Alert.AlertStatus.resolved)
.count();
// 计算平均响应时间(小时)
double avgResponseHours = alerts.stream()
.filter(a -> a.getResolvedTime() != null && a.getTimestamp() != null)
.mapToDouble(a -> ChronoUnit.HOURS.between(a.getTimestamp(), a.getResolvedTime()))
.average()
.orElse(0.0);
// 按级别统计
Map<Alert.AlertLevel, Long> levelCount = alerts.stream()
.collect(Collectors.groupingBy(Alert::getAlertLevel, Collectors.counting()));
// 按类型统计
Map<String, Long> typeCount = alerts.stream()
.collect(Collectors.groupingBy(Alert::getAlertType, Collectors.counting()));
overview.put("totalAlerts", totalAlerts);
overview.put("pendingAlerts", pendingAlerts);
overview.put("processingAlerts", processingAlerts);
overview.put("resolvedAlerts", resolvedAlerts);
overview.put("resolvedRate", totalAlerts > 0 ? (double) resolvedAlerts / totalAlerts * 100 : 0);
overview.put("avgResponseHours", avgResponseHours);
overview.put("alertLevelCount", levelCount);
overview.put("alertTypeCount", typeCount);
return overview;
}
/**
*
* @param period day/week/month
* @param startTime
* @param endTime
* @param areaId ID
* @return
*/
@Transactional(readOnly = true)
public List<Map<String, Object>> getAlertTrend(String period,
LocalDateTime startTime,
LocalDateTime endTime,
String areaId) {
List<Alert> alerts = alertRepository.findByTimestampBetween(startTime, endTime);
if (StringUtils.hasText(areaId)) {
alerts = alerts.stream()
.filter(alert -> areaId.equals(alert.getAreaId()))
.collect(Collectors.toList());
}
// 按时间分组
Map<String, List<Alert>> groupedAlerts = new TreeMap<>();
for (Alert alert : alerts) {
String timeKey = formatTimeKey(alert.getTimestamp(), period);
groupedAlerts.computeIfAbsent(timeKey, k -> new ArrayList<>()).add(alert);
}
// 构建结果
List<Map<String, Object>> result = new ArrayList<>();
for (Map.Entry<String, List<Alert>> entry : groupedAlerts.entrySet()) {
Map<String, Object> item = new HashMap<>();
item.put("timeLabel", entry.getKey());
item.put("totalAlerts", entry.getValue().size());
// 按级别统计
Map<Alert.AlertLevel, Long> levelCount = entry.getValue().stream()
.collect(Collectors.groupingBy(Alert::getAlertLevel, Collectors.counting()));
item.put("alertLevelCount", levelCount);
result.add(item);
}
return result;
}
/**
*
* @param topN N
* @param startTime
* @param endTime
* @return
*/
@Transactional(readOnly = true)
public List<Map<String, Object>> getDeviceAlertRanking(int topN,
LocalDateTime startTime,
LocalDateTime endTime) {
List<Alert> alerts;
if (startTime != null && endTime != null) {
alerts = alertRepository.findByTimestampBetween(startTime, endTime);
} else {
alerts = alertRepository.findAll();
}
// 按设备分组统计
Map<String, List<Alert>> deviceAlerts = alerts.stream()
.collect(Collectors.groupingBy(Alert::getDeviceId));
// 获取设备信息
List<Map<String, Object>> ranking = new ArrayList<>();
for (Map.Entry<String, List<Alert>> entry : deviceAlerts.entrySet()) {
String deviceId = entry.getKey();
List<Alert> deviceAlertList = entry.getValue();
Optional<Device> deviceOpt = deviceRepository.findById(deviceId);
String deviceName = deviceOpt.map(Device::getDeviceName).orElse("未知设备");
Map<String, Object> item = new HashMap<>();
item.put("deviceId", deviceId);
item.put("deviceName", deviceName);
item.put("totalAlerts", deviceAlertList.size());
item.put("pendingAlerts", deviceAlertList.stream()
.filter(a -> a.getStatus() == Alert.AlertStatus.pending)
.count());
item.put("resolvedAlerts", deviceAlertList.stream()
.filter(a -> a.getStatus() == Alert.AlertStatus.resolved)
.count());
// 计算最常见的告警类型
String mostCommonType = deviceAlertList.stream()
.collect(Collectors.groupingBy(Alert::getAlertType, Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse("未知");
item.put("mostCommonAlertType", mostCommonType);
ranking.add(item);
}
// 按告警总数排序并限制数量
return ranking.stream()
.sorted((a, b) -> Integer.compare(
(Integer) b.get("totalAlerts"),
(Integer) a.get("totalAlerts")))
.limit(topN)
.collect(Collectors.toList());
}
// ========== 告警分析相关方法 ==========
/**
*
* @param startTime
* @param endTime
* @param areaId ID
* @return
*/
@Transactional(readOnly = true)
public List<Map<String, Object>> analyzeAlertPatterns(LocalDateTime startTime,
LocalDateTime endTime,
String areaId) {
List<Alert> alerts = alertRepository.findByTimestampBetween(startTime, endTime);
if (StringUtils.hasText(areaId)) {
alerts = alerts.stream()
.filter(alert -> areaId.equals(alert.getAreaId()))
.collect(Collectors.toList());
}
// 按设备和时间窗口分组
Map<String, Map<String, List<Alert>>> deviceTimeAlerts = new HashMap<>();
for (Alert alert : alerts) {
String deviceId = alert.getDeviceId();
String timeWindow = formatTimeWindow(alert.getTimestamp());
deviceTimeAlerts.computeIfAbsent(deviceId, k -> new HashMap<>())
.computeIfAbsent(timeWindow, k -> new ArrayList<>())
.add(alert);
}
// 分析同一时间窗口内的告警组合
List<Map<String, Object>> patterns = new ArrayList<>();
for (Map<String, List<Alert>> timeAlerts : deviceTimeAlerts.values()) {
for (List<Alert> windowAlerts : timeAlerts.values()) {
if (windowAlerts.size() >= 2) {
// 发现多个告警同时出现
Set<String> alertTypes = windowAlerts.stream()
.map(Alert::getAlertType)
.collect(Collectors.toSet());
if (alertTypes.size() > 1) {
Map<String, Object> pattern = new HashMap<>();
pattern.put("alertTypes", alertTypes);
pattern.put("count", windowAlerts.size());
pattern.put("deviceIds", deviceTimeAlerts.keySet());
pattern.put("timestamp", windowAlerts.get(0).getTimestamp());
patterns.add(pattern);
}
}
}
}
return patterns;
}
/**
*
* @param deviceId ID
* @return
*/
@Transactional(readOnly = true)
public Map<String, Object> predictAlertRisk(String deviceId) {
Optional<Device> deviceOpt = deviceRepository.findById(deviceId);
if (deviceOpt.isEmpty()) {
return Collections.emptyMap();
}
Device device = deviceOpt.get();
LocalDateTime now = LocalDateTime.now();
LocalDateTime oneMonthAgo = now.minusMonths(1);
// 获取最近一个月的告警
List<Alert> recentAlerts = alertRepository.findByDeviceIdAndTimestampAfter(deviceId, oneMonthAgo);
Map<String, Object> prediction = new HashMap<>();
prediction.put("deviceId", deviceId);
prediction.put("deviceName", device.getDeviceName());
prediction.put("deviceStatus", device.getStatus());
// 计算告警频率
long alertCount = recentAlerts.size();
double alertsPerDay = alertCount / 30.0;
prediction.put("alertFrequency", alertsPerDay);
// 风险等级评估
String riskLevel;
if (alertsPerDay >= 1.0) {
riskLevel = "高风险";
} else if (alertsPerDay >= 0.5) {
riskLevel = "中风险";
} else if (alertsPerDay >= 0.1) {
riskLevel = "低风险";
} else {
riskLevel = "无风险";
}
prediction.put("riskLevel", riskLevel);
// 最常见的告警类型
String mostCommonType = recentAlerts.stream()
.collect(Collectors.groupingBy(Alert::getAlertType, Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse("无");
prediction.put("mostCommonAlertType", mostCommonType);
// 平均解决时间
double avgResolveHours = recentAlerts.stream()
.filter(a -> a.getResolvedTime() != null)
.mapToDouble(a -> ChronoUnit.HOURS.between(a.getTimestamp(), a.getResolvedTime()))
.average()
.orElse(0.0);
prediction.put("avgResolveHours", avgResolveHours);
return prediction;
}
// ========== 私有辅助方法 ==========
/**
*
*/
private boolean hasDuplicatePendingAlert(String deviceId, String alertType) {
List<Alert> pendingAlerts = alertRepository.findByDeviceIdAndAlertTypeAndStatus(
deviceId, alertType, Alert.AlertStatus.pending);
if (!pendingAlerts.isEmpty()) {
// 检查是否有30分钟内的重复告警
LocalDateTime thirtyMinutesAgo = LocalDateTime.now().minusMinutes(30);
return pendingAlerts.stream()
.anyMatch(alert -> alert.getTimestamp().isAfter(thirtyMinutesAgo));
}
return false;
}
/**
*
*/
private boolean shouldCreateWorkOrder(String alertType, Alert.AlertLevel alertLevel) {
// 严重级别以上的告警都需要创建工单
if (alertLevel == Alert.AlertLevel.critical || alertLevel == Alert.AlertLevel.error) {
return true;
}
// 特定类型的告警需要工单
Set<String> workOrderRequiredTypes = Set.of(
"WATER_MAKER_FAULT",
"WATER_SUPPLY_FAULT",
"DEVICE_FAULT",
"SAFETY_ALERT"
);
return workOrderRequiredTypes.contains(alertType);
}
/**
*
*/
private void createWorkOrderForAlert(Alert alert) {
try {
WorkOrder workOrder = new WorkOrder();
workOrder.setOrderId(generateOrderId());
workOrder.setAlertId(alert.getAlertId());
workOrder.setDeviceId(alert.getDeviceId());
workOrder.setAreaId(alert.getAreaId());
workOrder.setOrderType(WorkOrder.OrderType.repair);
workOrder.setDescription("告警处理工单: " + alert.getAlertMessage());
workOrder.setPriority(convertAlertLevelToPriority(alert.getAlertLevel()));
workOrder.setStatus(WorkOrder.OrderStatus.pending);
workOrder.setCreatedTime(LocalDateTime.now());
workOrderRepository.save(workOrder);
log.info("为告警创建工单成功: alertId={}, orderId={}",
alert.getAlertId(), workOrder.getOrderId());
} catch (Exception e) {
log.error("为告警创建工单失败: alertId={}, error={}",
alert.getAlertId(), e.getMessage(), e);
}
}
/**
* ID
*/
private String generateOrderId() {
return String.format("WO%s%03d",
System.currentTimeMillis(),
(int)(Math.random() * 1000));
}
/**
*
*/
private WorkOrder.OrderPriority convertAlertLevelToPriority(Alert.AlertLevel alertLevel) {
switch (alertLevel) {
case critical:
return WorkOrder.OrderPriority.urgent;
case error:
return WorkOrder.OrderPriority.high;
case warning:
return WorkOrder.OrderPriority.medium;
default:
return WorkOrder.OrderPriority.low;
}
}
/**
*
*/
private String formatTimeKey(LocalDateTime time, String period) {
switch (period) {
case "day":
return time.toLocalDate().toString();
case "week":
return String.format("%d-W%d",
time.getYear(),
time.get(java.time.temporal.WeekFields.ISO.weekOfYear()));
case "month":
return String.format("%d-%02d",
time.getYear(), time.getMonthValue());
default:
return time.toLocalDate().toString();
}
}
/**
*
*/
private String formatTimeWindow(LocalDateTime time) {
// 按小时窗口分组
return String.format("%s-%02d",
time.toLocalDate().toString(),
time.getHour());
}
}

@ -1,219 +1,146 @@
/**
*
*
*
*
* -
* - 线/线/
* -
* - 线
* -
*
*/
package com.campus.water.service;
import com.campus.water.entity.Device;
import com.campus.water.entity.dto.request.DeviceStatusUpdateRequest;
import com.campus.water.mapper.DeviceMapper;
import com.campus.water.entity.DeviceTerminalMapping;
import com.campus.water.entity.Device.DeviceStatus;
import com.campus.water.entity.Device.DeviceType;
import com.campus.water.entity.DeviceTerminalMapping.TerminalStatus;
import com.campus.water.mapper.DeviceRepository;
import com.campus.water.mapper.DeviceTerminalMappingRepository;
import com.campus.water.util.ResultVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
*
* CRUD
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class DeviceService {
private final DeviceMapper deviceMapper;
private final AlertService alertService;
private final DeviceRepository deviceRepository;
private final DeviceTerminalMappingRepository terminalMappingRepository;
/**
*
* ID
*/
@Transactional
public boolean updateDeviceStatus(DeviceStatusUpdateRequest request) {
try {
int rows = deviceMapper.updateDeviceStatus(
request.getDeviceId(),
request.getStatus(),
request.getRemark()
);
if (rows > 0) {
log.info("设备状态更新成功: deviceId={}, status={}",
request.getDeviceId(), request.getStatus());
// 如果是故障状态,自动创建告警
if ("fault".equals(request.getStatus())) {
createFaultAlert(request.getDeviceId(), request.getRemark());
}
return true;
}
return false;
} catch (Exception e) {
log.error("设备状态更新失败: deviceId={}, error={}",
request.getDeviceId(), e.getMessage(), e);
throw new RuntimeException("设备状态更新失败: " + e.getMessage());
}
public Device getDeviceById(String deviceId) {
return deviceRepository.findById(deviceId)
.orElseThrow(() -> new RuntimeException("设备不存在:" + deviceId));
}
/**
* 线
*
*/
@Transactional
public boolean markDeviceOnline(String deviceId) {
try {
int rows = deviceMapper.markDeviceOnline(deviceId);
if (rows > 0) {
log.info("设备标记为在线: deviceId={}", deviceId);
return true;
}
return false;
} catch (Exception e) {
log.error("标记设备在线失败: deviceId={}, error={}", deviceId, e.getMessage());
throw new RuntimeException("标记设备在线失败: " + e.getMessage());
public Device addDevice(Device device) {
// 检查设备ID是否已存在
if (deviceRepository.existsById(device.getDeviceId())) {
throw new RuntimeException("设备ID已存在" + device.getDeviceId());
}
device.setCreateTime(LocalDateTime.now());
device.setStatus(DeviceStatus.online); // 默认为在线状态
return deviceRepository.save(device);
}
/**
* 线
*
*/
@Transactional
public boolean markDeviceOffline(String deviceId, String reason) {
try {
int rows = deviceMapper.markDeviceOffline(deviceId, reason);
if (rows > 0) {
log.info("设备标记为离线: deviceId={}, reason={}", deviceId, reason);
return true;
}
return false;
} catch (Exception e) {
log.error("标记设备离线失败: deviceId={}, error={}", deviceId, e.getMessage());
throw new RuntimeException("标记设备离线失败: " + e.getMessage());
}
public Device updateDeviceInfo(Device device) {
Device existingDevice = getDeviceById(device.getDeviceId());
// 保留创建时间,更新其他可编辑字段
existingDevice.setDeviceName(device.getDeviceName());
existingDevice.setDeviceType(device.getDeviceType());
existingDevice.setAreaId(device.getAreaId());
existingDevice.setInstallLocation(device.getInstallLocation());
existingDevice.setInstallDate(device.getInstallDate());
return deviceRepository.save(existingDevice);
}
/**
*
* 线/线/
*/
@Transactional
public boolean markDeviceFault(String deviceId, String faultType, String description) {
try {
int rows = deviceMapper.markDeviceFault(deviceId, faultType, description);
if (rows > 0) {
log.info("设备标记为故障: deviceId={}, type={}, desc={}",
deviceId, faultType, description);
// 创建故障告警
createFaultAlert(deviceId, String.format("故障类型: %s, 描述: %s", faultType, description));
return true;
}
return false;
} catch (Exception e) {
log.error("标记设备故障失败: deviceId={}, error={}", deviceId, e.getMessage());
throw new RuntimeException("标记设备故障失败: " + e.getMessage());
}
public boolean updateDeviceStatus(String deviceId, DeviceStatus status) {
Device device = getDeviceById(deviceId);
device.setStatus(status);
deviceRepository.save(device);
return true;
}
/**
*
*/
@Transactional
public boolean batchUpdateDeviceStatus(List<String> deviceIds, String status, String remark) {
try {
if (deviceIds == null || deviceIds.isEmpty()) {
return false;
}
int rows = deviceMapper.batchUpdateDeviceStatus(deviceIds, status, remark);
log.info("批量更新设备状态: count={}, status={}, updated={}",
deviceIds.size(), status, rows);
// 如果是故障状态,为每个设备创建告警
if ("fault".equals(status)) {
for (String deviceId : deviceIds) {
createFaultAlert(deviceId, remark);
}
}
return rows > 0;
} catch (Exception e) {
log.error("批量更新设备状态失败: error={}", e.getMessage(), e);
throw new RuntimeException("批量更新设备状态失败: " + e.getMessage());
public boolean batchUpdateStatus(List<String> deviceIds, DeviceStatus status) {
List<Device> devices = deviceRepository.findAllById(deviceIds);
if (devices.size() != deviceIds.size()) {
throw new RuntimeException("部分设备ID不存在");
}
devices.forEach(device -> device.setStatus(status));
deviceRepository.saveAll(devices);
return true;
}
/**
*
*
*/
@Transactional(readOnly = true)
public Map<String, Object> getDeviceStatusCount(String areaId, String deviceType) {
return deviceMapper.countByStatus(areaId, deviceType);
}
/**
* 线
*/
@Transactional(readOnly = true)
public List<Device> getOfflineDevicesExceedThreshold(Integer thresholdMinutes, String areaId) {
return deviceMapper.findOfflineDevicesExceedThreshold(thresholdMinutes, areaId);
public List<Device> queryDevices(String areaId, DeviceType deviceType, DeviceStatus status) {
if (areaId != null && deviceType != null) {
return deviceRepository.findByAreaIdAndDeviceType(areaId, deviceType);
} else if (areaId != null) {
return deviceRepository.findByAreaId(areaId);
} else if (deviceType != null) {
return deviceRepository.findByDeviceType(deviceType);
} else if (status != null) {
return deviceRepository.findByStatus(status);
} else {
return deviceRepository.findAll();
}
}
/**
* 线
*
*/
@Transactional(readOnly = true)
public LocalDateTime getDeviceLastOnlineTime(String deviceId) {
Device device = deviceMapper.getDeviceLastOnlineTime(deviceId);
return device != null ? device.getUpdatedTime() : null;
}
@Transactional
public DeviceTerminalMapping bindTerminal(String deviceId, String terminalId, String terminalName) {
// 校验设备是否存在
getDeviceById(deviceId);
// 检查终端是否已绑定
Optional<DeviceTerminalMapping> existing = terminalMappingRepository.findByTerminalId(terminalId);
if (existing.isPresent()) {
throw new RuntimeException("终端已绑定设备:" + existing.get().getDeviceId());
}
/**
*
*/
@Transactional(readOnly = true)
public List<Device> getDevicesByStatus(String status, String areaId, String deviceType) {
return deviceMapper.findByStatus(status, areaId, deviceType);
DeviceTerminalMapping mapping = new DeviceTerminalMapping();
mapping.setDeviceId(deviceId);
mapping.setTerminalId(terminalId);
mapping.setTerminalName(terminalName);
mapping.setTerminalStatus(TerminalStatus.active);
mapping.setInstallDate(java.time.LocalDate.now());
return terminalMappingRepository.save(mapping);
}
/**
* 线
*
*/
@Transactional
public void autoDetectOfflineDevices(Integer thresholdMinutes) {
List<Device> offlineDevices = getOfflineDevicesExceedThreshold(thresholdMinutes, null);
for (Device device : offlineDevices) {
markDeviceOffline(device.getDeviceId(),
String.format("自动检测离线,超过%d分钟无数据", thresholdMinutes));
}
if (!offlineDevices.isEmpty()) {
log.warn("自动标记离线设备完成: count={}", offlineDevices.size());
}
public List<DeviceTerminalMapping> getBoundTerminals(String deviceId) {
getDeviceById(deviceId); // 校验设备存在性
return terminalMappingRepository.findByDeviceId(deviceId);
}
/**
*
*
*/
private void createFaultAlert(String deviceId, String description) {
try {
alertService.createManualAlert(
deviceId,
"DEVICE_FAULT",
"设备故障",
String.format("设备故障告警 - 设备ID: %s, 描述: %s", deviceId, description),
"fault"
);
} catch (Exception e) {
log.error("创建故障告警失败: deviceId={}, error={}", deviceId, e.getMessage());
}
public long countByStatus(DeviceStatus status) {
return deviceRepository.findByStatus(status).size();
}
}

@ -0,0 +1,37 @@
// 路径com/campus/water/service/DeviceStatusService.java
package com.campus.water.service;
import com.campus.water.entity.Device;
import com.campus.water.entity.dto.request.DeviceStatusUpdateRequest;
import java.util.List;
import java.util.Map;
public interface DeviceStatusService {
// 更新设备状态
boolean updateDeviceStatus(DeviceStatusUpdateRequest request);
// 标记设备在线
boolean markDeviceOnline(String deviceId);
// 标记设备离线
boolean markDeviceOffline(String deviceId, String reason);
// 标记设备故障
boolean markDeviceFault(String deviceId, String faultType, String description);
// 批量更新设备状态
boolean batchUpdateDeviceStatus(List<String> deviceIds, String status, String remark);
// 按状态查询设备
List<Device> getDevicesByStatus(String status, String areaId, String deviceType);
// 统计各状态设备数量
Map<String, Object> getDeviceStatusCount(String areaId, String deviceType);
// 获取超过阈值的离线设备
List<Device> getOfflineDevicesExceedThreshold(Integer thresholdMinutes, String areaId);
// 自动检测离线设备
void autoDetectOfflineDevices(Integer thresholdMinutes);
}

@ -1,247 +1,158 @@
/**
*
*
*
*
* - getWaterUsageStatistics():
* - getAlarmStatistics():
* - getDeviceStatusStatistics():
* - getDashboardStatistics():
*
*/
package com.campus.water.service;
import com.campus.water.entity.Alert;
import com.campus.water.entity.Device;
import com.campus.water.entity.TerminalUsageStats;
import com.campus.water.entity.dto.request.StatisticsQueryRequest;
import com.campus.water.entity.vo.AlarmStatisticsVO;
import com.campus.water.entity.vo.StatisticsVO;
import com.campus.water.mapper.StatisticsMapper;
import com.campus.water.mapper.AlertRepository;
import com.campus.water.mapper.DeviceRepository;
import com.campus.water.mapper.TerminalUsageStatsRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
public class StatisticsService {
private final StatisticsMapper statisticsMapper;
private final DeviceService deviceService;
private final TerminalUsageStatsRepository terminalUsageStatsRepository;
private final DeviceRepository deviceRepository;
private final AlertRepository alertRepository;
/**
*
* //
*/
@Transactional(readOnly = true)
public StatisticsVO getWaterUsageStatistics(StatisticsQueryRequest request) {
StatisticsVO result = new StatisticsVO();
result.setPeriod(request.getPeriod());
result.setStartDate(request.getStartDate());
result.setEndDate(request.getEndDate());
List<Map<String, Object>> data;
switch (request.getStatType()) {
case "by_device":
data = statisticsMapper.statWaterUsageByDevice(
request.getStartDate(), request.getEndDate(),
request.getAreaId(), request.getLimit());
break;
case "by_area":
data = statisticsMapper.statWaterUsageByArea(
request.getStartDate(), request.getEndDate(),
request.getDeviceType(), request.getLimit());
break;
case "by_time":
data = statisticsMapper.statWaterUsageByTime(
request.getStartDate(), request.getEndDate(),
request.getPeriod(), request.getDeviceId(),
request.getAreaId());
break;
default:
throw new IllegalArgumentException("不支持的统计类型: " + request.getStatType());
LocalDate startDate = request.getStartDate();
LocalDate endDate = request.getEndDate();
LocalDateTime startTime = startDate.atStartOfDay();
LocalDateTime endTime = endDate.atTime(LocalTime.MAX);
// 按终端ID统计
if (request.getTerminalId() != null && !request.getTerminalId().isEmpty()) {
result.setType("terminal");
List<TerminalUsageStats> stats = terminalUsageStatsRepository
.findByTerminalIdAndStatDateBetween(request.getTerminalId(), startDate, endDate);
List<String> dates = stats.stream()
.map(stat -> stat.getStatDate().toString())
.collect(Collectors.toList());
List<Double> waterUsage = stats.stream()
.map(TerminalUsageStats::getTotalWaterOutput)
.collect(Collectors.toList());
result.setDates(dates);
result.setWaterUsage(waterUsage);
double total = waterUsage.stream().mapToDouble(Double::doubleValue).sum();
result.setTotalUsage(total);
result.setAvgDailyUsage(dates.size() > 0 ? total / dates.size() : 0);
return result;
}
// 计算总计
double totalAmount = data.stream()
.mapToDouble(item -> item.get("totalWaterOutput") != null ?
Double.parseDouble(item.get("totalWaterOutput").toString()) : 0)
.sum();
int totalCount = data.stream()
.mapToInt(item -> item.get("usageCount") != null ?
Integer.parseInt(item.get("usageCount").toString()) : 0)
.sum();
result.setTotalCount(totalCount);
result.setTotalAmount(totalAmount);
result.setAvgAmount(totalCount > 0 ? totalAmount / totalCount : 0);
// 构建明细项
List<StatisticsVO.StatItemVO> items = new ArrayList<>();
for (Map<String, Object> item : data) {
StatisticsVO.StatItemVO statItem = new StatisticsVO.StatItemVO();
if (request.getStatType().equals("by_time")) {
statItem.setDimensionKey(item.get("timeLabel").toString());
statItem.setDimensionValue(item.get("timeLabel").toString());
} else if (request.getStatType().equals("by_device")) {
statItem.setDimensionKey(item.get("deviceId").toString());
statItem.setDimensionValue(item.get("deviceName") != null ?
item.get("deviceName").toString() : item.get("deviceId").toString());
} else {
statItem.setDimensionKey(item.get("areaId").toString());
statItem.setDimensionValue(item.get("areaName") != null ?
item.get("areaName").toString() : item.get("areaId").toString());
// 按设备统计
if ("by_device".equals(request.getStatType())) {
result.setType("device");
List<Device> devices = deviceRepository.findByAreaId(request.getAreaId());
List<String> deviceIds = devices.stream()
.map(Device::getDeviceId)
.collect(Collectors.toList());
Map<String, Double> deviceTotal = new HashMap<>();
for (String deviceId : deviceIds) {
List<TerminalUsageStats> stats = terminalUsageStatsRepository
.findByTerminalIdAndStatDateBetween(deviceId, startDate, endDate);
double total = stats.stream()
.mapToDouble(TerminalUsageStats::getTotalWaterOutput)
.sum();
deviceTotal.put(deviceId, total);
}
Integer count = item.get("usageCount") != null ?
Integer.parseInt(item.get("usageCount").toString()) : 0;
Double amount = item.get("totalWaterOutput") != null ?
Double.parseDouble(item.get("totalWaterOutput").toString()) : 0;
statItem.setCount(count);
statItem.setAmount(amount);
statItem.setPercentage(totalAmount > 0 ? (amount / totalAmount) * 100 : 0);
List<Map.Entry<String, Double>> sorted = new ArrayList<>(deviceTotal.entrySet());
sorted.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue()));
result.setDeviceStats(sorted.stream()
.map(entry -> {
Map<String, Object> item = new HashMap<>();
item.put("deviceId", entry.getKey());
item.put("totalUsage", entry.getValue());
return item;
})
.collect(Collectors.toList()));
return result;
}
items.add(statItem);
// 按区域统计
if ("by_area".equals(request.getStatType())) {
result.setType("area");
result.setAreaId(request.getAreaId());
List<TerminalUsageStats> stats = terminalUsageStatsRepository
.findByStatDateBetween(startDate, endDate);
double total = stats.stream()
.mapToDouble(TerminalUsageStats::getTotalWaterOutput)
.sum();
result.setTotalUsage(total);
return result;
}
result.setItems(items);
return result;
}
/**
*
*
*/
@Transactional(readOnly = true)
public AlarmStatisticsVO getAlarmStatistics(StatisticsQueryRequest request) {
AlarmStatisticsVO result = new AlarmStatisticsVO();
// 获取告警统计
List<Map<String, Object>> alarmStats;
if ("by_device".equals(request.getStatType())) {
alarmStats = statisticsMapper.statAlarmCountByDevice(
request.getStartDate(), request.getEndDate(),
request.getAreaId(), null, request.getLimit());
} else if ("by_area".equals(request.getStatType())) {
alarmStats = statisticsMapper.statAlarmCountByArea(
request.getStartDate(), request.getEndDate(),
request.getDeviceType(), request.getLimit());
LocalDate startDate = request.getStartDate();
LocalDate endDate = request.getEndDate();
LocalDateTime startTime = startDate.atStartOfDay();
LocalDateTime endTime = endDate.atTime(LocalTime.MAX);
List<Alert> alerts;
// 按终端ID筛选告警
if (request.getTerminalId() != null && !request.getTerminalId().isEmpty()) {
alerts = alertRepository.findByDeviceIdAndTimestampBetween(request.getTerminalId(), startTime, endTime);
} else {
throw new IllegalArgumentException("不支持的告警统计类型: " + request.getStatType());
alerts = alertRepository.findByTimestampBetween(startTime, endTime);
}
// 构建设备告警统计
List<AlarmStatisticsVO.DeviceAlarmStatVO> deviceStats = alarmStats.stream()
.map(item -> {
AlarmStatisticsVO.DeviceAlarmStatVO deviceStat = new AlarmStatisticsVO.DeviceAlarmStatVO();
deviceStat.setDeviceId(item.get("deviceId").toString());
deviceStat.setDeviceName(item.get("deviceName") != null ?
item.get("deviceName").toString() : "");
deviceStat.setTotalAlarms(Integer.parseInt(item.get("totalAlarms").toString()));
deviceStat.setPendingAlarms(Integer.parseInt(item.get("pendingAlarms").toString()));
deviceStat.setResolvedAlarms(Integer.parseInt(item.get("resolvedAlarms").toString()));
return deviceStat;
})
.collect(Collectors.toList());
// 计算总数
int totalAlarms = deviceStats.stream().mapToInt(AlarmStatisticsVO.DeviceAlarmStatVO::getTotalAlarms).sum();
int pendingAlarms = deviceStats.stream().mapToInt(AlarmStatisticsVO.DeviceAlarmStatVO::getPendingAlarms).sum();
int resolvedAlarms = deviceStats.stream().mapToInt(AlarmStatisticsVO.DeviceAlarmStatVO::getResolvedAlarms).sum();
result.setTotalAlarms(totalAlarms);
result.setPendingAlarms(pendingAlarms);
result.setResolvedAlarms(resolvedAlarms);
result.setDeviceAlarmStats(deviceStats);
// 获取告警处理统计
Map<String, Object> handleStats = statisticsMapper.getAlarmHandleStatistics(
request.getStartDate(), request.getEndDate(), request.getAreaId());
if (handleStats != null && handleStats.get("avgResponseHours") != null) {
result.setAverageResponseTime(Double.parseDouble(handleStats.get("avgResponseHours").toString()));
}
// 修复使用alertLevel直接获取级别原代码错误使用getLevel()
Map<String, Long> levelCount = alerts.stream()
.map(alert -> alert.getAlertLevel().name()) // 直接获取alertLevel字段
.collect(Collectors.groupingBy(level -> level, Collectors.counting()));
result.setLevelCount(levelCount);
// 统计告警状态分布
Map<String, Long> statusCount = alerts.stream()
.map(alert -> alert.getStatus().name())
.collect(Collectors.groupingBy(status -> status, Collectors.counting()));
result.setStatusCount(statusCount);
// 计算处理率修复int转Long类型问题
long total = alerts.size();
long resolved = alerts.stream()
.filter(alert -> alert.getStatus() == Alert.AlertStatus.resolved)
.count();
double handleRate = total > 0 ? (double) resolved / total * 100 : 0;
result.setHandleRate(handleRate);
return result;
}
/**
*
*/
@Transactional(readOnly = true)
// 其他统计方法
public Map<String, Object> getDeviceStatusStatistics(String areaId, String deviceType) {
return statisticsMapper.getDeviceStatusStatistics(areaId, deviceType);
return new HashMap<>();
}
/**
*
*/
@Transactional(readOnly = true)
public Map<String, Object> getDashboardStatistics() {
Map<String, Object> result = new HashMap<>();
// 今日数据
LocalDate today = LocalDate.now();
StatisticsVO todayWaterUsage = getWaterUsageStatistics(createTodayQuery());
result.put("todayWaterUsage", todayWaterUsage);
// 本月数据
StatisticsVO monthWaterUsage = getWaterUsageStatistics(createMonthQuery());
result.put("monthWaterUsage", monthWaterUsage);
// 设备状态统计
Map<String, Object> deviceStats = getDeviceStatusStatistics(null, null);
result.put("deviceStatus", deviceStats);
// 告警统计
AlarmStatisticsVO alarmStats = getAlarmStatistics(createAlarmQuery());
result.put("alarmStatistics", alarmStats);
// 热门设备(按用水量)
StatisticsQueryRequest hotDevicesQuery = new StatisticsQueryRequest();
hotDevicesQuery.setStatType("by_device");
hotDevicesQuery.setStartDate(today.minusDays(7));
hotDevicesQuery.setEndDate(today);
hotDevicesQuery.setLimit(5);
StatisticsVO hotDevices = getWaterUsageStatistics(hotDevicesQuery);
result.put("hotDevices", hotDevices);
return result;
}
private StatisticsQueryRequest createTodayQuery() {
StatisticsQueryRequest request = new StatisticsQueryRequest();
request.setStatType("by_time");
request.setPeriod("day");
request.setStartDate(LocalDate.now());
request.setEndDate(LocalDate.now());
return request;
}
private StatisticsQueryRequest createMonthQuery() {
StatisticsQueryRequest request = new StatisticsQueryRequest();
request.setStatType("by_time");
request.setPeriod("month");
request.setStartDate(LocalDate.now().withDayOfMonth(1));
request.setEndDate(LocalDate.now());
return request;
}
private StatisticsQueryRequest createAlarmQuery() {
StatisticsQueryRequest request = new StatisticsQueryRequest();
request.setStatType("by_device");
request.setStartDate(LocalDate.now().minusDays(30));
request.setEndDate(LocalDate.now());
request.setLimit(10);
return request;
return new HashMap<>();
}
}

@ -1,16 +1,9 @@
/**
*
*
*
*
* 1. 线5线
* 2.
* Spring @Scheduled
* application.yml
*/
* */
package com.campus.water.task;
import com.campus.water.service.DeviceService;
import com.campus.water.service.DeviceStatusService; // 添加这行导入
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
@ -21,34 +14,28 @@ import org.springframework.stereotype.Component;
@Slf4j
public class DeviceStatusMonitorTask {
private final DeviceService deviceService;
private final DeviceStatusService deviceStatusService; // 现在可以正确识别
/**
* 5线
*/
@Scheduled(fixedRate = 300000) // 5分钟
// 后续代码不变...
@Scheduled(fixedRate = 300000)
public void monitorOfflineDevices() {
log.info("开始自动检测离线设备...");
try {
// 检测30分钟无数据的设备
deviceService.autoDetectOfflineDevices(30);
deviceStatusService.autoDetectOfflineDevices(30);
} catch (Exception e) {
log.error("离线设备检测任务执行失败: {}", e.getMessage(), e);
log.error("离线设备检测任务失败: {}", e.getMessage(), e);
}
}
/**
*
*/
@Scheduled(cron = "0 0 * * * ?") // 每小时执行一次
@Scheduled(cron = "0 0 * * * ?")
public void collectDeviceStatusStatistics() {
log.info("开始收集设备状态统计...");
try {
// 这里可以保存统计结果到数据库
// deviceService.getDeviceStatusCount(null, null);
// 补充实际统计逻辑,例如:
// deviceStatusService.collectAndSaveStatistics();
log.info("设备状态统计收集完成");
} catch (Exception e) {
log.error("设备状态统计收集失败: {}", e.getMessage(), e);
log.error("统计收集失败: {}", e.getMessage(), e);
}
}
}

@ -2,6 +2,10 @@
mqtt:
enabled: true # 是否启用 MQTT 客户端
# JWT 配置
jwt:
secret: campusWaterSystem2024SecretKey!@# # 替换为实际密钥
expiration: 86400000 # 24小时有效期毫秒
# Spring 核心配置:允许 Bean 定义覆盖(解决 Bean 重复定义冲突)
spring:
@ -9,7 +13,7 @@ spring:
allow-bean-definition-overriding: true # 缩进在 spring 下,作为子配置
# 数据库配置
datasource:
url: jdbc:mysql://localhost:3306/campus_water_management?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
url: jdbc:mysql://localhost:3306/campus_water_management?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true
username: root
password: wl1113
driver-class-name: com.mysql.cj.jdbc.Driver
@ -31,5 +35,4 @@ server:
charset: UTF-8
enabled: true
force: true # 移除末尾多余的 zs
port: 8080
Loading…
Cancel
Save