ether-docs/plans/2026-03-23-equipment-enhanc...

41 KiB
Raw Blame History

设施设备管理增强功能开发计划

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: 实现非居物业设施设备管理增强功能M02-13~18包括设备技术参数扩展、预防性维护引擎、能耗监控、备件库存、故障预测、点检标准库

Architecture: 基于现有 ether-pms 单体架构,在 module-mdm 模块新增设备管理相关实体和服务,前端新增设备管理页面。能耗监控和故障预测作为独立功能模块开发。

Tech Stack: Spring Boot 3.x + JPA + PostgreSQL + Vue3 + TypeScript + Ant Design Vue


开发阶段总览

阶段一M02-13设备技术参数扩展     - 约3天
阶段二M02-14预防性维护引擎      - 约5天
阶段三M02-15能耗监控管理         - 约4天
阶段四M02-16备件库存管理         - 约4天
阶段五M02-17设备故障预测         - 约3天
阶段六M02-18设备点检标准库       - 约3天
─────────────────────────────────────────
总计                                      约22天

阶段一M02-13 设备技术参数扩展

数据库迁移

文件: ether-pms/src/main/resources/db/migration/V10__add_equipment_extension_fields.sql

-- 添加设备扩展字段到 mdm_space_node 表(设备关联空间节点)
ALTER TABLE mdm_space_node ADD COLUMN design_life_years INTEGER;
ALTER TABLE mdm_space_node ADD COLUMN rated_power DECIMAL(10,2);
ALTER TABLE mdm_space_node ADD COLUMN rated_voltage VARCHAR(20);
ALTER TABLE mdm_space_node ADD COLUMN rated_current DECIMAL(10,2);
ALTER TABLE mdm_space_node ADD COLUMN maintenance_vendor VARCHAR(100);
ALTER TABLE mdm_space_node ADD COLUMN maintenance_vendor_contact VARCHAR(50);
ALTER TABLE mdm_space_node ADD COLUMN maintenance_vendor_phone VARCHAR(20);
ALTER TABLE mdm_space_node ADD COLUMN maintenance_contract_no VARCHAR(50);
ALTER TABLE mdm_space_node ADD COLUMN maintenance_contract_start DATE;
ALTER TABLE mdm_space_node ADD COLUMN maintenance_contract_end DATE;
ALTER TABLE mdm_space_node ADD COLUMN special_equipment_type VARCHAR(50);
ALTER TABLE mdm_space_node ADD COLUMN special_equipment_cert VARCHAR(100);
ALTER TABLE mdm_space_node ADD COLUMN inspection_cycle INTEGER;
ALTER TABLE mdm_space_node ADD COLUMN next_inspection_date DATE;
ALTER TABLE mdm_space_node ADD COLUMN last_inspection_date DATE;
ALTER TABLE mdm_space_node ADD COLUMN last_inspection_result VARCHAR(20);
ALTER TABLE mdm_space_node ADD COLUMN common_spare_parts JSONB;
ALTER TABLE mdm_space_node ADD COLUMN energy_consumption_standard DECIMAL(12,2);
ALTER TABLE mdm_space_node ADD COLUMN installation_environment VARCHAR(50);
ALTER TABLE mdm_space_node ADD COLUMN protection_level VARCHAR(20);

Task 1.1: 创建设备分类枚举

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/enums/EquipmentCategory.java
package com.ether.pms.mdm.enums;

public enum EquipmentCategory {
    HVAC("暖通空调"),
    ELECTRICAL("电气设备"),
    FIRE("消防设备"),
    ELEVATOR("电梯设备"),
    SECURITY("安防设备"),
   给排水("给排水设备"),
    LIGHTING("照明设备"),
    SPECIAL("特种设备");

    private final String desc;
    EquipmentCategory(String desc) { this.desc = desc; }
    public String getDesc() { return desc; }
}

Step 1: 创建枚举类 Step 2: 验证编译通过 Step 3: Commit: feat: add equipment category enum


Task 1.2: 创建设备类型枚举

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/enums/EquipmentType.java
package com.ether.pms.mdm.enums;

public enum EquipmentType {
    CENTRAL_AC("中央空调", EquipmentCategory.HVAC),
    AIR_CONDITIONER("分体空调", EquipmentCategory.HVAC),
    AIR_HANDLING_UNIT("空气处理机组", EquipmentCategory.HVAC),
    FAN_COIL("风机盘管", EquipmentCategory.HVAC),
    LOW_VOLTAGE_CABINET("低压配电柜", EquipmentCategory.ELECTRICAL),
    TRANSFORMER("变压器", EquipmentCategory.ELECTRICAL),
    GENERATOR("发电机", EquipmentCategory.ELECTRICAL),
    UPS("不间断电源", EquipmentCategory.ELECTRICAL),
    FIRE_PUMP("消防泵", EquipmentCategory.FIRE),
    SPRINKLER("喷淋系统", EquipmentCategory.FIRE),
    FIRE_ALARM("火灾报警系统", EquipmentCategory.FIRE),
    ELEVATOR("电梯", EquipmentCategory.ELEVATOR),
    CCTV("监控系统", EquipmentCategory.SECURITY),
    ACCESS_CONTROL("门禁系统", EquipmentCategory.SECURITY),
    WATER_PUMP("给水泵", EquipmentCategory.WATER_DRAINAGE),
    DRAINAGE_PUMP("排水泵", EquipmentCategory.WATER_DRAINAGE),
    LED_LIGHT("LED灯具", EquipmentCategory.LIGHTING),
    HIGH_BAY_LIGHT("工矿灯", EquipmentCategory.LIGHTING);

