feat(ops): add confirm event DTO and update audit DTO

This commit is contained in:
lzh
2026-01-19 14:41:05 +08:00
parent a71a29f548
commit eedef70581
7 changed files with 889 additions and 2 deletions

View File

@@ -0,0 +1,140 @@
package com.viewsh.module.ops.environment.integration.adapter;
import com.viewsh.module.ops.api.badge.BadgeDeviceStatusDTO;
import com.viewsh.module.ops.core.dispatch.model.AssigneeStatus;
import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum;
import java.time.LocalDateTime;
/**
* 工牌设备状态适配器
* <p>
* 将 {@link BadgeDeviceStatusDTO} 适配为通用的 {@link AssigneeStatus} 接口
* <p>
* 设计说明:
* - 适配器模式:将工牌设备状态对象转换为通用接口
* - 设备即执行人用工牌设备ID作为执行人ID参与调度
* - 解耦设计:调度引擎通过通用接口访问设备状态,不依赖具体实现
*
* @author lzh
*/
public class BadgeAssigneeStatusAdapter implements AssigneeStatus {
private final BadgeDeviceStatusDTO badgeStatus;
private final Long waitingTaskCount;
public BadgeAssigneeStatusAdapter(BadgeDeviceStatusDTO badgeStatus) {
this(badgeStatus, 0L);
}
public BadgeAssigneeStatusAdapter(BadgeDeviceStatusDTO badgeStatus, Long waitingTaskCount) {
this.badgeStatus = badgeStatus;
this.waitingTaskCount = waitingTaskCount != null ? waitingTaskCount : 0L;
}
@Override
public String getStatus() {
return badgeStatus != null && badgeStatus.getStatus() != null
? badgeStatus.getStatus().getCode()
: "offline";
}
@Override
public boolean isIdle() {
return badgeStatus != null && badgeStatus.getStatus() == BadgeDeviceStatusEnum.IDLE;
}
@Override
public boolean isBusy() {
return badgeStatus != null && badgeStatus.getStatus() == BadgeDeviceStatusEnum.BUSY;
}
@Override
public boolean isOnline() {
return badgeStatus != null && badgeStatus.isOnline();
}
@Override
public Long getCurrentTaskCount() {
// 有正在执行的工单则返回1否则返回0
return badgeStatus != null && badgeStatus.hasCurrentOrder() ? 1L : 0L;
}
@Override
public Long getWaitingTaskCount() {
return waitingTaskCount;
}
@Override
public Long getAssigneeId() {
// 工牌设备ID作为执行人ID
return badgeStatus != null ? badgeStatus.getDeviceId() : null;
}
@Override
public String getAssigneeName() {
// 工牌设备编码作为执行人名称
return badgeStatus != null ? badgeStatus.getDeviceCode() : null;
}
@Override
public Long getAreaId() {
return badgeStatus != null ? badgeStatus.getCurrentAreaId() : null;
}
@Override
public LocalDateTime getLastHeartbeatTime() {
if (badgeStatus == null || badgeStatus.getLastHeartbeatTime() == null) {
return null;
}
// 将时间戳转换为 LocalDateTime
return LocalDateTime.ofInstant(
java.time.Instant.ofEpochMilli(badgeStatus.getLastHeartbeatTime()),
java.time.ZoneId.systemDefault()
);
}
@Override
public Integer getBatteryLevel() {
return badgeStatus != null ? badgeStatus.getBatteryLevel() : null;
}
@Override
public Object getExtension(String key) {
if (badgeStatus == null) {
return null;
}
// 支持业务特定的扩展属性
switch (key) {
case "currentAreaName":
return badgeStatus.getCurrentAreaName();
case "currentOrderId":
return badgeStatus.getCurrentOpsOrderId();
case "statusChangeTime":
return badgeStatus.getStatusChangeTime();
default:
return null;
}
}
/**
* 获取原始的工牌设备状态对象
*/
public BadgeDeviceStatusDTO getBadgeStatus() {
return badgeStatus;
}
/**
* 创建适配器(静态工厂方法)
*/
public static BadgeAssigneeStatusAdapter of(BadgeDeviceStatusDTO badgeStatus) {
return new BadgeAssigneeStatusAdapter(badgeStatus);
}
/**
* 创建带等待任务数的适配器
*/
public static BadgeAssigneeStatusAdapter of(BadgeDeviceStatusDTO badgeStatus, Long waitingTaskCount) {
return new BadgeAssigneeStatusAdapter(badgeStatus, waitingTaskCount);
}
}

