feat(ops,iot): 保洁前端 API 层和区域管理新增

新增保洁业务前端 API 接口层(工牌、工单、仪表盘)和运营区域管理完整功能,包含 Service/Controller/Test 三层结构。

主要功能:

1. IoT 设备查询 API(RPC 接口)
   - IotDeviceQueryApi: 提供设备简化信息查询
   - IotDeviceSimpleRespDTO: 设备简化 DTO

2. 保洁工牌管理
   - CleanBadgeService/Impl: 工牌通知、优先级调整、手动完成
   - BadgeNotifyReqDTO/UpgradePriorityReqDTO/ManualCompleteOrderReqDTO

3. 保洁工单管理
   - CleanWorkOrderService/Impl: 工单时间线查询

4. 保洁仪表盘
   - CleanDashboardService/Impl: 快速统计(待处理/进行中/已完成/在线工牌数)
   - QuickStatsRespDTO: 快速统计 DTO

5. 运营区域管理(Ops Biz)
   - OpsBusAreaService/Impl: 区域 CRUD(支持树形结构、分页查询)
   - AreaDeviceRelationService/Impl: 区域设备关联管理(绑定/解绑/批量更新)
   - OpsBusAreaMapper/AreaDeviceRelationMapper: 扩展 MyBatis 批量方法
   - 7 个 VO 类:CreateReqVO/UpdateReqVO/PageReqVO/RespVO/BindReqVO/RelationRespVO/DeviceUpdateReqVO

6. 前端 Controller(Ops Server)
   - OpsBusAreaController: 区域管理 REST API(11 个接口)
   - AreaDeviceRelationController: 设备关联 REST API(8 个接口)
   - CleanBadgeController: 工牌管理 REST API(5 个接口)
   - CleanDashboardController: 仪表盘 REST API(1 个接口)
   - CleanDeviceController: 设备管理 REST API(2 个接口)
   - CleanWorkOrderController: 工单管理 REST API(2 个接口)

7. 测试覆盖
   - OpsBusAreaServiceTest: 区域服务测试(284 行)
   - AreaDeviceRelationServiceTest: 设备关联测试(240 行)
   - OpsBusAreaControllerTest: 区域 Controller 测试(186 行)
   - AreaDeviceRelationControllerTest: 设备关联 Controller 测试(182 行)

8. API 层扩展
   - ErrorCodeConstants: 错误码常量(区域、设备关联)
   - NotifyTypeEnum: 通知类型枚举(语音、文本、震动)
   - 4 个 Badge/Order DTO: BadgeStatusRespDTO/BadgeRealtimeStatusRespDTO/OrderTimelineRespDTO

9. RPC 配置
   - RpcConfiguration: 注入 IotDeviceQueryApi

影响模块:Ops API、Ops Biz、Ops Server、Ops Environment Biz、IoT API、IoT Server

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-02-02 22:42:45 +08:00
parent bdf5b640b0
commit 955c825e2c
43 changed files with 3312 additions and 1 deletions

View File

@@ -0,0 +1,49 @@
package com.viewsh.module.ops.api.clean;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 工牌实时状态详情响应 DTO
* <p>
* 用于工牌详情页展示
*
* @author lzh
*/
@Schema(description = "管理后台 - 工牌实时状态详情 Response DTO")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BadgeRealtimeStatusRespDTO {
@Schema(description = "设备ID", example = "3001")
private Long deviceId;
@Schema(description = "设备编码", example = "badge_001")
private String deviceKey;
@Schema(description = "设备状态", example = "BUSY")
private String status;
@Schema(description = "电量0-100", example = "72")
private Integer batteryLevel;
@Schema(description = "最后心跳时间", example = "2026-01-23 15:00:30")
private String lastHeartbeatTime;
@Schema(description = "信号强度dBm", example = "-42")
private Integer rssi;
@Schema(description = "是否在区域内", example = "true")
private Boolean isInArea;
@Schema(description = "当前区域ID", example = "101")
private Long areaId;
@Schema(description = "当前区域名称", example = "A区洗手间")
private String areaName;
}

View File

@@ -0,0 +1,49 @@
package com.viewsh.module.ops.api.clean;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 工牌状态响应 DTO
* <p>
* 用于工牌列表展示
*
* @author lzh
*/
@Schema(description = "管理后台 - 工牌状态 Response DTO")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BadgeStatusRespDTO {
@Schema(description = "设备ID", example = "3001")
private Long deviceId;
@Schema(description = "设备编码", example = "badge_001")
private String deviceKey;
@Schema(description = "状态IDLE/BUSY/OFFLINE/PAUSED", example = "IDLE")
private String status;
@Schema(description = "电量0-100", example = "85")
private Integer batteryLevel;
@Schema(description = "最后心跳时间", example = "2026-01-23 14:30:25")
private String lastHeartbeatTime;
@Schema(description = "当前所在区域ID", example = "100")
private Long currentAreaId;
@Schema(description = "当前所在区域名称", example = "A区洗手间")
private String currentAreaName;
@Schema(description = "今日完成工单数", example = "5")
private Integer todayCompletedCount;
@Schema(description = "今日工作时长(分钟)", example = "180")
private Integer todayWorkMinutes;
}