    private final String desc;
    private final EquipmentCategory category;
    EquipmentType(String desc, EquipmentCategory category) {
        this.desc = desc;
        this.category = category;
    }
    public String getDesc() { return desc; }
    public EquipmentCategory getCategory() { return category; }
}

Step 1: 创建枚举类 Step 2: 验证编译通过 Step 3: Commit: feat: add equipment type enum


Task 1.3: 更新SpaceNode实体

Files:

  • Modify: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/SpaceNode.java

在现有实体末尾添加扩展字段:

// ========== 设备扩展字段 ==========
@Column(name = "is_equipment")
private Boolean isEquipment = false;

@Column(name = "design_life_years")
private Integer designLifeYears;

@Column(name = "rated_power", precision = 10, scale = 2)
private BigDecimal ratedPower;

@Column(name = "rated_voltage", length = 20)
private String ratedVoltage;

@Column(name = "rated_current", precision = 10, scale = 2)
private BigDecimal ratedCurrent;

@Column(name = "maintenance_vendor", length = 100)
private String maintenanceVendor;

@Column(name = "maintenance_vendor_contact", length = 50)
private String maintenanceVendorContact;

@Column(name = "maintenance_vendor_phone", length = 20)
private String maintenanceVendorPhone;

@Column(name = "maintenance_contract_no", length = 50)
private String maintenanceContractNo;

@Column(name = "maintenance_contract_start")
private LocalDate maintenanceContractStart;

@Column(name = "maintenance_contract_end")
private LocalDate maintenanceContractEnd;

@Column(name = "special_equipment_type", length = 50)
private String specialEquipmentType;

@Column(name = "special_equipment_cert", length = 100)
private String specialEquipmentCert;

@Column(name = "inspection_cycle")
private Integer inspectionCycle;

@Column(name = "next_inspection_date")
private LocalDate nextInspectionDate;

@Column(name = "last_inspection_date")
private LocalDate lastInspectionDate;

@Column(name = "last_inspection_result", length = 20)
private String lastInspectionResult;

@Column(name = "common_spare_parts", columnDefinition = "TEXT")
private String commonSpareParts;

@Column(name = "energy_consumption_standard", precision = 12, scale = 2)
private BigDecimal energyConsumptionStandard;

@Column(name = "installation_environment", length = 50)
private String installationEnvironment;

@Column(name = "protection_level", length = 20)
private String protectionLevel;
// ========== 设备扩展字段结束 ==========

Step 1: 添加扩展字段到 SpaceNode 实体 Step 2: 运行 cd ether-pms && mvn compile 验证 Step 3: Commit: feat: add equipment extension fields to SpaceNode


Task 1.4: 创建SpaceNode DTO

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeEquipmentDTO.java
package com.ether.pms.mdm.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;

@Data
@EqualsAndHashCode(callSuper = true)
public class SpaceNodeEquipmentDTO extends SpaceNodeDTO {

    private Boolean isEquipment;
    private Integer designLifeYears;
    private BigDecimal ratedPower;
    private String ratedVoltage;
    private BigDecimal ratedCurrent;
    private String maintenanceVendor;
    private String maintenanceVendorContact;
    private String maintenanceVendorPhone;
    private String maintenanceContractNo;
    private LocalDate maintenanceContractStart;
    private LocalDate maintenanceContractEnd;
    private String specialEquipmentType;
    private String specialEquipmentCert;
    private Integer inspectionCycle;
    private LocalDate nextInspectionDate;
    private LocalDate lastInspectionDate;
    private String lastInspectionResult;
    private List<SparePartInfo> commonSpareParts;
    private BigDecimal energyConsumptionStandard;
    private String installationEnvironment;
    private String protectionLevel;

    @Data
    public static class SparePartInfo {
        private String name;
        private String model;
        private Integer quantity;
    }
}

Step 1: 创建 DTO 类 Step 2: 运行 cd ether-pms && mvn compile 验证 Step 3: Commit: feat: add SpaceNodeEquipmentDTO


Task 1.5: 更新SpaceNodeService

Files:

  • Modify: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/SpaceNodeService.java

添加设备相关方法:

public SpaceNodeEquipmentDTO getEquipmentById(UUID id) {
    SpaceNode node = spaceNodeRepository.findById(id)
        .orElseThrow(() -> new BusinessException("EQUIPMENT_NOT_FOUND"));
    return convertToEquipmentDTO(node);
}