View File

@@ -31,8 +31,13 @@ public class CleanOrderAuditEventDTO {
*/
private Long orderId;
/**
* 审计类型
/**
* 触发来源 (如 IOT_BUTTON_QUERY)
*/
private String triggerSource;
/**
* 审计类型
* <p>
* - BEACON_ARRIVE_CONFIRMED: 蓝牙信标到岗确认
* - BEACON_LEAVE_WARNING_SENT: 离开区域警告已发送

View File

@@ -0,0 +1,47 @@
package com.viewsh.module.ops.environment.integration.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 保洁工单确认事件 DTO
* <p>
* 由 IoT 模块发布Ops 模块消费
*
* @author AI
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CleanOrderConfirmEventDTO extends BaseDeviceEventDTO {
/**
* 工单ID
*/
@JsonProperty("orderId")
private Long orderId;
/**
* 区域ID
*/
@JsonProperty("areaId")
private Long areaId;
/**
* 触发来源
*/
@JsonProperty("triggerSource")
private String triggerSource;
/**
* 按键ID
*/
@JsonProperty("buttonId")
private Integer buttonId;
/**
* 设备Key (IoT模块发来的字段名)
*/
@JsonProperty("deviceKey")
private String deviceKey;
}

View File

@@ -0,0 +1,204 @@
package com.viewsh.module.ops.environment.integration.handler;
import com.viewsh.module.ops.core.badge.BadgeDeviceStatusService;
import com.viewsh.module.ops.core.event.OrderStateChangedEvent;
import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum;
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 工牌设备状态事件处理器
* <p>
* 监听工单状态变更事件,同步更新工牌设备状态
* <p>
* 设计说明:
* - 使用 Spring EventListener 监听工单状态变更事件
* - 根据工单状态转换规则更新工牌设备状态
* - 支持工单分配、开始、暂停、完成等场景
*
* @author lzh
*/
@Slf4j
@Component
public class BadgeDeviceStatusEventHandler {
@Resource
private BadgeDeviceStatusService badgeDeviceStatusService;
/**
* 处理工单状态变更事件
* <p>
* 状态映射规则:
* - PENDING → ASSIGNED: 不影响设备状态(设备可能还在处理其他任务)
* - ASSIGNED → ARRIVED: 设备状态 → BUSY开始执行任务
* - ARRIVED → PAUSED: 设备状态 → PAUSED任务暂停
* - PAUSED → ARRIVED/BUSY: 设备状态 → BUSY任务恢复
* - ARRIVED → COMPLETED: 设备状态 → IDLE任务完成检查是否还有等待任务
* - 任意状态 → CANCELLED: 清除设备当前工单,检查是否还有等待任务
*
* @param event 工单状态变更事件
*/
@EventListener
@Async("opsExecutor")
public void onOrderStateChanged(OrderStateChangedEvent event) {
try {
// 只处理保洁类型的工单
if (!"CLEAN".equals(event.getOrderType())) {
return;
}
log.debug("[BadgeDeviceStatusEventHandler] 收到工单状态变更事件: orderId={}, {} → {}, operatorId={}",
event.getOrderId(), event.getOldStatus(), event.getNewStatus(), event.getOperatorId());
// 获取设备ID从 payload 或 operatorId 获取)
Long deviceId = getDeviceIdFromEvent(event);
if (deviceId == null) {
log.debug("[BadgeDeviceStatusEventHandler] 无法获取设备ID跳过处理: orderId={}", event.getOrderId());
return;
}
// 根据状态转换更新设备状态
handleStatusTransition(deviceId, event);
} catch (Exception e) {
log.error("[BadgeDeviceStatusEventHandler] 处理工单状态变更事件失败: orderId={}", event.getOrderId(), e);
}
}
/**
* 根据状态转换更新设备状态
*/
private void handleStatusTransition(Long deviceId, OrderStateChangedEvent event) {
WorkOrderStatusEnum newStatus = event.getNewStatus();
switch (newStatus) {
case ASSIGNED:
// 工单已分配,不影响设备状态(设备可能还在处理其他任务)
// 设置设备当前工单
badgeDeviceStatusService.setCurrentOrder(deviceId, event.getOrderId());
break;
case ARRIVED:
// 保洁员已到岗,开始执行任务
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY,
event.getOperatorId(), "工单开始执行: " + event.getOrderCode());
badgeDeviceStatusService.setCurrentOrder(deviceId, event.getOrderId());
break;
case PAUSED:
// 任务暂停
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED,
event.getOperatorId(), event.getRemark());
break;
case COMPLETED:
// 任务完成
badgeDeviceStatusService.clearCurrentOrder(deviceId);
// 检查是否还有等待任务,如果有则保持 BUSY否则转为 IDLE
checkAndUpdateDeviceStatusAfterCompletion(deviceId, event);
break;
case CANCELLED:
// 工单取消
badgeDeviceStatusService.clearCurrentOrder(deviceId);
// 检查是否还有等待任务,如果有则保持 BUSY否则转为 IDLE
checkAndUpdateDeviceStatusAfterCompletion(deviceId, event);
break;
default:
log.debug("[BadgeDeviceStatusEventHandler] 不处理的状态: {}", newStatus);
break;
}
}
/**
* 任务完成或取消后,检查并更新设备状态
* <p>
* 如果有等待任务,保持 BUSY 状态
* 如果没有等待任务,转为 IDLE 状态
*/
private void checkAndUpdateDeviceStatusAfterCompletion(Long deviceId, OrderStateChangedEvent event) {
// 检查是否还有等待任务(通过查询队列)
// 这里简化处理,直接转为 IDLE
// 实际业务中可能需要查询队列判断是否还有等待任务
// 检查是否是被P0打断的任务恢复
boolean isResumeFromInterrupt = event.hasPayload("interruptReason")
&& "RESUME_FROM_INTERRUPT".equals(event.getPayloadString("interruptReason"));
if (isResumeFromInterrupt) {
// 恢复被中断的任务,保持 BUSY
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY,
event.getOperatorId(), "恢复被中断的任务");
} else {
// 正常完成,转为 IDLE
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE,
event.getOperatorId(), "任务完成,转为空闲");
}
}
/**
* 从事件中获取设备ID
* <p>
* 优先级:
* 1. payload.deviceId
* 2. operatorId如果 operatorType 是 DEVICE
* 3. 通过工单查询(需要额外的查询,暂不实现)
*/
private Long getDeviceIdFromEvent(OrderStateChangedEvent event) {
// 优先从 payload 获取
Long deviceId = event.getPayloadLong("deviceId");
if (deviceId != null) {
return deviceId;
}
// TODO: 可以通过工单ID查询获取设备ID
// 这需要注入 OrderService 或 OrderMapper
return null;
}
/**
* 处理 P0 紧急任务打断事件
* <p>
* 当 P0 任务需要打断当前任务时:
* 1. 将被打断的设备状态转为 PAUSED
* 2. 记录被打断的工单ID
*
* @param deviceId 被打断的设备ID
* @param interruptedOrderId 被打断的工单ID
* @param urgentOrderId P0紧急工单ID
*/
public void handleP0Interrupt(Long deviceId, Long interruptedOrderId, Long urgentOrderId) {
log.info("[BadgeDeviceStatusEventHandler] P0紧急任务打断: deviceId={}, interruptedOrderId={}, urgentOrderId={}",
deviceId, interruptedOrderId, urgentOrderId);
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED,
null, "被P0紧急任务打断: " + urgentOrderId);
}
/**
* 处理 P0 任务完成后的恢复事件
* <p>
* 当 P0 任务完成后,恢复被打断的任务:
* 1. 将设备状态转为 BUSY
* 2. 设置当前工单为被打断的工单
*
* @param deviceId 设备ID
* @param interruptedOrderId 被打断的工单ID
*/
public void handleP0Resume(Long deviceId, Long interruptedOrderId) {
log.info("[BadgeDeviceStatusEventHandler] P0任务完成恢复被中断任务: deviceId={}, interruptedOrderId={}",
deviceId, interruptedOrderId);
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY,
null, "P0任务完成恢复被中断的任务");
badgeDeviceStatusService.setCurrentOrder(deviceId, interruptedOrderId);
}
}

