diff --git a/src/main/java/com/campus/water/config/MyBatisConfig.java b/src/main/java/com/campus/water/config/MyBatisConfig.java deleted file mode 100644 index b78aedb..0000000 --- a/src/main/java/com/campus/water/config/MyBatisConfig.java +++ /dev/null @@ -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配置类 - * 功能:配置MyBatis与Spring Boot的集成,包括数据源、事务管理、Mapper扫描等 - * 用途: - * 1. 数据源配置:连接池、连接参数 - * 2. MyBatis核心配置:SqlSessionFactory、事务管理器 - * 3. Mapper扫描:自动注册MyBatis映射接口 - * 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实例 - * 功能:MyBatis的核心工厂类,用于创建SqlSession - * 配置项: - * - 数据源绑定 - * - 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 分页拦截器 - * 功能:自动处理分页查询,支持MySQL、Oracle等多种数据库 - * 特性: - * - 自动识别数据库类型 - * - 支持多种分页方式 - * - 线程安全 - */ - @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(); - } -} \ No newline at end of file diff --git a/src/main/java/com/campus/water/config/SwaggerConfig.java b/src/main/java/com/campus/water/config/SwaggerConfig.java deleted file mode 100644 index 5f4c669..0000000 --- a/src/main/java/com/campus/water/config/SwaggerConfig.java +++ /dev/null @@ -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 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 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("水质数据查询、分析等接口") - ); - } - */ -} \ No newline at end of file diff --git a/src/main/java/com/campus/water/controller/app/AppStatisticsController.java b/src/main/java/com/campus/water/controller/app/AppStatisticsController.java deleted file mode 100644 index a92a874..0000000 --- a/src/main/java/com/campus/water/controller/app/AppStatisticsController.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 移动端(App)统计接口控制器 - * 功能:为移动端提供简化的统计查询接口 - * 用途:支持学生在手机上查看个人用水统计和设备状态 - * 接口特点: - * - 简化参数:减少查询维度,优化移动端体验 - * - 个人化:基于用户ID过滤数据 - * - 快速响应:返回核心数据,减少数据传输量 - * 接口列表: - * 1. GET /personal-water-usage: 个人用水统计(今日/本周/本月) - * 2. GET /device-status-overview: 设备状态概览 - * 技术:Spring MVC、Header参数验证、移动端优化 - */ -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> 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>> getDeviceStatusOverview( - @RequestParam(required = false) String areaId) { - try { - Map result = statisticsService.getDeviceStatusStatistics(areaId, null); - return ResponseEntity.ok(ResultVO.success(result)); - } catch (Exception e) { - return ResponseEntity.ok(ResultVO.error(500, "获取设备状态概览失败: " + e.getMessage())); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/campus/water/controller/web/DeviceStatusController.java b/src/main/java/com/campus/water/controller/web/DeviceStatusController.java index 05c71cf..501e405 100644 --- a/src/main/java/com/campus/water/controller/web/DeviceStatusController.java +++ b/src/main/java/com/campus/water/controller/web/DeviceStatusController.java @@ -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> 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> 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 devices = deviceService.getDevicesByStatus(status, areaId, deviceType); + List 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 result = deviceService.getDeviceStatusCount(areaId, deviceType); + Map 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 devices = deviceService.getOfflineDevicesExceedThreshold(thresholdMinutes, areaId); + List 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> 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())); diff --git a/src/main/java/com/campus/water/controller/web/StatisticsController.java b/src/main/java/com/campus/water/controller/web/StatisticsController.java index b008925..6d6b666 100644 --- a/src/main/java/com/campus/water/controller/web/StatisticsController.java +++ b/src/main/java/com/campus/water/controller/web/StatisticsController.java @@ -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; diff --git a/src/main/java/com/campus/water/entity/Admin.java b/src/main/java/com/campus/water/entity/Admin.java new file mode 100644 index 0000000..b92767f --- /dev/null +++ b/src/main/java/com/campus/water/entity/Admin.java @@ -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 + } +} \ No newline at end of file diff --git a/src/main/java/com/campus/water/entity/dto/request/DeviceStatusUpdateRequest.java b/src/main/java/com/campus/water/entity/dto/request/DeviceStatusUpdateRequest.java index aa31b5f..37921ac 100644 --- a/src/main/java/com/campus/water/entity/dto/request/DeviceStatusUpdateRequest.java +++ b/src/main/java/com/campus/water/entity/dto/request/DeviceStatusUpdateRequest.java @@ -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时必填) +} \ No newline at end of file diff --git a/src/main/java/com/campus/water/entity/dto/request/StatisticsQueryRequest.java b/src/main/java/com/campus/water/entity/dto/request/StatisticsQueryRequest.java index 9127415..ff6c558 100644 --- a/src/main/java/com/campus/water/entity/dto/request/StatisticsQueryRequest.java +++ b/src/main/java/com/campus/water/entity/dto/request/StatisticsQueryRequest.java @@ -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; // 限制数量 + // 若有其他字段可继续补充 } \ No newline at end of file diff --git a/src/main/java/com/campus/water/entity/po/DevicePO.java b/src/main/java/com/campus/water/entity/po/DevicePO.java new file mode 100644 index 0000000..f094e92 --- /dev/null +++ b/src/main/java/com/campus/water/entity/po/DevicePO.java @@ -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; // 更新时间 +} \ No newline at end of file diff --git a/src/main/java/com/campus/water/entity/vo/AlarmStatisticsVO.java b/src/main/java/com/campus/water/entity/vo/AlarmStatisticsVO.java index 183ff67..95e0606 100644 --- a/src/main/java/com/campus/water/entity/vo/AlarmStatisticsVO.java +++ b/src/main/java/com/campus/water/entity/vo/AlarmStatisticsVO.java @@ -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 alarmLevelCount; - - // 按类型统计 - private Map alarmTypeCount; - - // 按设备统计 - private List 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 levelCount; // 告警级别统计(解决setLevelCount()错误) + private Map statusCount; // 告警状态统计(解决setStatusCount()错误) + private double handleRate; // 处理率(解决setHandleRate()错误) + private long totalAlarms; // 总告警数 } \ No newline at end of file diff --git a/src/main/java/com/campus/water/entity/vo/StatisticsVO.java b/src/main/java/com/campus/water/entity/vo/StatisticsVO.java index 7892510..09a424b 100644 --- a/src/main/java/com/campus/water/entity/vo/StatisticsVO.java +++ b/src/main/java/com/campus/water/entity/vo/StatisticsVO.java @@ -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 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 timeLabels; // 时间标签 - private List values; // 对应数值 - } + private String type; // 统计类型(解决setType()错误) + private String areaId; // 区域ID(解决setAreaId()错误) + private List dates; // 日期列表(解决setDates()错误) + private List waterUsage; // 用水量列表(解决setWaterUsage()错误) + private double totalUsage; // 总用水量(解决setTotalUsage()错误) + private double avgDailyUsage; // 日均用水量(解决setAvgDailyUsage()错误) + private List> deviceStats; // 设备统计详情 + // 其他需要的字段 } \ No newline at end of file diff --git a/src/main/java/com/campus/water/mapper/DeviceMapper.java b/src/main/java/com/campus/water/mapper/DeviceMapper.java deleted file mode 100644 index a52b773..0000000 --- a/src/main/java/com/campus/water/mapper/DeviceMapper.java +++ /dev/null @@ -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操作 - * - 设备状态管理相关操作 - * - 设备统计和查询操作 - * 对应XML:DeviceMapper.xml中的SQL实现 - */ -@Mapper -public interface DeviceMapper { - - // ========== 设备基本信息CRUD操作 ========== - - /** - * 根据设备ID查询设备信息 - * @param deviceId 设备ID - * @return 设备实体对象 - */ - Device findById(@Param("deviceId") String deviceId); - - /** - * 查询所有设备 - * @return 设备列表 - */ - List findAll(); - - /** - * 根据设备名称模糊查询 - * @param deviceName 设备名称(模糊匹配) - * @return 匹配的设备列表 - */ - List 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 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 findByStatus(@Param("status") String status, - @Param("areaId") String areaId, - @Param("deviceType") String deviceType); - - /** - * 统计各状态设备数量 - * @param areaId 区域ID(可选) - * @param deviceType 设备类型(可选) - * @return 状态统计列表,每个元素包含status和count - */ - List> countByStatus(@Param("areaId") String areaId, - @Param("deviceType") String deviceType); - - /** - * 根据区域ID查询设备 - * @param areaId 区域ID - * @return 设备列表 - */ - List findByAreaId(@Param("areaId") String areaId); - - /** - * 根据设备类型查询设备 - * @param deviceType 设备类型(water_maker/water_supply) - * @return 设备列表 - */ - List findByDeviceType(@Param("deviceType") String deviceType); - - /** - * 根据安装位置模糊查询设备 - * @param location 安装位置关键词 - * @return 设备列表 - */ - List findByInstallLocationContaining(@Param("location") String location); - - /** - * 查询安装日期在指定范围内的设备 - * @param startDate 开始日期 - * @param endDate 结束日期 - * @return 设备列表 - */ - List 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 findOfflineDevicesExceedThreshold(@Param("thresholdMinutes") Integer thresholdMinutes, - @Param("areaId") String areaId); - - /** - * 更新设备最后通信时间 - * @param deviceId 设备ID - * @return 影响的行数 - */ - int updateLastCommunicationTime(@Param("deviceId") String deviceId); - - /** - * 查询需要维护的设备(根据维护计划) - * @param currentDate 当前日期 - * @return 需要维护的设备列表 - */ - List findDevicesNeedMaintenance(@Param("currentDate") LocalDate currentDate); - - // ========== 设备统计报表相关操作 ========== - - /** - * 统计各区域设备数量 - * @return 区域设备统计列表,每个元素包含areaId, areaName, deviceCount - */ - List> countDevicesByArea(); - - /** - * 统计各类型设备数量 - * @return 类型设备统计列表,每个元素包含deviceType, deviceCount - */ - List> countDevicesByType(); - - /** - * 统计设备在线率(按区域) - * @return 在线率统计列表,每个元素包含areaId, areaName, totalDevices, onlineDevices, onlineRate - */ - List> getOnlineRateByArea(); - - /** - * 查询设备运行时长统计 - * @param startDate 开始日期 - * @param endDate 结束日期 - * @return 设备运行时长列表,每个元素包含deviceId, deviceName, totalOnlineHours - */ - List> getDeviceRuntimeStats(@Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate); - - // ========== 批量操作 ========== - - /** - * 批量插入设备 - * @param devices 设备列表 - * @return 影响的行数 - */ - int batchInsert(@Param("devices") List devices); - - /** - * 批量更新设备信息 - * @param devices 设备列表 - * @return 影响的行数 - */ - int batchUpdate(@Param("devices") List devices); - - // ========== 设备关联查询 ========== - - /** - * 根据设备ID查询关联的终端设备 - * @param deviceId 设备ID - * @return 终端映射列表 - */ - List> findTerminalsByDeviceId(@Param("deviceId") String deviceId); - - /** - * 根据设备ID查询最近的告警记录 - * @param deviceId 设备ID - * @param limit 限制条数 - * @return 告警记录列表 - */ - List> findRecentAlertsByDeviceId(@Param("deviceId") String deviceId, - @Param("limit") Integer limit); - - /** - * 根据设备ID查询最近的维修记录 - * @param deviceId 设备ID - * @param limit 限制条数 - * @return 工单记录列表 - */ - List> findRecentWorkOrdersByDeviceId(@Param("deviceId") String deviceId, - @Param("limit") Integer limit); - - // ========== 设备高级搜索 ========== - - /** - * 多条件组合查询设备 - * @param deviceName 设备名称(可选) - * @param deviceType 设备类型(可选) - * @param status 设备状态(可选) - * @param areaId 区域ID(可选) - * @param startDate 安装开始日期(可选) - * @param endDate 安装结束日期(可选) - * @return 设备列表 - */ - List 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> getDeviceExportData(@Param("conditions") Map conditions); -} \ No newline at end of file diff --git a/src/main/java/com/campus/water/mapper/DeviceMapper.xml b/src/main/java/com/campus/water/mapper/DeviceMapper.xml deleted file mode 100644 index 9591e40..0000000 --- a/src/main/java/com/campus/water/mapper/DeviceMapper.xml +++ /dev/null @@ -1,383 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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} - ) - - - - 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} - - - - DELETE FROM device WHERE device_id = #{deviceId} - - - - - - UPDATE device - SET status = #{status}, - updated_time = NOW(), - remark = CONCAT(IFNULL(remark, ''), ';', #{remark}) - WHERE device_id = #{deviceId} - - - - 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 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 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 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 - - #{deviceId} - - - - - - - - - - - - - - - - - - - - - - - - - UPDATE device - SET updated_time = NOW(), - status = 'online' - WHERE device_id = #{deviceId} - - - - - - - - - - - - - - - - - - INSERT INTO device ( - device_id, device_name, device_type, area_id, - install_location, install_date, status, - create_time, remark - ) VALUES - - ( - #{device.deviceId}, #{device.deviceName}, #{device.deviceType}, #{device.areaId}, - #{device.installLocation}, #{device.installDate}, #{device.status}, - #{device.createTime}, #{device.remark} - ) - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/java/com/campus/water/mapper/DevicePOMapper.java b/src/main/java/com/campus/water/mapper/DevicePOMapper.java new file mode 100644 index 0000000..8d6c0c3 --- /dev/null +++ b/src/main/java/com/campus/water/mapper/DevicePOMapper.java @@ -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的主键类型是Long(id字段),因此这里泛型第二参数改为Long +public interface DevicePOMapper extends JpaRepository { + + // 根据设备唯一标识(deviceId)查询(注意区分主键id和业务字段deviceId) + DevicePO findByDeviceId(String deviceId); + + // 根据状态字符串查询(参数类型为String,匹配DevicePO的status字段) + List 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 findByAreaId(String areaId); + + // 补充:按区域和状态查询 + List findByAreaIdAndStatus(String areaId, String status); +} \ No newline at end of file diff --git a/src/main/java/com/campus/water/mapper/StatisticsMapper.java b/src/main/java/com/campus/water/mapper/StatisticsMapper.java deleted file mode 100644 index e00caca..0000000 --- a/src/main/java/com/campus/water/mapper/StatisticsMapper.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * 统计数据处理映射接口(MyBatis Mapper) - * 功能:定义统计数据查询的数据库操作方法 - * 用途:与数据库交互,执行复杂的统计聚合查询 - * 核心方法: - * - 按设备/区域/时间维度统计用水量 - * - 按设备/区域统计告警次数 - * - 获取设备状态统计和告警处理统计 - * 备注:对应StatisticsMapper.xml中的SQL映射 - */ -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> statWaterUsageByDevice( - @Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate, - @Param("areaId") String areaId, - @Param("limit") Integer limit); - - // 按区域统计用水量 - List> statWaterUsageByArea( - @Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate, - @Param("deviceType") String deviceType, - @Param("limit") Integer limit); - - // 按时间段统计用水量(日/周/月) - List> 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> statAlarmCountByDevice( - @Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate, - @Param("areaId") String areaId, - @Param("alarmLevel") String alarmLevel, - @Param("limit") Integer limit); - - // 按区域统计告警次数 - List> statAlarmCountByArea( - @Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate, - @Param("deviceType") String deviceType, - @Param("limit") Integer limit); - - // 按时间段统计告警趋势 - List> statAlarmTrendByTime( - @Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate, - @Param("period") String period, - @Param("deviceId") String deviceId, - @Param("areaId") String areaId); - - // 统计设备使用情况(使用次数、总用水量) - List> statDeviceUsage( - @Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate, - @Param("areaId") String areaId, - @Param("deviceType") String deviceType, - @Param("limit") Integer limit); - - // 统计终端使用情况 - List> statTerminalUsage( - @Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate, - @Param("areaId") String areaId, - @Param("limit") Integer limit); - - // 统计水质达标率 - List> statWaterQualityRate( - @Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate, - @Param("deviceId") String deviceId, - @Param("areaId") String areaId); - - // 获取设备状态统计 - Map getDeviceStatusStatistics( - @Param("areaId") String areaId, - @Param("deviceType") String deviceType); - - // 获取告警处理统计 - Map getAlarmHandleStatistics( - @Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate, - @Param("areaId") String areaId); -} \ No newline at end of file diff --git a/src/main/java/com/campus/water/mapper/StatisticsMapper.xml b/src/main/java/com/campus/water/mapper/StatisticsMapper.xml deleted file mode 100644 index 4883759..0000000 --- a/src/main/java/com/campus/water/mapper/StatisticsMapper.xml +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/java/com/campus/water/mapper/StatisticsRepository.java b/src/main/java/com/campus/water/mapper/StatisticsRepository.java new file mode 100644 index 0000000..17f7f58 --- /dev/null +++ b/src/main/java/com/campus/water/mapper/StatisticsRepository.java @@ -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 { + + /** + * 按设备统计指定时间范围内的用水量 + * @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 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 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 getDailyWaterUsage(LocalDate startDate, LocalDate endDate); +} \ No newline at end of file diff --git a/src/main/java/com/campus/water/service/AlertService.java b/src/main/java/com/campus/water/service/AlertService.java deleted file mode 100644 index 7f8995a..0000000 --- a/src/main/java/com/campus/water/service/AlertService.java +++ /dev/null @@ -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 batchCreateAlerts(List alerts) { - try { - List 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 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 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 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 alertIds, Alert.AlertStatus status, String updatedBy) { - try { - List 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 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 findAlertsByConditions(String deviceId, String alertType, - Alert.AlertLevel alertLevel, Alert.AlertStatus status, - String areaId, LocalDateTime startTime, - LocalDateTime endTime, Pageable pageable) { - Specification spec = (root, query, criteriaBuilder) -> { - List 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 findRecentAlertsByDeviceId(String deviceId, int limit) { - return alertRepository.findByDeviceIdOrderByTimestampDesc(deviceId) - .stream() - .limit(limit) - .collect(Collectors.toList()); - } - - /** - * 查询未处理告警 - * @param areaId 区域ID(可选) - * @return 未处理告警列表 - */ - @Transactional(readOnly = true) - public List 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 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 getAlertOverview(String areaId, LocalDateTime startTime, LocalDateTime endTime) { - Map overview = new HashMap<>(); - - // 获取统计时间段内的告警 - List 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 levelCount = alerts.stream() - .collect(Collectors.groupingBy(Alert::getAlertLevel, Collectors.counting())); - - // 按类型统计 - Map 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> getAlertTrend(String period, - LocalDateTime startTime, - LocalDateTime endTime, - String areaId) { - List alerts = alertRepository.findByTimestampBetween(startTime, endTime); - - if (StringUtils.hasText(areaId)) { - alerts = alerts.stream() - .filter(alert -> areaId.equals(alert.getAreaId())) - .collect(Collectors.toList()); - } - - // 按时间分组 - Map> groupedAlerts = new TreeMap<>(); - - for (Alert alert : alerts) { - String timeKey = formatTimeKey(alert.getTimestamp(), period); - groupedAlerts.computeIfAbsent(timeKey, k -> new ArrayList<>()).add(alert); - } - - // 构建结果 - List> result = new ArrayList<>(); - for (Map.Entry> entry : groupedAlerts.entrySet()) { - Map item = new HashMap<>(); - item.put("timeLabel", entry.getKey()); - item.put("totalAlerts", entry.getValue().size()); - - // 按级别统计 - Map 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> getDeviceAlertRanking(int topN, - LocalDateTime startTime, - LocalDateTime endTime) { - List alerts; - if (startTime != null && endTime != null) { - alerts = alertRepository.findByTimestampBetween(startTime, endTime); - } else { - alerts = alertRepository.findAll(); - } - - // 按设备分组统计 - Map> deviceAlerts = alerts.stream() - .collect(Collectors.groupingBy(Alert::getDeviceId)); - - // 获取设备信息 - List> ranking = new ArrayList<>(); - for (Map.Entry> entry : deviceAlerts.entrySet()) { - String deviceId = entry.getKey(); - List deviceAlertList = entry.getValue(); - - Optional deviceOpt = deviceRepository.findById(deviceId); - String deviceName = deviceOpt.map(Device::getDeviceName).orElse("未知设备"); - - Map 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> analyzeAlertPatterns(LocalDateTime startTime, - LocalDateTime endTime, - String areaId) { - List alerts = alertRepository.findByTimestampBetween(startTime, endTime); - - if (StringUtils.hasText(areaId)) { - alerts = alerts.stream() - .filter(alert -> areaId.equals(alert.getAreaId())) - .collect(Collectors.toList()); - } - - // 按设备和时间窗口分组 - Map>> 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> patterns = new ArrayList<>(); - for (Map> timeAlerts : deviceTimeAlerts.values()) { - for (List windowAlerts : timeAlerts.values()) { - if (windowAlerts.size() >= 2) { - // 发现多个告警同时出现 - Set alertTypes = windowAlerts.stream() - .map(Alert::getAlertType) - .collect(Collectors.toSet()); - - if (alertTypes.size() > 1) { - Map 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 predictAlertRisk(String deviceId) { - Optional deviceOpt = deviceRepository.findById(deviceId); - if (deviceOpt.isEmpty()) { - return Collections.emptyMap(); - } - - Device device = deviceOpt.get(); - LocalDateTime now = LocalDateTime.now(); - LocalDateTime oneMonthAgo = now.minusMonths(1); - - // 获取最近一个月的告警 - List recentAlerts = alertRepository.findByDeviceIdAndTimestampAfter(deviceId, oneMonthAgo); - - Map 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 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 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()); - } -} \ No newline at end of file diff --git a/src/main/java/com/campus/water/service/DeviceService.java b/src/main/java/com/campus/water/service/DeviceService.java index 2fc2e74..ee558ae 100644 --- a/src/main/java/com/campus/water/service/DeviceService.java +++ b/src/main/java/com/campus/water/service/DeviceService.java @@ -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 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 deviceIds, DeviceStatus status) { + List 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 getDeviceStatusCount(String areaId, String deviceType) { - return deviceMapper.countByStatus(areaId, deviceType); - } - - /** - * 查询离线设备(超过阈值) - */ - @Transactional(readOnly = true) - public List getOfflineDevicesExceedThreshold(Integer thresholdMinutes, String areaId) { - return deviceMapper.findOfflineDevicesExceedThreshold(thresholdMinutes, areaId); + public List 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 existing = terminalMappingRepository.findByTerminalId(terminalId); + if (existing.isPresent()) { + throw new RuntimeException("终端已绑定设备:" + existing.get().getDeviceId()); + } - /** - * 根据状态查询设备 - */ - @Transactional(readOnly = true) - public List 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 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 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(); } } \ No newline at end of file diff --git a/src/main/java/com/campus/water/service/DeviceStatusService.java b/src/main/java/com/campus/water/service/DeviceStatusService.java new file mode 100644 index 0000000..e2b2aa3 --- /dev/null +++ b/src/main/java/com/campus/water/service/DeviceStatusService.java @@ -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 deviceIds, String status, String remark); + + // 按状态查询设备 + List getDevicesByStatus(String status, String areaId, String deviceType); + + // 统计各状态设备数量 + Map getDeviceStatusCount(String areaId, String deviceType); + + // 获取超过阈值的离线设备 + List getOfflineDevicesExceedThreshold(Integer thresholdMinutes, String areaId); + + // 自动检测离线设备 + void autoDetectOfflineDevices(Integer thresholdMinutes); +} \ No newline at end of file diff --git a/src/main/java/com/campus/water/service/StatisticsService.java b/src/main/java/com/campus/water/service/StatisticsService.java index d5e31e8..303a53e 100644 --- a/src/main/java/com/campus/water/service/StatisticsService.java +++ b/src/main/java/com/campus/water/service/StatisticsService.java @@ -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> 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 stats = terminalUsageStatsRepository + .findByTerminalIdAndStatDateBetween(request.getTerminalId(), startDate, endDate); + + List dates = stats.stream() + .map(stat -> stat.getStatDate().toString()) + .collect(Collectors.toList()); + List 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 items = new ArrayList<>(); - for (Map 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 devices = deviceRepository.findByAreaId(request.getAreaId()); + List deviceIds = devices.stream() + .map(Device::getDeviceId) + .collect(Collectors.toList()); + + Map deviceTotal = new HashMap<>(); + for (String deviceId : deviceIds) { + List 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> sorted = new ArrayList<>(deviceTotal.entrySet()); + sorted.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue())); + result.setDeviceStats(sorted.stream() + .map(entry -> { + Map 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 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> 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 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 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 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 levelCount = alerts.stream() + .map(alert -> alert.getAlertLevel().name()) // 直接获取alertLevel字段 + .collect(Collectors.groupingBy(level -> level, Collectors.counting())); + result.setLevelCount(levelCount); + + // 统计告警状态分布 + Map 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 getDeviceStatusStatistics(String areaId, String deviceType) { - return statisticsMapper.getDeviceStatusStatistics(areaId, deviceType); + return new HashMap<>(); } - /** - * 获取综合仪表盘数据 - */ - @Transactional(readOnly = true) public Map getDashboardStatistics() { - Map result = new HashMap<>(); - - // 今日数据 - LocalDate today = LocalDate.now(); - StatisticsVO todayWaterUsage = getWaterUsageStatistics(createTodayQuery()); - result.put("todayWaterUsage", todayWaterUsage); - - // 本月数据 - StatisticsVO monthWaterUsage = getWaterUsageStatistics(createMonthQuery()); - result.put("monthWaterUsage", monthWaterUsage); - - // 设备状态统计 - Map 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<>(); } } \ No newline at end of file diff --git a/src/main/java/com/campus/water/task/DeviceStatusMonitorTask.java b/src/main/java/com/campus/water/task/DeviceStatusMonitorTask.java index dafd726..8a05577 100644 --- a/src/main/java/com/campus/water/task/DeviceStatusMonitorTask.java +++ b/src/main/java/com/campus/water/task/DeviceStatusMonitorTask.java @@ -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); } } } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 02fced5..7a6b00f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -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 \ No newline at end of file