public List<SpaceNodeEquipmentDTO> getSpecialEquipmentList(UUID projectId) {
    List<SpaceNode> list = spaceNodeRepository.findByProjectIdAndIsEquipmentAndSpecialEquipmentTypeIsNotNull(
        projectId, true);
    return list.stream().map(this::convertToEquipmentDTO).collect(Collectors.toList());
}

public List<SpaceNodeEquipmentDTO> getExpiringInspectionEquipment(UUID projectId, Integer daysAhead) {
    LocalDate threshold = LocalDate.now().plusDays(daysAhead);
    List<SpaceNode> list = spaceNodeRepository
        .findByProjectIdAndIsEquipmentAndNextInspectionDateBefore(projectId, true, threshold);
    return list.stream().map(this::convertToEquipmentDTO).collect(Collectors.toList());
}

Step 1: 添加服务方法 Step 2: 添加单元测试 Step 3: 运行测试验证 Step 4: Commit: feat: add equipment service methods


Task 1.6: 更新SpaceNodeController

Files:

  • Modify: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/controller/SpaceNodeController.java

添加设备相关API

@GetMapping("/{id}/equipment")
public ApiResponse<SpaceNodeEquipmentDTO> getEquipment(@PathVariable UUID id) {
    return ApiResponse.success(spaceNodeService.getEquipmentById(id));
}

@GetMapping("/special-equipment")
public ApiResponse<List<SpaceNodeEquipmentDTO>> getSpecialEquipment(
    @RequestParam UUID projectId) {
    return ApiResponse.success(spaceNodeService.getSpecialEquipmentList(projectId));
}

@GetMapping("/expiring-inspection")
public ApiResponse<List<SpaceNodeEquipmentDTO>> getExpiringInspection(
    @RequestParam UUID projectId,
    @RequestParam(defaultValue = "90") Integer daysAhead) {
    return ApiResponse.success(spaceNodeService.getExpiringInspectionEquipment(projectId, daysAhead));
}

Step 1: 添加控制器端点 Step 2: 使用 curl 测试端点 Step 3: Commit: feat: add equipment API endpoints


Task 1.7: 前端 - 创建设备管理页面

Files:

  • Create: ether-admin/src/views/equipment/EquipmentList.vue
  • Create: ether-admin/src/views/equipment/EquipmentDetail.vue
  • Create: ether-admin/src/api/equipment.ts

Step 1: 创建 API 模块 ether-admin/src/api/equipment.ts

import request from '@/utils/request'

export interface EquipmentForm {
  id?: string
  code: string
  name: string
  nodeType: string
  locationDesc?: string
  designLifeYears?: number
  ratedPower?: number
  ratedVoltage?: string
  maintenanceVendor?: string
  maintenanceVendorPhone?: string
  specialEquipmentType?: string
  inspectionCycle?: number
  nextInspectionDate?: string
  [key: string]: any
}

export function getEquipmentList(params: any) {
  return request({
    url: '/api/v1/mdm/space-nodes/equipment',
    method: 'get',
    params
  })
}

export function getEquipmentDetail(id: string) {
  return request({
    url: `/api/v1/mdm/space-nodes/${id}/equipment`,
    method: 'get'
  })
}

export function getSpecialEquipment(projectId: string) {
  return request({
    url: '/api/v1/mdm/space-nodes/special-equipment',
    method: 'get',
    params: { projectId }
  })
}

export function getExpiringInspection(projectId: string, daysAhead?: number) {
  return request({
    url: '/api/v1/mdm/space-nodes/expiring-inspection',
    method: 'get',
    params: { projectId, daysAhead }
  })
}

Step 2: 创建设备列表页面 EquipmentList.vue

Step 3: 创建设备详情页面 EquipmentDetail.vue

Step 4: 运行 npm run build 验证前端编译

Step 5: Commit: feat: add equipment management pages


阶段二M02-14 预防性维护引擎

Task 2.1: 创建维保计划实体

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/MaintenancePlan.java
package com.ether.pms.mdm.entity;

import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.UUID;

@Entity
@Table(name = "ops_maintenance_plan")
@Data
public class MaintenancePlan {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(name = "project_id", nullable = false)
    private UUID projectId;

    @Column(name = "plan_code", nullable = false, unique = true)
    private String planCode;

    @Column(name = "plan_name", nullable = false)
    private String planName;

    @Column(name = "equipment_type")
    private String equipmentType;

    @Column(name = "trigger_type", nullable = false)
    @Enumerated(EnumType.STRING)
    private TriggerType triggerType;

    public enum TriggerType {
        TIME_BASED,    // 时间触发
        HOURS_BASED,   // 运行小时触发
        CYCLES_BASED,  // 次数触发
        CONDITION_BASED // 条件触发
    }

    @Column(name = "trigger_value")
    private Integer triggerValue;