View File

@@ -0,0 +1,136 @@
package com.viewsh.module.ops.api.badge;
import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 工牌设备状态 DTO
* <p>
* 用于跨模块传输工牌设备状态数据
*
* @author lzh
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BadgeDeviceStatusDTO {
/**
* 工牌设备ID
*/
private Long deviceId;
/**
* 工牌设备编码
*/
private String deviceCode;
/**
* 设备状态
*/
private BadgeDeviceStatusEnum status;
/**
* 电量0-100
*/
private Integer batteryLevel;
/**
* 当前所在区域ID
*/
private Long currentAreaId;
/**
* 当前所在区域名称
*/
private String currentAreaName;
/**
* 当前正在执行的工单ID
*/
private Long currentOpsOrderId;
/**
* 最后心跳时间(时间戳)
*/
private Long lastHeartbeatTime;
/**
* 状态变更时间
*/
private LocalDateTime statusChangeTime;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
// ========== 便捷方法 ==========
/**
* 判断是否在线非OFFLINE状态
*/
public boolean isOnline() {
return status != null && status.isActive();
}
/**
* 判断是否可接新单
*/
public boolean canAcceptNewOrder() {
return status != null && status.isCanAcceptNewOrder();
}
/**
* 判断是否正在执行工单
*/
public boolean isBusy() {
return status == BadgeDeviceStatusEnum.BUSY;
}
/**
* 判断是否已暂停
*/
public boolean isPaused() {
return status == BadgeDeviceStatusEnum.PAUSED;
}
/**
* 判断是否有当前工单
*/
public boolean hasCurrentOrder() {
return currentOpsOrderId != null;
}
/**
* 获取状态代码
*/
public String getStatusCode() {
return status != null ? status.getCode() : null;
}
/**
* 判断心跳是否超时(超过指定分钟数)
*
* @param thresholdMinutes 超时阈值(分钟)
* @return 是否超时
*/
public boolean isHeartbeatTimeout(int thresholdMinutes) {
if (lastHeartbeatTime == null) {
return true;
}
long thresholdMillis = System.currentTimeMillis() - (thresholdMinutes * 60L * 1000L);
return lastHeartbeatTime < thresholdMillis;
}
}

