refactor(websocket): 重构频道订阅处理器基类实现
- 提取公共基类 BaseChannelSubscribeHandler 处理订阅逻辑 - 将原处理器拆分为带 GRPC 调用和简单订阅两种实现 - 使用依赖注入替代 Autowired 注解 - 统一订阅和取消订阅的处理流程 - 优化日志记录和异常处理机制
This commit is contained in:
parent
05b90f8dc3
commit
f57c9b9dda
@ -111,4 +111,5 @@ public class InspectionWaypointController extends BaseController
|
|||||||
{
|
{
|
||||||
return toAjax(inspectionWaypointService.deleteInspectionWaypointByIds(ids));
|
return toAjax(inspectionWaypointService.deleteInspectionWaypointByIds(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,95 @@
|
|||||||
|
package com.cmvr.framework.websocket.handler;
|
||||||
|
|
||||||
|
import com.cmvr.framework.websocket.manager.ChannelSubscriptionManager;
|
||||||
|
import com.cmvr.framework.websocket.message.WSChannelSubscriptionMessage;
|
||||||
|
import com.cmvr.framework.websocket.service.GrpcClientService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public abstract class BaseChannelSubscribeHandler implements WSMessageHandler<WSChannelSubscriptionMessage> {
|
||||||
|
|
||||||
|
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||||
|
protected final ChannelSubscriptionManager channelSubscriptionManager;
|
||||||
|
protected final GrpcClientService grpcClientService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要GRPC远程调用
|
||||||
|
* true=格式 service/method/terminalId/deviceId 走grpc
|
||||||
|
* false=普通频道,只本地订阅,不调用grpc
|
||||||
|
*/
|
||||||
|
protected abstract boolean isNeedGrpc(String channel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析grpc四个参数 [service,method,terminalId,deviceId]
|
||||||
|
* 只有isNeedGrpc=true才会进入执行
|
||||||
|
*/
|
||||||
|
protected abstract String[] parseGrpcParam(String channel);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(WebSocketSession session, WSChannelSubscriptionMessage message) throws Exception {
|
||||||
|
String action = message.getAction();
|
||||||
|
String userId = session.getAttributes().get("userId").toString();
|
||||||
|
String channel = message.getChannel();
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case "subscribe":
|
||||||
|
doSubscribe(channel, session);
|
||||||
|
break;
|
||||||
|
case "unsubscribe":
|
||||||
|
doUnSubscribe(channel, session);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log.warn("未知订阅动作:{},channel:{}", action, channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("用户{} {}频道{}", userId, "subscribe".equals(action) ? "订阅" : "取消订阅", channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 订阅统一模板 */
|
||||||
|
private void doSubscribe(String channel, WebSocketSession session) {
|
||||||
|
boolean needGrpc = isNeedGrpc(channel);
|
||||||
|
// 没人订阅 + 需要grpc → 发起远端订阅
|
||||||
|
if (needGrpc && !channelSubscriptionManager.hasSubscribers(channel)) {
|
||||||
|
String[] params = parseGrpcParam(channel);
|
||||||
|
String service = params[0];
|
||||||
|
String method = params[1];
|
||||||
|
String terminalId = params[2];
|
||||||
|
String deviceId = params[3];
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
grpcClientService.send(true, terminalId, deviceId, service, method);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("频道{} grpc订阅异常", channel, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 本地统一订阅
|
||||||
|
channelSubscriptionManager.subscribe(channel, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 取消订阅统一模板 */
|
||||||
|
private void doUnSubscribe(String channel, WebSocketSession session) {
|
||||||
|
channelSubscriptionManager.unsubscribe(channel, session);
|
||||||
|
boolean needGrpc = isNeedGrpc(channel);
|
||||||
|
// 无任何订阅 + 需要grpc → 远端取消
|
||||||
|
if (needGrpc && !channelSubscriptionManager.hasSubscribers(channel)) {
|
||||||
|
String[] params = parseGrpcParam(channel);
|
||||||
|
String service = params[0];
|
||||||
|
String method = params[1];
|
||||||
|
String terminalId = params[2];
|
||||||
|
String deviceId = params[3];
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
grpcClientService.send(false, terminalId, deviceId, service, method);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("频道{} grpc取消订阅异常", channel, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,67 +5,26 @@ import com.cmvr.framework.websocket.enums.WSMessageTypeEnum;
|
|||||||
import com.cmvr.framework.websocket.manager.ChannelSubscriptionManager;
|
import com.cmvr.framework.websocket.manager.ChannelSubscriptionManager;
|
||||||
import com.cmvr.framework.websocket.message.WSChannelSubscriptionMessage;
|
import com.cmvr.framework.websocket.message.WSChannelSubscriptionMessage;
|
||||||
import com.cmvr.framework.websocket.service.GrpcClientService;
|
import com.cmvr.framework.websocket.service.GrpcClientService;
|
||||||
import org.slf4j.Logger;
|
import org.springframework.stereotype.Component;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
/**
|
@Component
|
||||||
* 频道订阅消息处理
|
|
||||||
*/
|
|
||||||
@WSMessageType(value = WSMessageTypeEnum.CHANNEL_SUBSCRIPTION, messageClass = WSChannelSubscriptionMessage.class)
|
@WSMessageType(value = WSMessageTypeEnum.CHANNEL_SUBSCRIPTION, messageClass = WSChannelSubscriptionMessage.class)
|
||||||
public class ChannelSubscriptionMessageHandler implements WSMessageHandler<WSChannelSubscriptionMessage> {
|
public class ChannelSubscriptionMessageHandler extends BaseChannelSubscribeHandler {
|
||||||
private static final Logger log = LoggerFactory.getLogger(ChannelSubscriptionMessageHandler.class);
|
|
||||||
@Autowired
|
public ChannelSubscriptionMessageHandler(ChannelSubscriptionManager channelSubscriptionManager, GrpcClientService grpcClientService) {
|
||||||
private ChannelSubscriptionManager channelSubscriptionManager;
|
super(channelSubscriptionManager, grpcClientService);
|
||||||
@Autowired
|
|
||||||
private GrpcClientService grpcClientService;
|
|
||||||
public void handle(WebSocketSession session, WSChannelSubscriptionMessage message) throws Exception {
|
|
||||||
String action = message.getAction();
|
|
||||||
String userId = session.getAttributes().get("userId").toString();
|
|
||||||
// service/method/terminalId/deviceId
|
|
||||||
String channel = message.getChannel();
|
|
||||||
String[] channelInfoArr = channel.split("\\/");
|
|
||||||
String service = channelInfoArr[0];
|
|
||||||
String method = channelInfoArr[1];
|
|
||||||
String terminalId = channelInfoArr[2];
|
|
||||||
String deviceId = channelInfoArr[3];
|
|
||||||
if ("subscribe".equals(action)) {
|
|
||||||
if (!channelSubscriptionManager.hasSubscribers(channel)) {
|
|
||||||
// 异步调用gRPC
|
|
||||||
CompletableFuture.runAsync(() -> {
|
|
||||||
try {
|
|
||||||
grpcClientService.send(true, terminalId, deviceId, service, method);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("gRPC订阅失败: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
channelSubscriptionManager.subscribe(channel, session);
|
|
||||||
} else if ("unsubscribe".equals(action)) {
|
|
||||||
channelSubscriptionManager.unsubscribe(channel, session);
|
|
||||||
if (!channelSubscriptionManager.hasSubscribers(channel)) {
|
|
||||||
CompletableFuture.runAsync(() -> {
|
|
||||||
try {
|
|
||||||
grpcClientService.send(false, terminalId, deviceId, service, method);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("gRPC取消订阅失败: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.out.println("未知频道操作: " + action);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统一记录日志
|
// 当前频道带 / 分段,需要grpc
|
||||||
log.info("用户{} {} 频道{}", userId, getActionDescription(action), channel);
|
@Override
|
||||||
|
protected boolean isNeedGrpc(String channel) {
|
||||||
|
return channel.contains("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据动作获取描述文本
|
@Override
|
||||||
private String getActionDescription(String action) {
|
protected String[] parseGrpcParam(String channel) {
|
||||||
return "subscribe".equals(action) ? "订阅了" : "取消订阅了";
|
return channel.split("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,12 +4,10 @@ import com.cmvr.framework.websocket.annotation.WSMessageType;
|
|||||||
import com.cmvr.framework.websocket.enums.WSMessageTypeEnum;
|
import com.cmvr.framework.websocket.enums.WSMessageTypeEnum;
|
||||||
import com.cmvr.framework.websocket.message.WSHeartbeatMessage;
|
import com.cmvr.framework.websocket.message.WSHeartbeatMessage;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@WSMessageType(value = WSMessageTypeEnum.HEARTBEAT, messageClass = WSHeartbeatMessage.class)
|
@WSMessageType(value = WSMessageTypeEnum.HEARTBEAT, messageClass = WSHeartbeatMessage.class)
|
||||||
public class HeartbeatMessageHandler implements WSMessageHandler<WSHeartbeatMessage>{
|
public class HeartbeatMessageHandler implements WSMessageHandler<WSHeartbeatMessage>{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(WebSocketSession session, WSHeartbeatMessage message) throws IOException {
|
public void handle(WebSocketSession session, WSHeartbeatMessage message) throws IOException {
|
||||||
System.out.println("heartbeat");
|
System.out.println("heartbeat");
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.cmvr.framework.websocket.handler;
|
||||||
|
|
||||||
|
import com.cmvr.framework.websocket.annotation.WSMessageType;
|
||||||
|
import com.cmvr.framework.websocket.enums.WSMessageTypeEnum;
|
||||||
|
import com.cmvr.framework.websocket.manager.ChannelSubscriptionManager;
|
||||||
|
import com.cmvr.framework.websocket.message.WSChannelSubscriptionMessage;
|
||||||
|
import com.cmvr.framework.websocket.service.GrpcClientService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@WSMessageType(value = WSMessageTypeEnum.CHANNEL_SUBSCRIPTION, messageClass = WSChannelSubscriptionMessage.class)
|
||||||
|
public class SimpleChannelSubscribeHandler extends BaseChannelSubscribeHandler {
|
||||||
|
|
||||||
|
public SimpleChannelSubscribeHandler(ChannelSubscriptionManager channelSubscriptionManager, GrpcClientService grpcClientService) {
|
||||||
|
super(channelSubscriptionManager, grpcClientService);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单频道不需要grpc调用
|
||||||
|
@Override
|
||||||
|
protected boolean isNeedGrpc(String channel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不会执行到,随便返回
|
||||||
|
@Override
|
||||||
|
protected String[] parseGrpcParam(String channel) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -51,4 +51,8 @@ public class InspectionTask extends BaseEntity
|
|||||||
@ApiModelProperty("地图id")
|
@ApiModelProperty("地图id")
|
||||||
private String mapId;
|
private String mapId;
|
||||||
|
|
||||||
|
@Excel(name = "点位数量")
|
||||||
|
@ApiModelProperty("点位数量")
|
||||||
|
private Integer waypointCount;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,4 +54,8 @@ public class InspectionTaskInstance extends BaseEntity
|
|||||||
@ApiModelProperty("检测任务id")
|
@ApiModelProperty("检测任务id")
|
||||||
private String taskInsId;
|
private String taskInsId;
|
||||||
|
|
||||||
|
@Excel(name = "点位数量")
|
||||||
|
@ApiModelProperty("点位数量")
|
||||||
|
private Integer waypointCount;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,4 +94,8 @@ public class InspectionTaskInstanceVo extends BaseEntity
|
|||||||
@ApiModelProperty("检测任务id")
|
@ApiModelProperty("检测任务id")
|
||||||
private String taskInsId;
|
private String taskInsId;
|
||||||
|
|
||||||
|
@Excel(name = "点位数量")
|
||||||
|
@ApiModelProperty("点位数量")
|
||||||
|
private Integer waypointCount;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,4 +66,8 @@ public class InspectionTaskVo extends BaseEntity
|
|||||||
@ApiModelProperty("地图名称")
|
@ApiModelProperty("地图名称")
|
||||||
private String mapName;
|
private String mapName;
|
||||||
|
|
||||||
|
@Excel(name = "点位数量")
|
||||||
|
@ApiModelProperty("点位数量")
|
||||||
|
private Integer waypointCount;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.cmvr.inspection.listener;
|
package com.cmvr.inspection.listener;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.cmvr.common.utils.DateUtils;
|
import com.cmvr.common.utils.DateUtils;
|
||||||
import com.cmvr.framework.websocket.service.MessagePushService;
|
import com.cmvr.framework.websocket.service.MessagePushService;
|
||||||
import com.cmvr.inspection.domain.InspectionTaskInstance;
|
import com.cmvr.inspection.domain.InspectionTaskInstance;
|
||||||
@ -9,74 +10,144 @@ import com.cmvr.inspection.service.IInspectionTaskInstanceService;
|
|||||||
import com.cmvr.test.flow.runtime.event.FlowExecutionEvent;
|
import com.cmvr.test.flow.runtime.event.FlowExecutionEvent;
|
||||||
import com.cmvr.test.flow.runtime.event.FlowExecutionListener;
|
import com.cmvr.test.flow.runtime.event.FlowExecutionListener;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class InspectionFlowExecutionListener implements FlowExecutionListener {
|
public class InspectionFlowExecutionListener implements FlowExecutionListener {
|
||||||
@Autowired
|
private final IInspectionTaskInstanceService inspectionTaskInstanceService;
|
||||||
private IInspectionTaskInstanceService inspectionTaskInstanceService;
|
private final MessagePushService messagePushService;
|
||||||
@Autowired
|
|
||||||
private MessagePushService messagePushService;
|
/** key:itemId, value:已完成节点集合 */
|
||||||
|
private final Map<String, Set<String>> itemNodeExecutMap = new HashMap<>();
|
||||||
|
/** key:taskInsId(流程实例id=taskInsId), value:已执行item集合 */
|
||||||
|
private final Map<String, Set<String>> taskItemExecutMap = new HashMap<>();
|
||||||
|
|
||||||
|
public InspectionFlowExecutionListener(IInspectionTaskInstanceService inspectionTaskInstanceService, MessagePushService messagePushService) {
|
||||||
|
this.inspectionTaskInstanceService = inspectionTaskInstanceService;
|
||||||
|
this.messagePushService = messagePushService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(FlowExecutionEvent event) {
|
public void onEvent(FlowExecutionEvent event) {
|
||||||
// 节点执行事件
|
String instId = event.getInstId();
|
||||||
if (event.getNodeId() != null) {
|
String itemId = event.getItemId();
|
||||||
String info = event.getEventType() == FlowExecutionEvent.EventType.NODE_STARTED ? "开始执行" : "执行完成";
|
String nodeId = event.getNodeId();
|
||||||
// 查询巡检任务实例,event中的instId是巡检任务中的taskInsId
|
if (StrUtil.isBlank(instId)) {
|
||||||
InspectionTaskInstance inspectionTaskInstance = inspectionTaskInstanceService.lambdaQuery().eq(InspectionTaskInstance::getTaskInsId, event.getInstId())
|
return;
|
||||||
.one();
|
|
||||||
String insId = inspectionTaskInstance.getId();
|
|
||||||
Map<String, String> data = new HashMap<>();
|
|
||||||
data.put("insId", insId);
|
|
||||||
data.put("logInfo", info);
|
|
||||||
try {
|
|
||||||
messagePushService.pushToChannel("Inspection/TaskInstance", data);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.info("如果没人订阅,则吃掉异常");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1.节点完成才记录当前item下node
|
||||||
|
if (StrUtil.isNotBlank(itemId) && StrUtil.isNotBlank(nodeId)
|
||||||
|
&& FlowExecutionEvent.EventType.NODE_COMPLETED == event.getEventType()) {
|
||||||
|
itemNodeExecutMap.computeIfAbsent(itemId, k -> new HashSet<>()).add(nodeId);
|
||||||
}
|
}
|
||||||
// 通过 instId 找到巡检任务实例,并修改其状态
|
// 任意事件:当前任务绑定item(item一启动就入Map,所以统计已完成item要-1)
|
||||||
// 1.构造更新条件
|
if (StrUtil.isNotBlank(instId) && StrUtil.isNotBlank(itemId)) {
|
||||||
LambdaQueryChainWrapper<InspectionTaskInstance> wrapper = inspectionTaskInstanceService.lambdaQuery()
|
taskItemExecutMap.computeIfAbsent(instId, k -> new HashSet<>()).add(itemId);
|
||||||
.eq(InspectionTaskInstance::getTaskInsId, event.getInstId());
|
}
|
||||||
// 2.要更新的字段
|
|
||||||
InspectionTaskInstance updateEntity = getInspectionTaskInstance();
|
InspectionTaskInstance taskInstance = inspectionTaskInstanceService.lambdaQuery()
|
||||||
|
.eq(InspectionTaskInstance::getTaskInsId, instId)
|
||||||
|
.one();
|
||||||
|
if (taskInstance == null) {
|
||||||
|
log.warn("未查询到巡检实例,taskInsId:{}", instId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int targetStatus = TaskStatusEnum.RUNNING.getCode();
|
||||||
|
boolean needUpdateDb = false;
|
||||||
|
Date now = DateUtils.getNowDate();
|
||||||
switch (event.getEventType()) {
|
switch (event.getEventType()) {
|
||||||
case TASK_COMPLETED:
|
case TASK_COMPLETED:
|
||||||
// 设置状态为成功
|
targetStatus = TaskStatusEnum.SUCCESS.getCode();
|
||||||
updateEntity.setStatus(TaskStatusEnum.SUCCESS.getCode());
|
needUpdateDb = true;
|
||||||
// 正确API:update(更新实体, 条件Wrapper)
|
clearCache(instId);
|
||||||
inspectionTaskInstanceService.update(updateEntity, wrapper.getWrapper());
|
pushCompleteMsg(taskInstance.getId(), now, targetStatus);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TASK_FAILED:
|
case TASK_FAILED:
|
||||||
// 设置状态为失败
|
targetStatus = TaskStatusEnum.FAILED.getCode();
|
||||||
updateEntity.setStatus(TaskStatusEnum.FAILED.getCode());
|
needUpdateDb = true;
|
||||||
// 正确API:update(更新实体, 条件Wrapper)
|
clearCache(instId);
|
||||||
inspectionTaskInstanceService.update(updateEntity, wrapper.getWrapper());
|
|
||||||
break;
|
break;
|
||||||
case NODE_COMPLETED:
|
case NODE_COMPLETED:
|
||||||
// 判断当前任务所有节点是否执行完毕
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (needUpdateDb) {
|
||||||
|
taskInstance.setStatus(targetStatus);
|
||||||
|
taskInstance.setEndTime(now);
|
||||||
|
inspectionTaskInstanceService.updateById(taskInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
// 节点推送
|
||||||
private static InspectionTaskInstance getInspectionTaskInstance() {
|
if (StrUtil.isNotBlank(nodeId)) {
|
||||||
InspectionTaskInstance updateEntity = InspectionTaskInstance.builder()
|
int waypointCount = taskInstance.getWaypointCount() == null ? 0 : taskInstance.getWaypointCount();
|
||||||
.endTime(DateUtils.getNowDate())
|
double progress = 0D;
|
||||||
|
if (waypointCount > 0) {
|
||||||
|
// 当前任务已执行item数量(item启动就存入集合,已完整跑完的item=总数-1)
|
||||||
|
Set<String> itemSet = taskItemExecutMap.getOrDefault(instId, new HashSet<>());
|
||||||
|
int itemTotal = itemSet.size();
|
||||||
|
int finishedItemNum = itemTotal - 1;
|
||||||
|
|
||||||
|
// 当前item已完成节点数
|
||||||
|
Set<String> finishedNodeSet = itemNodeExecutMap.getOrDefault(itemId, new HashSet<>());
|
||||||
|
int finishedNode = finishedNodeSet.size();
|
||||||
|
int totalNode = event.getNodeCount() == 0 ? 1 : event.getNodeCount();
|
||||||
|
|
||||||
|
// 已完整跑完点位占比
|
||||||
|
double itemTotalRatio = finishedItemNum / (double) waypointCount;
|
||||||
|
// 当前运行item内部节点占比
|
||||||
|
double innerRatio = finishedNode / (double) totalNode / waypointCount;
|
||||||
|
|
||||||
|
// 还原你的兜底:防止循环节点进度超限,最低不低于极小值
|
||||||
|
double singleItemBase = 1d / waypointCount;
|
||||||
|
innerRatio = Math.max(singleItemBase - 0.01, innerRatio);
|
||||||
|
|
||||||
|
progress = (itemTotalRatio + innerRatio) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
String info = event.getEventType() == FlowExecutionEvent.EventType.NODE_STARTED ? "开始执行" : "执行完成";
|
||||||
|
Map<String, Object> pushMap = MapUtil.<String, Object>builder()
|
||||||
|
.put("createTime", now)
|
||||||
|
.put("insId", taskInstance.getId())
|
||||||
|
.put("logInfo", "节点【" + event.getNodeName() + "】" + info)
|
||||||
|
.put("status", targetStatus)
|
||||||
|
.put("progress", String.format("%.2f", progress))
|
||||||
|
.put("itemId", itemId)
|
||||||
|
.put("taskId", taskInstance.getTaskId())
|
||||||
.build();
|
.build();
|
||||||
updateEntity.setUpdateTime(DateUtils.getNowDate());
|
try {
|
||||||
return updateEntity;
|
messagePushService.pushToChannel("InspectionTaskInstance", pushMap);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.info("消息推送异常,无订阅忽略,itemId:{}", itemId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 任务结束统一清理缓存 */
|
||||||
|
private void clearCache(String taskInsId) {
|
||||||
|
Set<String> itemList = taskItemExecutMap.getOrDefault(taskInsId, new HashSet<>());
|
||||||
|
itemList.forEach(itemNodeExecutMap::remove);
|
||||||
|
taskItemExecutMap.remove(taskInsId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送完成任务信息(参数dbInsId为数据库主键ID)
|
||||||
|
*/
|
||||||
|
public void pushCompleteMsg(String dbInsId, Date now, int targetStatus) {
|
||||||
|
Map<String, Object> pushMap = MapUtil.<String, Object>builder()
|
||||||
|
.put("createTime", now)
|
||||||
|
.put("insId", dbInsId)
|
||||||
|
.put("status", targetStatus)
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
messagePushService.pushToChannel("InspectionTaskInstance", pushMap);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.info("消息推送异常,无订阅忽略,insId:{}", dbInsId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -50,4 +50,6 @@ public interface InspectionTaskWaypointMapper extends MPJBaseMapper<InspectionT
|
|||||||
* @param waypointIds 点位ID数组
|
* @param waypointIds 点位ID数组
|
||||||
*/
|
*/
|
||||||
void deleteTaskWaypointByWaypointIds(String[] waypointIds);
|
void deleteTaskWaypointByWaypointIds(String[] waypointIds);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,4 +52,6 @@ public interface InspectionWaypointMapper extends MPJBaseMapper<InspectionWaypo
|
|||||||
* @return 巡检点位视图集合
|
* @return 巡检点位视图集合
|
||||||
*/
|
*/
|
||||||
List<InspectionWaypointVo> selectInspectionWaypointVoList(InspectionWaypoint inspectionWaypoint);
|
List<InspectionWaypointVo> selectInspectionWaypointVoList(InspectionWaypoint inspectionWaypoint);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,6 +88,9 @@ public class InspectionTaskInstanceServiceImpl extends ServiceImpl<InspectionTas
|
|||||||
inspectionTaskInstance.setStatus(TaskStatusEnum.NOT_STARTED.getCode());
|
inspectionTaskInstance.setStatus(TaskStatusEnum.NOT_STARTED.getCode());
|
||||||
inspectionTaskInstance.setCreateTime(DateUtils.getNowDate());
|
inspectionTaskInstance.setCreateTime(DateUtils.getNowDate());
|
||||||
inspectionTaskInstance.setCreateBy(SecurityUtils.getUsername());
|
inspectionTaskInstance.setCreateBy(SecurityUtils.getUsername());
|
||||||
|
// 获取点位数量
|
||||||
|
InspectionTask inspectionTask = inspectionTaskService.selectInspectionTaskById(inspectionTaskInstance.getTaskId());
|
||||||
|
inspectionTaskInstance.setWaypointCount(inspectionTask.getWaypointCount());
|
||||||
return inspectionTaskInstanceMapper.insertInspectionTaskInstance(inspectionTaskInstance);
|
return inspectionTaskInstanceMapper.insertInspectionTaskInstance(inspectionTaskInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +109,9 @@ public class InspectionTaskInstanceServiceImpl extends ServiceImpl<InspectionTas
|
|||||||
}
|
}
|
||||||
inspectionTaskInstance.setUpdateTime(DateUtils.getNowDate());
|
inspectionTaskInstance.setUpdateTime(DateUtils.getNowDate());
|
||||||
inspectionTaskInstance.setUpdateBy(SecurityUtils.getUsername());
|
inspectionTaskInstance.setUpdateBy(SecurityUtils.getUsername());
|
||||||
|
// 获取点位数量
|
||||||
|
InspectionTask inspectionTask = inspectionTaskService.selectInspectionTaskById(inspectionTaskInstance.getTaskId());
|
||||||
|
inspectionTaskInstance.setWaypointCount(inspectionTask.getWaypointCount());
|
||||||
return inspectionTaskInstanceMapper.updateInspectionTaskInstance(inspectionTaskInstance);
|
return inspectionTaskInstanceMapper.updateInspectionTaskInstance(inspectionTaskInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.cmvr.inspection.domain.InspectionTaskWaypoint;
|
import com.cmvr.inspection.domain.InspectionTaskWaypoint;
|
||||||
import com.cmvr.inspection.domain.InspectionWaypoint;
|
import com.cmvr.inspection.domain.InspectionWaypoint;
|
||||||
@ -162,8 +161,10 @@ public class InspectionTaskServiceImpl extends ServiceImpl<InspectionTaskMapper,
|
|||||||
list.add(tw);
|
list.add(tw);
|
||||||
}
|
}
|
||||||
inspectionTaskWaypointMapper.batchInsertTaskWaypoint(list);
|
inspectionTaskWaypointMapper.batchInsertTaskWaypoint(list);
|
||||||
// 查询巡检任务
|
// 查询巡检任务,并修改点位数量
|
||||||
InspectionTask inspectionTask = inspectionTaskMapper.selectInspectionTaskById(taskId);
|
InspectionTask inspectionTask = inspectionTaskMapper.selectInspectionTaskById(taskId);
|
||||||
|
inspectionTask.setWaypointCount(waypointIds.length);
|
||||||
|
inspectionTaskMapper.updateInspectionTask(inspectionTask);
|
||||||
// 查询点位
|
// 查询点位
|
||||||
List<InspectionWaypoint> waypoints = inspectionTaskWaypointMapper.selectWaypointsByTaskId(taskId);
|
List<InspectionWaypoint> waypoints = inspectionTaskWaypointMapper.selectWaypointsByTaskId(taskId);
|
||||||
// 同步任务编排
|
// 同步任务编排
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package com.cmvr.inspection.service.impl;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.cmvr.inspection.domain.vo.InspectionWaypointVo;
|
import com.cmvr.inspection.domain.vo.InspectionWaypointVo;
|
||||||
import com.cmvr.inspection.mapper.InspectionTaskWaypointMapper;
|
import com.cmvr.inspection.mapper.InspectionTaskWaypointMapper;
|
||||||
@ -115,5 +114,4 @@ public class InspectionWaypointServiceImpl extends ServiceImpl<InspectionWaypoin
|
|||||||
}
|
}
|
||||||
return inspectionWaypointMapper.deleteInspectionWaypointByIds(ids);
|
return inspectionWaypointMapper.deleteInspectionWaypointByIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,10 +41,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="createByName" column="create_by_name" />
|
<result property="createByName" column="create_by_name" />
|
||||||
<result property="updateByName" column="update_by_name" />
|
<result property="updateByName" column="update_by_name" />
|
||||||
<result property="taskInsId" column="task_ins_id" />
|
<result property="taskInsId" column="task_ins_id" />
|
||||||
|
<result property="waypointCount" column="waypoint_count" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectInspectionTaskInstanceVo">
|
<sql id="selectInspectionTaskInstanceVo">
|
||||||
select id, create_by, create_time, update_by, update_time, remark, task_id, robot_id, status, start_time, end_time, task_ins_id from inspection_task_instance
|
select id, create_by, create_time, update_by, update_time, remark, task_id, robot_id, status, start_time, end_time, task_ins_id, waypoint_count from inspection_task_instance
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="selectInspectionTaskInstanceList" parameterType="InspectionTaskInstance" resultMap="InspectionTaskInstanceResult">
|
<select id="selectInspectionTaskInstanceList" parameterType="InspectionTaskInstance" resultMap="InspectionTaskInstanceResult">
|
||||||
@ -76,6 +77,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="startTime != null">start_time,</if>
|
<if test="startTime != null">start_time,</if>
|
||||||
<if test="endTime != null">end_time,</if>
|
<if test="endTime != null">end_time,</if>
|
||||||
<if test="taskInsId != null">task_ins_id,</if>
|
<if test="taskInsId != null">task_ins_id,</if>
|
||||||
|
<if test="waypointCount != null">waypoint_count,</if>
|
||||||
</trim>
|
</trim>
|
||||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||||
<if test="id != null">#{id},</if>
|
<if test="id != null">#{id},</if>
|
||||||
@ -90,6 +92,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="startTime != null">#{startTime},</if>
|
<if test="startTime != null">#{startTime},</if>
|
||||||
<if test="endTime != null">#{endTime},</if>
|
<if test="endTime != null">#{endTime},</if>
|
||||||
<if test="taskInsId != null">#{taskInsId},</if>
|
<if test="taskInsId != null">#{taskInsId},</if>
|
||||||
|
<if test="waypointCount != null">#{waypointCount},</if>
|
||||||
</trim>
|
</trim>
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
@ -107,6 +110,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="startTime != null">start_time = #{startTime},</if>
|
<if test="startTime != null">start_time = #{startTime},</if>
|
||||||
<if test="endTime != null">end_time = #{endTime},</if>
|
<if test="endTime != null">end_time = #{endTime},</if>
|
||||||
<if test="taskInsId != null">task_ins_id = #{taskInsId},</if>
|
<if test="taskInsId != null">task_ins_id = #{taskInsId},</if>
|
||||||
|
<if test="waypointCount != null">waypoint_count = #{waypointCount},</if>
|
||||||
</trim>
|
</trim>
|
||||||
where id = #{id}
|
where id = #{id}
|
||||||
</update>
|
</update>
|
||||||
@ -123,7 +127,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
<select id="selectInspectionTaskInstanceVoList" parameterType="InspectionTaskInstanceQuery" resultMap="InspectionTaskInstanceVoResult">
|
<select id="selectInspectionTaskInstanceVoList" parameterType="InspectionTaskInstanceQuery" resultMap="InspectionTaskInstanceVoResult">
|
||||||
select ti.id, ti.create_by, ti.create_time, ti.update_by, ti.update_time, ti.remark,
|
select ti.id, ti.create_by, ti.create_time, ti.update_by, ti.update_time, ti.remark,ti.waypoint_count,
|
||||||
ti.task_id, t.task_name, t.task_code,
|
ti.task_id, t.task_name, t.task_code,
|
||||||
ti.robot_id, r.robot_name,
|
ti.robot_id, r.robot_name,
|
||||||
r.current_map_id as robot_current_map_id, m.map_name,
|
r.current_map_id as robot_current_map_id, m.map_name,
|
||||||
|
|||||||
@ -36,7 +36,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectInspectionTaskVo">
|
<sql id="selectInspectionTaskVo">
|
||||||
select id, create_by, create_time, update_by, update_time, remark, task_code, task_name, task_type, task_config_id, map_id from inspection_task
|
select id, create_by, create_time, update_by, update_time, remark, task_code, task_name, task_type, task_config_id, map_id, waypoint_count from inspection_task
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="selectInspectionTaskList" parameterType="InspectionTask" resultMap="InspectionTaskResult">
|
<select id="selectInspectionTaskList" parameterType="InspectionTask" resultMap="InspectionTaskResult">
|
||||||
@ -67,6 +67,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="taskType != null">task_type,</if>
|
<if test="taskType != null">task_type,</if>
|
||||||
<if test="taskConfigId != null">task_config_id,</if>
|
<if test="taskConfigId != null">task_config_id,</if>
|
||||||
<if test="mapId != null">map_id,</if>
|
<if test="mapId != null">map_id,</if>
|
||||||
|
<if test="waypointCount != null">waypoint_count,</if>
|
||||||
</trim>
|
</trim>
|
||||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||||
<if test="id != null">#{id},</if>
|
<if test="id != null">#{id},</if>
|
||||||
@ -80,6 +81,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="taskType != null">#{taskType},</if>
|
<if test="taskType != null">#{taskType},</if>
|
||||||
<if test="taskConfigId != null">#{taskConfigId},</if>
|
<if test="taskConfigId != null">#{taskConfigId},</if>
|
||||||
<if test="mapId != null">#{mapId},</if>
|
<if test="mapId != null">#{mapId},</if>
|
||||||
|
<if test="waypointCount != null">#{waypointCount},</if>
|
||||||
</trim>
|
</trim>
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
@ -96,6 +98,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="taskType != null">task_type = #{taskType},</if>
|
<if test="taskType != null">task_type = #{taskType},</if>
|
||||||
<if test="taskConfigId != null">task_config_id = #{taskConfigId},</if>
|
<if test="taskConfigId != null">task_config_id = #{taskConfigId},</if>
|
||||||
<if test="mapId != null">map_id = #{mapId},</if>
|
<if test="mapId != null">map_id = #{mapId},</if>
|
||||||
|
<if test="waypointCount != null">waypoint_count = #{waypointCount},</if>
|
||||||
</trim>
|
</trim>
|
||||||
where id = #{id}
|
where id = #{id}
|
||||||
</update>
|
</update>
|
||||||
@ -113,7 +116,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
|
|
||||||
<select id="selectInspectionTaskVoList" parameterType="InspectionTask" resultMap="InspectionTaskVoResult">
|
<select id="selectInspectionTaskVoList" parameterType="InspectionTask" resultMap="InspectionTaskVoResult">
|
||||||
select t.id, t.create_by, t.create_time, t.update_by, t.update_time, t.remark,
|
select t.id, t.create_by, t.create_time, t.update_by, t.update_time, t.remark,
|
||||||
t.task_code, t.task_name, t.task_type, t.task_config_id, t.map_id,
|
t.task_code, t.task_name, t.task_type, t.task_config_id, t.map_id, t.waypoint_count,
|
||||||
m.map_name,
|
m.map_name,
|
||||||
u1.user_name as create_by_name, u2.user_name as update_by_name
|
u1.user_name as create_by_name, u2.user_name as update_by_name
|
||||||
from inspection_task t
|
from inspection_task t
|
||||||
|
|||||||
@ -40,14 +40,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
select id, create_by, create_time, update_by, update_time, remark, waypoint_name, enabled, sort_order, detect_item_id, map_id from inspection_waypoint
|
select id, create_by, create_time, update_by, update_time, remark, waypoint_name, enabled, sort_order, detect_item_id, map_id from inspection_waypoint
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="selectInspectionWaypointList" parameterType="InspectionWaypoint" resultMap="InspectionWaypointResult">
|
|
||||||
<include refid="selectInspectionWaypointVo"/>
|
|
||||||
<where>
|
|
||||||
<if test="waypointName != null and waypointName != ''"> and waypoint_name like concat('%', #{waypointName}, '%')</if>
|
|
||||||
<if test="enabled != null and enabled != ''"> and enabled = #{enabled}</if>
|
|
||||||
</where>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectInspectionWaypointById" parameterType="String" resultMap="InspectionWaypointResult">
|
<select id="selectInspectionWaypointById" parameterType="String" resultMap="InspectionWaypointResult">
|
||||||
<include refid="selectInspectionWaypointVo"/>
|
<include refid="selectInspectionWaypointVo"/>
|
||||||
where id = #{id}
|
where id = #{id}
|
||||||
|
|||||||
@ -32,6 +32,11 @@ public class FlowExecutionEvent {
|
|||||||
*/
|
*/
|
||||||
private String taskId;
|
private String taskId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点数量
|
||||||
|
*/
|
||||||
|
private Integer nodeCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当前检测项 ID
|
* 当前检测项 ID
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -40,6 +40,7 @@ public class FlowAfterInterceptor extends AbstractFlowMsgPreInterceptor{
|
|||||||
.taskId(message.getTaskId())
|
.taskId(message.getTaskId())
|
||||||
.itemId(message.getItemId())
|
.itemId(message.getItemId())
|
||||||
.nodeId(message.getNodeId())
|
.nodeId(message.getNodeId())
|
||||||
|
.nodeCount(message.getGraph().allNodeIds().size())
|
||||||
.nodeType(message.getNodeType())
|
.nodeType(message.getNodeType())
|
||||||
.errorMessage(errorMessage)
|
.errorMessage(errorMessage)
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@ -27,6 +27,7 @@ public class FlowBeforeInterceptor extends AbstractFlowMsgPreInterceptor{
|
|||||||
.taskId(message.getTaskId())
|
.taskId(message.getTaskId())
|
||||||
.itemId(message.getItemId())
|
.itemId(message.getItemId())
|
||||||
.nodeId(message.getNodeId())
|
.nodeId(message.getNodeId())
|
||||||
|
.nodeCount(message.getGraph().allNodeIds().size())
|
||||||
.nodeName(message.getNodeName())
|
.nodeName(message.getNodeName())
|
||||||
.nodeType(message.getNodeType())
|
.nodeType(message.getNodeType())
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@ -57,6 +57,7 @@ CREATE TABLE `inspection_task` (
|
|||||||
`task_type` char(1) DEFAULT '1' COMMENT '任务类型(1巡检任务 2讲解任务)',
|
`task_type` char(1) DEFAULT '1' COMMENT '任务类型(1巡检任务 2讲解任务)',
|
||||||
`task_config_id` varchar(64) DEFAULT NULL COMMENT '任务配置ID',
|
`task_config_id` varchar(64) DEFAULT NULL COMMENT '任务配置ID',
|
||||||
`map_id` varchar(64) DEFAULT NULL COMMENT '地图ID',
|
`map_id` varchar(64) DEFAULT NULL COMMENT '地图ID',
|
||||||
|
`waypoint_count` int(11) DEFAULT '0' COMMENT '点位总数',
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `uk_task_code` (`task_code`)
|
UNIQUE KEY `uk_task_code` (`task_code`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='巡检任务表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='巡检任务表';
|
||||||
@ -104,6 +105,7 @@ CREATE TABLE `inspection_task_instance` (
|
|||||||
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
|
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
|
||||||
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
|
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
|
||||||
`task_ins_id` varchar(64) DEFAULT NULL COMMENT '检测任务id',
|
`task_ins_id` varchar(64) DEFAULT NULL COMMENT '检测任务id',
|
||||||
|
`waypoint_count` int(11) DEFAULT '0' COMMENT '点位总数',
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `idx_task_id` (`task_id`),
|
KEY `idx_task_id` (`task_id`),
|
||||||
KEY `idx_robot_id` (`robot_id`),
|
KEY `idx_robot_id` (`robot_id`),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user