    @Column(name = "trigger_unit")
    private String triggerUnit;

    @Column(name = "maintenance_items", columnDefinition = "TEXT")
    private String maintenanceItems;

    @Column(name = "estimated_duration")
    private Integer estimatedDuration;

    @Column(name = "assigned_to")
    private UUID assignedTo;

    @Column(name = "sla_response_hours")
    private Integer slaResponseHours;

    @Column(name = "sla_complete_hours")
    private Integer slaCompleteHours;

    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private Status status = Status.ACTIVE;

    public enum Status {
        ACTIVE, INACTIVE
    }

    @Column(name = "created_at")
    private LocalDateTime createdAt;

    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    @PrePersist
    public void prePersist() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
    }

    @PreUpdate
    public void preUpdate() {
        updatedAt = LocalDateTime.now();
    }
}

Step 1: 创建实体类 Step 2: 运行 mvn compile 验证 Step 3: Commit: feat: add MaintenancePlan entity


Task 2.2: 创建维保任务实体

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/MaintenanceTask.java
package com.ether.pms.mdm.entity;

import jakarta.persistence.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;

@Entity
@Table(name = "ops_maintenance_task")
@Data
public class MaintenanceTask {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(name = "project_id", nullable = false)
    private UUID projectId;

    @Column(name = "task_code", nullable = false, unique = true)
    private String taskCode;

    @Column(name = "plan_id")
    private UUID planId;

    @Column(name = "equipment_id")
    private UUID equipmentId;

    @Column(name = "task_type")
    @Enumerated(EnumType.STRING)
    private TaskType taskType = TaskType.PREVENTIVE;

    public enum TaskType {
        PREVENTIVE,   // 预防性维护
        CORRECTIVE    // 纠正性维护
    }

    @Column(name = "trigger_type")
    @Enumerated(EnumType.STRING)
    private MaintenancePlan.TriggerType triggerType;

    @Column(name = "maintenance_items", columnDefinition = "TEXT")
    private String maintenanceItems;

    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private Status status = Status.PENDING;

    public enum Status {
        PENDING,      // 待接受
        ACCEPTED,     // 已接受
        IN_PROGRESS,  // 执行中
        COMPLETED,    // 已完成
        CANCELLED    // 已取消
    }

    @Column(name = "assigned_to")
    private UUID assignedTo;

    @Column(name = "scheduled_date")
    private LocalDateTime scheduledDate;

    @Column(name = "actual_start_date")
    private LocalDateTime actualStartDate;

    @Column(name = "actual_end_date")
    private LocalDateTime actualEndDate;

    @Column(name = "labor_hours", precision = 10, scale = 2)
    private BigDecimal laborHours;

    @Column(name = "materials_cost", precision = 12, scale = 2)
    private BigDecimal materialsCost;

    @Column(columnDefinition = "TEXT")
    private String remarks;

    @Column(name = "created_at")
    private LocalDateTime createdAt;

    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    @PrePersist
    public void prePersist() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
    }

    @PreUpdate
    public void preUpdate() {
        updatedAt = LocalDateTime.now();
    }
}

Step 1: 创建实体类 Step 2: 运行 mvn compile 验证 Step 3: Commit: feat: add MaintenanceTask entity


Task 2.3: 创建维保Repository

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/repository/MaintenancePlanRepository.java
  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/repository/MaintenanceTaskRepository.java
package com.ether.pms.mdm.repository;

public interface MaintenancePlanRepository extends JpaRepository<MaintenancePlan, UUID> {
    List<MaintenancePlan> findByProjectIdAndStatus(UUID projectId, MaintenancePlan.Status status);
    Optional<MaintenancePlan> findByPlanCode(String planCode);
}

public interface MaintenanceTaskRepository extends JpaRepository<MaintenanceTask, UUID> {
    List<MaintenanceTask> findByProjectIdAndStatus(UUID projectId, MaintenanceTask.Status status);
    List<MaintenanceTask> findByAssignedToAndStatus(UUID assignedTo, MaintenanceTask.Status status);
    List<MaintenanceTask> findByEquipmentIdAndStatusNot(UUID equipmentId, MaintenanceTask.Status status);
    Optional<MaintenanceTask> findByTaskCode(String taskCode);
}

Step 1: 创建 Repository Step 2: 添加查询方法单元测试 Step 3: Commit: feat: add maintenance repositories


Task 2.4: 创建维保Service

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/MaintenancePlanService.java
  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/MaintenanceTaskService.java
  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/MaintenancePlanServiceImpl.java
  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/MaintenanceTaskServiceImpl.java

Service核心方法

// MaintenancePlanService
UUID createPlan(MaintenancePlan plan);
List<MaintenancePlan> getActivePlansByProject(UUID projectId);
void updatePlan(UUID id, MaintenancePlan plan);
void deactivatePlan(UUID id);