View File

@@ -0,0 +1,126 @@
package com.viewsh.module.ops.enums;
import com.viewsh.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 工牌设备<E8AEBE><E5A487>态枚举
* <p>
* 与 CleanerStatusEnum 保持一致,用于工牌设备状态管理
*
* @author lzh
*/
@AllArgsConstructor
@Getter
public enum BadgeDeviceStatusEnum implements ArrayValuable<String> {
/**
* 空闲 - 可接新单
*/
IDLE("idle", "空闲", true),
/**
* 忙碌 - 正在执行工单
*/
BUSY("busy", "忙碌", false),
/**
* 暂停 - 临时离开或被P0打断
*/
PAUSED("paused", "暂停", false),
/**
* 离线 - 设备无心跳
*/
OFFLINE("offline", "离线", false);
public static final String[] ARRAYS = Arrays.stream(values()).map(BadgeDeviceStatusEnum::getCode).toArray(String[]::new);
/**
* 状态代码
*/
private final String code;
/**
* 状态描述
*/
private final String description;
/**
* 是否可接新单
*/
private final boolean canAcceptNewOrder;
@Override
public String[] array() {
return ARRAYS;
}
/**
* 获取状态代码
*/
public String getStatus() {
return code;
}
/**
* 检查是否为活跃状态非OFFLINE
*/
public boolean isActive() {
return this != OFFLINE;
}
/**
* 检查是否可以转换到目标状态
*
* @param target 目标状态
* @return 是否可以转换
*/
public boolean canTransitionTo(BadgeDeviceStatusEnum target) {
return switch (this) {
case OFFLINE -> target == IDLE;
case IDLE -> target == BUSY || target == OFFLINE;
case BUSY -> target == PAUSED || target == IDLE || target == OFFLINE;
case PAUSED -> target == BUSY || target == IDLE || target == OFFLINE;
};
}
/**
* 根据code获取枚举
*
* @param code 状态代码
* @return 枚举实例未找到返回OFFLINE
*/
public static BadgeDeviceStatusEnum fromCode(String code) {
if (code == null) {
return OFFLINE;
}
return Arrays.stream(values())
.filter(e -> e.getCode().equals(code))
.findFirst()
.orElse(OFFLINE);
}
/**
* 从保洁员状态枚举转换
*
* @param cleanerStatus 保洁员状态枚举
* @return 工牌设备状态枚举
*/
public static BadgeDeviceStatusEnum fromCleanerStatus(CleanerStatusEnum cleanerStatus) {
if (cleanerStatus == null) {
return OFFLINE;
}
return fromCode(cleanerStatus.getCode());
}
/**
* 转换为保洁员状态枚举
*/
public CleanerStatusEnum toCleanerStatus() {
return CleanerStatusEnum.fromCode(code);
}
}