View File

@@ -0,0 +1,63 @@
package com.viewsh.module.ops.api.clean;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
* 工单时间轴响应 DTO
* <p>
* 用于工单详情页时间轴展示
*
* @author lzh
*/
@Schema(description = "管理后台 - 工单时间轴 Response DTO")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderTimelineRespDTO {
@Schema(description = "工单ID", example = "1001")
private Long orderId;
@Schema(description = "当前状态", example = "ARRIVED")
private String currentStatus;
@Schema(description = "时间轴节点列表")
private List<TimelineItemDTO> timeline;
/**
* 时间轴节点 DTO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "时间轴节点")
public static class TimelineItemDTO {
@Schema(description = "状态", example = "PENDING")
private String status;
@Schema(description = "状态名称", example = "工单创建")
private String statusName;
@Schema(description = "时间", example = "2026-01-23 14:30:25")
private String time;
@Schema(description = "操作人", example = "系统")
private String operator;
@Schema(description = "描述", example = "蓝牙信标触发自动创建")
private String description;
@Schema(description = "额外信息如RSSI值、信标ID等")
private Map<String, Object> extra;
}
}

View File

@@ -0,0 +1,34 @@
package com.viewsh.module.ops.api.clean;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 快速统计响应 DTO
* <p>
* 用于工单中心仪表盘快速统计展示
*
* @author lzh
*/
@Schema(description = "管理后台 - 快速统计 Response DTO")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class QuickStatsRespDTO {
@Schema(description = "待分配数量", example = "8")
private Integer pendingCount;
@Schema(description = "进行中数量", example = "15")
private Integer inProgressCount;
@Schema(description = "今日完成数量", example = "42")
private Integer completedTodayCount;
@Schema(description = "在线工牌数量", example = "12")
private Integer onlineBadgeCount;
}

View File

@@ -0,0 +1,25 @@
package com.viewsh.module.ops.enums;
import com.viewsh.framework.common.exception.ErrorCode;
/**
* ops 错误码枚举类
* <p>
* ops 系统,使用 1-020-000-000 段
*/
public interface ErrorCodeConstants {
// ========== 业务区域 1-020-001-000 ============
ErrorCode AREA_NOT_FOUND = new ErrorCode(1_020_001_000, "区域不存在");
ErrorCode AREA_HAS_CHILDREN = new ErrorCode(1_020_001_001, "该区域下存在子区域,请先处理子区域");
ErrorCode AREA_HAS_DEVICES = new ErrorCode(1_020_001_002, "该区域已绑定设备,请先解除绑定");
ErrorCode AREA_PARENT_LOOP = new ErrorCode(1_020_001_003, "不能将父级设置为自己或子孙节点");
ErrorCode AREA_CODE_EXISTS = new ErrorCode(1_020_001_004, "区域编码已存在");
// ========== 区域设备关联 1-020-002-000 ============
ErrorCode DEVICE_NOT_FOUND = new ErrorCode(1_020_002_000, "设备不存在");
ErrorCode DEVICE_ALREADY_BOUND = new ErrorCode(1_020_002_001, "该工牌已绑定至此区域");
ErrorCode DEVICE_TYPE_ALREADY_BOUND = new ErrorCode(1_020_002_002, "该区域已绑定{},一个区域只能绑定一个");
ErrorCode DEVICE_RELATION_NOT_FOUND = new ErrorCode(1_020_002_003, "设备关联关系不存在");
}

View File

@@ -0,0 +1,55 @@
package com.viewsh.module.ops.enums;
import com.viewsh.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 工牌通知类型枚举
* <p>
* 用于工牌设备通知(语音、震动等)
*
* @author lzh
*/
@AllArgsConstructor
@Getter
public enum NotifyTypeEnum implements ArrayValuable<String> {
VOICE("VOICE", "语音通知"),
VIBRATE("VIBRATE", "震动通知");
public static final String[] ARRAYS = Arrays.stream(values()).map(NotifyTypeEnum::getType).toArray(String[]::new);
/**
* 类型
*/
private final String type;
/**
* 描述
*/
private final String description;
@Override
public String[] array() {
return ARRAYS;
}
/**
* 根据类型获取枚举
*
* @param type 类型
* @return 枚举实例,未找到返回 null
*/
public static NotifyTypeEnum fromType(String type) {
if (type == null) {
return null;
}
return Arrays.stream(values())
.filter(e -> e.getType().equals(type))
.findFirst()
.orElse(null);
}
}