// MaintenanceTaskService
List<MaintenanceTask> getTasksByAssignee(UUID assignee, String status);
void acceptTask(UUID taskId, UUID userId);
void startTask(UUID taskId);
void completeTask(UUID taskId, BigDecimal laborHours, BigDecimal materialsCost, String remarks);
List<MaintenanceTask> getOverdueTasks();

Step 1: 创建 Service 接口和实现 Step 2: 添加单元测试 Step 3: 运行测试验证 Step 4: Commit: feat: add maintenance service


Task 2.5: 创建维保Controller

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/controller/MaintenancePlanController.java
  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/controller/MaintenanceTaskController.java

API 设计:

方法 路径 描述
POST /api/v1/ops/maintenance-plans 创建维保计划
GET /api/v1/ops/maintenance-plans 获取维保计划列表
PUT /api/v1/ops/maintenance-plans/{id} 更新维保计划
DELETE /api/v1/ops/maintenance-plans/{id} 删除维保计划
GET /api/v1/ops/maintenance-tasks 获取维保任务列表
POST /api/v1/ops/maintenance-tasks/{id}/accept 接受任务
POST /api/v1/ops/maintenance-tasks/{id}/start 开始执行
POST /api/v1/ops/maintenance-tasks/{id}/complete 完成维保

Step 1: 创建 Controller Step 2: 使用 curl 测试 API Step 3: Commit: feat: add maintenance controllers


Task 2.6: 创建定时任务 - 维保触发检查

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/scheduler/MaintenanceScheduler.java
package com.ether.pms.mdm.scheduler;

@Component
@RequiredArgsConstructor
public class MaintenanceScheduler {

    private final MaintenancePlanService planService;
    private final MaintenanceTaskService taskService;

    @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点
    public void checkTimeBasedMaintenance() {
        log.info("检查时间触发的维保计划...");
        List<MaintenancePlan> plans = planService.getTimeBasedActivePlans();
        plans.forEach(plan -> taskService.generateTasksFromPlan(plan));
    }

    @Scheduled(cron = "0 0 * * * ?") // 每小时
    public void checkHourlyMaintenance() {
        log.info("检查运行小时触发的维保计划...");
        // 检查运行小时触发的计划
    }
}

Step 1: 创建定时任务类 Step 2: 在 PmsApplication 添加 @EnableScheduling Step 3: 测试定时任务执行 Step 4: Commit: feat: add maintenance scheduler


阶段三M02-15 能耗监控管理

Task 3.1: 创建能耗相关实体

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyMeter.java
  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyConsumption.java
@Entity
@Table(name = "ops_energy_meter")
@Data
public class EnergyMeter {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(name = "project_id", nullable = false)
    private UUID projectId;

    @Column(name = "meter_code", nullable = false, unique = true)
    private String meterCode;

    @Column(name = "meter_name", nullable = false)
    private String meterName;

    @Column(name = "energy_type", nullable = false)
    @Enumerated(EnumType.STRING)
    private EnergyType energyType;

    public enum EnergyType {
        LIGHTING, // 照明插座用电
        HVAC,     // 空调用电
        POWER,    // 动力用电
        SPECIAL,  // 特殊用电
        WATER,    // 给排水
        GAS       // 燃气
    }

    @Column(name = "space_node_id")
    private UUID spaceNodeId;

    @Column(name = "installation_location")
    private String installationLocation;

    @Column(name = "unit_price", precision = 10, scale = 4)
    private BigDecimal unitPrice;

    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private Status status = Status.ACTIVE;

    public enum Status { ACTIVE, INACTIVE }
}

@Entity
@Table(name = "ops_energy_consumption")
@Data
public class EnergyConsumption {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(name = "project_id", nullable = false)
    private UUID projectId;

    @Column(name = "meter_id", nullable = false)
    private UUID meterId;

    @Column(name = "consumption_date", nullable = false)
    private LocalDate consumptionDate;

    @Column(name = "previous_reading", precision = 12, scale = 2)
    private BigDecimal previousReading;

    @Column(name = "current_reading", precision = 12, scale = 2)
    private BigDecimal currentReading;

    @Column(nullable = false, precision = 12, scale = 2)
    private BigDecimal consumption;

    @Column(precision = 10, scale = 2)
    private BigDecimal amount;

    @Column(name = "recorded_by")
    private UUID recordedBy;

    @Column(name = "record_method")
    @Enumerated(EnumType.STRING)
    private RecordMethod recordMethod = RecordMethod.MANUAL;

    public enum RecordMethod { MANUAL, IOT }
}

Step 1: 创建实体 Step 2: 运行编译验证 Step 3: Commit: feat: add energy management entities


Task 3.2: 创建能耗Service

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyMeterService.java
  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyConsumptionService.java
  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyStatisticsService.java
// EnergyConsumptionService
void recordConsumption(UUID meterId, BigDecimal currentReading, UUID recordedBy);
List<EnergyConsumption> getConsumptionByMeter(UUID meterId, LocalDate startDate, LocalDate endDate);