View File

@@ -0,0 +1,229 @@
package com.viewsh.module.ops.core.badge;
import com.viewsh.module.ops.api.badge.BadgeDeviceStatusDTO;
import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum;
import java.util.List;
/**
* 工牌设备状态服务接口
* <p>
* 职责:
* 1. 管理工牌设备状态Redis 存储和查询)
* 2. 处理设备心跳更新
* 3. 状态转换IDLE ↔ BUSY ↔ PAUSED ↔ OFFLINE
* 4. 区域设备查询
* 5. 当前任务关联
* <p>
* 设计原则:
* - 状态存储在 Redis 中,不依赖数据库表
* - 与保洁员状态解耦,只关注设备本身
* - 为调度引擎提供设备状态查询能力
*
* @author lzh
*/
public interface BadgeDeviceStatusService {
// ==================== 状态管理 ====================
/**
* 更新工牌设备状态
* <p>
* 状态转换会记录状态变更时间和操作原因
*
* @param deviceId 设备ID
* @param status 目标状态
* @param operatorId 操作人ID可为null表示系统操作
* @param reason 状态变更原因
*/
void updateBadgeStatus(Long deviceId, BadgeDeviceStatusEnum status, Long operatorId, String reason);
/**
* 批量更新工牌设备状态
*
* @param deviceIds 设备ID列表
* @param status 目标状态
* @param operatorId 操作人ID
* @param reason 状态变更原因
*/
void batchUpdateBadgeStatus(List<Long> deviceIds, BadgeDeviceStatusEnum status, Long operatorId, String reason);
// ==================== 状态查询 ====================
/**
* 获取工牌设备状态
*
* @param deviceId 设备ID
* @return 设备状态DTO不存在返回null
*/
BadgeDeviceStatusDTO getBadgeStatus(Long deviceId);
/**
* 批量获取工牌设备状态
*
* @param deviceIds 设备ID列表
* @return 设备状态DTO列表
*/
List<BadgeDeviceStatusDTO> batchGetBadgeStatus(List<Long> deviceIds);
/**
* 获取指定区域的工牌设备列表
* <p>
* 只返回非 OFFLINE 状态的设备
*
* @param areaId 区域ID
* @return 设备状态DTO列表
*/
List<BadgeDeviceStatusDTO> listBadgesByArea(Long areaId);
/**
* 获取可接单的工牌设备IDLE 状态)
*
* @param areaId 区域ID
* @return 可接单设备列表
*/
List<BadgeDeviceStatusDTO> listAvailableBadges(Long areaId);
/**
* 获取所有活跃的工牌设备非OFFLINE状态
*
* @return 活跃设备列表
*/
List<BadgeDeviceStatusDTO> listActiveBadges();
// ==================== 心跳处理 ====================
/**
* 处理工牌设备心跳
* <p>
* 更新最后心跳时间和电量:
* <ul>
* <li>如果设备之前为 OFFLINE转为 IDLE</li>
* <li>如果设备已存在,更新心跳时间和电量</li>
* <li>如果设备不存在,创建新记录(状态为 IDLE</li>
* </ul>
*
* @param deviceId 设备ID
* @param deviceCode 设备编码
* @param batteryLevel 电量0-100
*/
void handleHeartbeat(Long deviceId, String deviceCode, Integer batteryLevel);
/**
* 处理工牌设备心跳(带区域信息)
*
* @param deviceId 设备ID
* @param deviceCode 设备编码
* @param batteryLevel 电量0-100
* @param areaId 当前所在区域ID
* @param areaName 当前所在区域名称
*/
void handleHeartbeatWithArea(Long deviceId, String deviceCode, Integer batteryLevel,
Long areaId, String areaName);
// ==================== 在线状态检查 ====================
/**
* 检查工牌设备是否在线非OFFLINE状态
*
* @param deviceId 设备ID
* @return 是否在线
*/
boolean isBadgeOnline(Long deviceId);
/**
* 检查工牌设备心跳是否超时
*
* @param deviceId 设备ID
* @param thresholdMinutes 超时阈值(分钟)
* @return 是否超时
*/
boolean isHeartbeatTimeout(Long deviceId, int thresholdMinutes);
/**
* 检查心跳超时并将超时设备设为OFFLINE
* <p>
* 定时任务调用默认超时时间为30分钟
*/
void checkAndMarkOfflineDevices();
// ==================== 工单关联 ====================
/**
* 设置当前工单
*
* @param deviceId 设备ID
* @param orderId 工单ID
*/
void setCurrentOrder(Long deviceId, Long orderId);
/**
* 清除当前工单
*
* @param deviceId 设备ID
*/
void clearCurrentOrder(Long deviceId);
/**
* 获取当前有工单的设备列表
*
* @return 有工单的设备列表
*/
List<BadgeDeviceStatusDTO> listBadgesWithCurrentOrder();
// ==================== 区域管理 ====================
/**
* 更新工牌设备所在区域
*
* @param deviceId 设备ID
* @param areaId 区域ID
* @param areaName 区域名称
*/
void updateBadgeArea(Long deviceId, Long areaId, String areaName);
/**
* 初始化区域设备索引
* <p>
* 从 ops_area_device_relation 表加载 BADGE 类型的设备,
* 建立区域到设备的索引关系
*/
void initAreaDeviceIndex();
/**
* 刷新区域设备索引
* <p>
* 重新从数据库加载区域设备关系
*/
void refreshAreaDeviceIndex();
/**
* 将设备添加到区域索引
*
* @param deviceId 设备ID
* @param areaId 区域ID
*/
void addToAreaIndex(Long deviceId, Long areaId);
/**
* 从区域索引移除设备
*
* @param deviceId 设备ID
* @param areaId 区域ID
*/
void removeFromAreaIndex(Long deviceId, Long areaId);
// ==================== 设备管理 ====================
/**
* 删除工牌设备状态
*
* @param deviceId 设备ID
*/
void deleteBadgeStatus(Long deviceId);
/**
* 清理所有离线设备的状态
*/
void clearOfflineBadges();
}