// EnergyStatisticsService
Map<EnergyType, BigDecimal> getConsumptionByType(UUID projectId, LocalDate month);
BigDecimal getUnitConsumption(UUID projectId, LocalDate month); // kWh/m²
Map<String, BigDecimal> getConsumptionTrend(UUID projectId, Integer months);
List<EnergyAlert> getAlerts(UUID projectId);

Step 1: 创建 Service Step 2: 添加单元测试 Step 3: Commit: feat: add energy service


Task 3.3: 创建能耗Controller

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/controller/EnergyController.java

API 设计:

方法 路径 描述
GET /api/v1/ops/energy-meters 获取计量点列表
POST /api/v1/ops/energy-meters 创建计量点
POST /api/v1/ops/energy-consumption 录入能耗数据
GET /api/v1/ops/energy-statistics/by-type 按分项统计
GET /api/v1/ops/energy-statistics/unit-consumption 单方能耗统计
GET /api/v1/ops/energy-statistics/trend 能耗趋势

Step 1: 创建 Controller Step 2: 测试 API Step 3: Commit: feat: add energy controller


阶段四M02-16 备件库存管理

Task 4.1: 创建备件实体

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePart.java
  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePartRecord.java
@Entity
@Table(name = "ops_spare_part")
@Data
public class SparePart {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(name = "project_id", nullable = false)
    private UUID projectId;

    @Column(name = "spare_part_code", nullable = false, unique = true)
    private String sparePartCode;

    @Column(name = "spare_part_name", nullable = false)
    private String sparePartName;

    @Column(name = "category_id")
    private UUID categoryId;

    @Column
    private String specification;

    @Column(nullable = false)
    private String unit;

    @Column(name = "safe_stock")
    private Integer safeStock;

    @Column(name = "current_stock")
    private Integer currentStock;

    @Column(name = "unit_price", precision = 10, scale = 2)
    private BigDecimal unitPrice;

    @Column
    private String supplier;

    @Column(name = "location")
    private String location;

    @Column
    @Enumerated(EnumType.STRING)
    private Status status = Status.ACTIVE;
}

@Entity
@Table(name = "ops_spare_part_record")
@Data
public class SparePartRecord {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(name = "record_code", nullable = false, unique = true)
    private String recordCode;

    @Column(name = "record_type", nullable = false)
    @Enumerated(EnumType.STRING)
    private RecordType recordType;

    public enum RecordType {
        IN,      // 入库
        OUT,     // 出库
        CHECK,   // 盘点
        ADJUST   // 调整
    }

    @Column(name = "spare_part_id", nullable = false)
    private UUID sparePartId;

    @Column(nullable = false)
    private Integer quantity;

    @Column(nullable = false)
    private Integer balance;

    @Column(name = "related_order_id")
    private UUID relatedOrderId;

    @Column(name = "recorded_by")
    private UUID recordedBy;

    @Column(name = "record_date")
    private LocalDateTime recordDate;
}

Step 1: 创建实体 Step 2: 编译验证 Step 3: Commit: feat: add spare part entities


Task 4.2: 创建备件Service

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/SparePartService.java
public interface SparePartService {
    SparePart create(SparePart sparePart);
    void updateStock(UUID sparePartId, Integer quantity, SparePartRecord.RecordType type, UUID relatedOrderId);
    List<SparePart> getLowStockParts(UUID projectId);
    List<SparePartRecord> getRecordsByPart(UUID sparePartId);
}

Step 1: 创建 Service Step 2: 添加库存扣减逻辑(事务处理) Step 3: 添加单元测试 Step 4: Commit: feat: add spare part service


Task 4.3: 创建备件Controller

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/controller/SparePartController.java

API 设计:

方法 路径 描述
GET /api/v1/ops/spare-parts 获取备件列表
POST /api/v1/ops/spare-parts 创建备件
GET /api/v1/ops/spare-parts/{id} 获取备件详情
GET /api/v1/ops/spare-parts/low-stock 获取低库存备件
POST /api/v1/ops/spare-part-records 创建备件记录
GET /api/v1/ops/spare-part-records/{sparePartId} 获取备件记录

Step 1: 创建 Controller Step 2: 测试 API Step 3: Commit: feat: add spare part controller


阶段五M02-17 设备故障预测

Task 5.1: 创建故障历史实体

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/EquipmentFailureHistory.java
  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/EquipmentHealthScore.java
@Entity
@Table(name = "ops_equipment_failure_history")
@Data
public class EquipmentFailureHistory {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(name = "equipment_id", nullable = false)
    private UUID equipmentId;

    @Column(name = "failure_date", nullable = false)
    private LocalDateTime failureDate;

    @Column(name = "failure_type")
    private String failureType;

    @Column(name = "failure_cause")
    private String failureCause;

    @Column(name = "failure_description", columnDefinition = "TEXT")
    private String failureDescription;

    @Column(name = "repair_start_time")
    private LocalDateTime repairStartTime;

    @Column(name = "repair_end_time")
    private LocalDateTime repairEndTime;

    @Column(name = "repair_duration", precision = 10, scale = 2)
    private BigDecimal repairDuration;

    @Column(name = "repair_cost", precision = 12, scale = 2)
    private BigDecimal repairCost;

    @Column(name = "spare_parts_used", columnDefinition = "TEXT")
    private String sparePartsUsed;

    @Column(name = "work_order_id")
    private UUID workOrderId;
}

@Entity
@Table(name = "ops_equipment_health_score")
@Data
public class EquipmentHealthScore {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(name = "equipment_id", nullable = false)
    private UUID equipmentId;

    @Column(name = "score_date", nullable = false)
    private LocalDate scoreDate;

    @Column(name = "health_score", precision = 5, scale = 2)
    private BigDecimal healthScore;

    @Column(precision = 10, scale = 2)
    private BigDecimal mtbf;

    @Column(precision = 10, scale = 2)
    private BigDecimal mttr;

    @Column(name = "failure_count")
    private Integer failureCount;

    @Column(name = "maintenance_completion_rate", precision = 5, scale = 2)
    private BigDecimal maintenanceCompletionRate;

    @Column(name = "alert_level")
    @Enumerated(EnumType.STRING)
    private AlertLevel alertLevel;

    public enum AlertLevel { NORMAL, WARNING, CRITICAL }
}

Step 1: 创建实体 Step 2: 编译验证 Step 3: Commit: feat: add equipment health entities


Task 5.2: 创建故障预测Service

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/EquipmentHealthService.java
public interface EquipmentHealthService {

    // 计算设备健康度
    EquipmentHealthScore calculateHealthScore(UUID equipmentId);

    // 获取设备MTBF
    BigDecimal calculateMTBF(UUID equipmentId, Integer days);

    // 获取设备MTTR
    BigDecimal calculateMTTR(UUID equipmentId, Integer days);

    // 获取设备健康度历史
    List<EquipmentHealthScore> getHealthHistory(UUID equipmentId);

    // 获取预警设备列表
    List<UUID> getAlertEquipmentIds(UUID projectId, AlertLevel level);

    // 记录故障
    void recordFailure(EquipmentFailureHistory failure);
}

健康度算法实现:

public EquipmentHealthScore calculateHealthScore(UUID equipmentId) {
    // 基础分 = 100
    BigDecimal baseScore = new BigDecimal("100");

    // 故障率扣分 = 故障次数 × 5近30天
    int failureCount = failureHistoryRepository.countByEquipmentIdAndFailureDateAfter(
        equipmentId, LocalDateTime.now().minusDays(30));
    BigDecimal failureDeduction = new BigDecimal(failureCount * 5);

    // 维保扣分 = (1 - 维保完成率) × 20
    BigDecimal maintenanceRate = maintenanceTaskService.getCompletionRate(equipmentId);
    BigDecimal maintenanceDeduction = new BigDecimal("1").subtract(maintenanceRate)
        .multiply(new BigDecimal("20"));

    // 年龄扣分 = 投入使用年限 × 2最高扣10分
    SpaceNode equipment = spaceNodeRepository.findById(equipmentId).orElseThrow();
    int yearsInService = calculateYearsInService(equipment.getCreatedAt());
    BigDecimal ageDeduction = new BigDecimal(Math.min(yearsInService * 2, 10));

    BigDecimal healthScore = baseScore
        .subtract(failureDeduction)
        .subtract(maintenanceDeduction)
        .subtract(ageDeduction);

    return healthScore;
}

Step 1: 创建 Service 接口 Step 2: 实现计算逻辑 Step 3: 添加单元测试 Step 4: Commit: feat: add equipment health service


阶段六M02-18 设备点检标准库

Task 6.1: 创建点检模板实体

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionTemplate.java
@Entity
@Table(name = "ops_inspection_template")
@Data
public class InspectionTemplate {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(name = "project_id", nullable = false)
    private UUID projectId;

    @Column(name = "template_code", nullable = false, unique = true)
    private String templateCode;

    @Column(name = "template_name", nullable = false)
    private String templateName;

    @Column(name = "equipment_type")
    private String equipmentType;

    @Column(name = "inspection_items", columnDefinition = "TEXT")
    private String inspectionItems;

    @Column(name = "estimated_duration")
    private Integer estimatedDuration;

    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private Status status = Status.ACTIVE;

    public enum Status { ACTIVE, INACTIVE }

    @Column
    private Integer version;

    @Column(name = "created_by")
    private UUID createdBy;

    @Column(name = "created_at")
    private LocalDateTime createdAt;

    @PrePersist
    public void prePersist() {
        createdAt = LocalDateTime.now();
        if (version == null) version = 1;
    }
}

Step 1: 创建实体 Step 2: 编译验证 Step 3: Commit: feat: add inspection template entity


Task 6.2: 创建点检Service

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionTemplateService.java
public interface InspectionTemplateService {
    InspectionTemplate create(InspectionTemplate template);
    InspectionTemplate copyTemplate(UUID templateId, String newName);
    List<InspectionTemplate> getTemplatesByType(String equipmentType);
    void updateTemplate(UUID id, InspectionTemplate template);
}

点检项目JSON结构

{
  "items": [
    {
      "itemCode": "I001",
      "itemName": "电机温度检查",
      "inspectionPoint": "电机外壳",
      "inspectionMethod": "HAND_FEEL",
      "standard": "温度正常,无明显过热",
      "normalValue": "≤60°C",
      "abnormalValue": ">70°C",
      "handlingMethod": "停机检查,联系维修",
      "isRequired": true
    }
  ]
}

Step 1: 创建 Service Step 2: 添加 JSON 解析/存储逻辑 Step 3: Commit: feat: add inspection template service


Task 6.3: 创建点检Controller

Files:

  • Create: ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionTemplateController.java

API 设计:

方法 路径 描述
GET /api/v1/ops/inspection-templates 获取点检模板列表
POST /api/v1/ops/inspection-templates 创建点检模板
GET /api/v1/ops/inspection-templates/{id} 获取模板详情
PUT /api/v1/ops/inspection-templates/{id} 更新模板
POST /api/v1/ops/inspection-templates/{id}/copy 复制模板
GET /api/v1/ops/inspection-templates/by-type/{equipmentType} 按设备类型获取

Step 1: 创建 Controller Step 2: 测试 API Step 3: Commit: feat: add inspection template controller


前端页面开发(统一在最后)

Task F.1: 设备管理页面

Files:

  • Create: ether-admin/src/views/equipment/EquipmentList.vue
  • Create: ether-admin/src/views/equipment/EquipmentDetail.vue
  • Create: ether-admin/src/views/equipment/EquipmentForm.vue
  • Modify: ether-admin/src/views/Layout.vue - 添加设备管理菜单

Step 1: 创建设备列表页面 Step 2: 创建设备详情页面 Step 3: 添加设备表单组件 Step 4: 更新 Layout 添加菜单 Step 5: 运行 npm run build 验证 Step 6: Commit: feat: add equipment management frontend


Task F.2: 维保管理页面

Files:

  • Create: ether-admin/src/views/maintenance/PlanList.vue
  • Create: ether-admin/src/views/maintenance/TaskList.vue

Step 1: 创建维保计划列表 Step 2: 创建维保任务列表 Step 3: 编译验证 Step 4: Commit: feat: add maintenance management frontend


Task F.3: 能耗监控页面

Files:

  • Create: ether-admin/src/views/energy/EnergyMeterList.vue
  • Create: ether-admin/src/views/energy/EnergyStatistics.vue

Step 1: 创建计量点管理页面 Step 2: 创建能耗统计页面 Step 3: 编译验证 Step 4: Commit: feat: add energy monitoring frontend


Task F.4: 备件管理页面

Files:

  • Create: ether-admin/src/views/sparepart/SparePartList.vue
  • Create: ether-admin/src/views/sparepart/SparePartRecord.vue

Step 1: 创建备件列表页面 Step 2: 创建备件记录页面 Step 3: 编译验证 Step 4: Commit: feat: add spare part management frontend


测试验证

Task T.1: 后端单元测试

为每个 Service 创建单元测试:

  • SpaceNodeServiceTest
  • MaintenancePlanServiceTest
  • MaintenanceTaskServiceTest
  • EnergyConsumptionServiceTest
  • SparePartServiceTest
  • EquipmentHealthServiceTest
cd ether-pms && mvn test -Dtest=*ServiceTest

Task T.2: 后端集成测试

# 使用 H2 内存数据库进行集成测试
cd ether-pms && mvn verify

Task T.3: 前端构建验证

cd ether-admin && npm run build

Task T.4: E2E 测试

使用 Playwright 进行 E2E 测试:

# 设备管理 E2E
npx playwright test tests/equipment.spec.ts

# 维保管理 E2E
npx playwright test tests/maintenance.spec.ts

数据库迁移脚本汇总

文件列表:

  1. V10__add_equipment_extension_fields.sql - 设备扩展字段
  2. V11__create_maintenance_tables.sql - 维保计划和任务表
  3. V12__create_energy_tables.sql - 能耗表
  4. V13__create_spare_part_tables.sql - 备件表
  5. V14__create_equipment_health_tables.sql - 设备健康表
  6. V15__create_inspection_template_table.sql - 点检模板表

部署检查清单

  • 数据库迁移脚本执行
  • 后端编译通过
  • 后端单元测试通过
  • 前端编译通过
  • API 端点验证
  • E2E 测试通过
  • 文档更新

风险与缓解

风险 影响 缓解措施
定时任务性能 维保检查任务执行慢 使用 @Async 或分布式任务
能耗数据量大 查询性能问题 添加索引,分区表
IoT 数据接入 协议兼容问题 预留适配器接口
历史数据不足 故障预测不准 初期使用规则判断

计划版本: v1.0 最后更新: 2026-03-23