完善 MCS 桌面代理与开炼机动作同步,修复原料相关菜单权限,桌面端新增上辅机 MES 菜单。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package org.jeecg.modules.device.sync.agent;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MES → 桌面设备库命令。
|
||||
*/
|
||||
@Data
|
||||
public class DeviceAgentCommand implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String CMD_QUERY = "EQ_QUERY";
|
||||
public static final String CMD_WRITE = "EQ_WRITE";
|
||||
public static final String CMD_SYNC_PULL = "EQ_SYNC_PULL";
|
||||
public static final String CMD_SYNC_PUSH = "EQ_SYNC_PUSH";
|
||||
public static final String CMD_TEST = "EQ_TEST";
|
||||
|
||||
private String requestId;
|
||||
private String cmd;
|
||||
private String deviceId;
|
||||
/** 白名单表名(可选,与 sql 二选一或同时校验) */
|
||||
private String table;
|
||||
/** 已登记 sqlKey 或受限 SQL(桌面端白名单校验) */
|
||||
private String sql;
|
||||
private String sqlKey;
|
||||
private List<Object> params;
|
||||
private Map<String, Object> payload;
|
||||
private Integer timeoutMs;
|
||||
private Long sentAt;
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package org.jeecg.modules.device.sync.agent;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 设备代理命令总线:下发 EQ_* 并等待回执。
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceAgentCommandService {
|
||||
|
||||
public static final int DEFAULT_TIMEOUT_MS = 30_000;
|
||||
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
private final DeviceAgentSessionRegistry sessionRegistry;
|
||||
|
||||
private final ConcurrentHashMap<String, CompletableFuture<DeviceAgentResult>> pending = new ConcurrentHashMap<>();
|
||||
|
||||
public DeviceAgentResult sendAndWait(DeviceAgentCommand command) {
|
||||
if (command == null) {
|
||||
throw new IllegalArgumentException("command 不能为空");
|
||||
}
|
||||
if (StringUtils.isBlank(command.getRequestId())) {
|
||||
command.setRequestId(UUID.randomUUID().toString().replace("-", ""));
|
||||
}
|
||||
if (StringUtils.isBlank(command.getDeviceId())) {
|
||||
DeviceAgentSession picked = sessionRegistry.pickOnlineAgent(null);
|
||||
if (picked == null) {
|
||||
throw new IllegalStateException("无在线桌面设备代理,无法访问设备库");
|
||||
}
|
||||
command.setDeviceId(picked.getDeviceId());
|
||||
}
|
||||
if (!sessionRegistry.isOnline(command.getDeviceId())) {
|
||||
throw new IllegalStateException("桌面设备代理不在线: " + command.getDeviceId());
|
||||
}
|
||||
int timeout = command.getTimeoutMs() != null && command.getTimeoutMs() > 0
|
||||
? command.getTimeoutMs() : DEFAULT_TIMEOUT_MS;
|
||||
command.setSentAt(System.currentTimeMillis());
|
||||
|
||||
CompletableFuture<DeviceAgentResult> future = new CompletableFuture<>();
|
||||
CompletableFuture<DeviceAgentResult> previous = pending.putIfAbsent(command.getRequestId(), future);
|
||||
if (previous != null) {
|
||||
throw new IllegalStateException("重复的 requestId: " + command.getRequestId());
|
||||
}
|
||||
|
||||
try {
|
||||
String destination = "/topic/device/" + command.getDeviceId() + "/cmd";
|
||||
messagingTemplate.convertAndSend(destination, JSON.toJSONString(command));
|
||||
log.info("已下发设备库命令 cmd={} requestId={} deviceId={}",
|
||||
command.getCmd(), command.getRequestId(), command.getDeviceId());
|
||||
return future.get(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
pending.remove(command.getRequestId(), future);
|
||||
throw new IllegalStateException("等待桌面设备库回执超时(" + timeout + "ms), requestId=" + command.getRequestId());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
pending.remove(command.getRequestId(), future);
|
||||
throw new IllegalStateException("等待桌面设备库回执被中断", e);
|
||||
} catch (Exception e) {
|
||||
pending.remove(command.getRequestId(), future);
|
||||
if (e instanceof IllegalStateException) {
|
||||
throw (IllegalStateException) e;
|
||||
}
|
||||
throw new IllegalStateException("下发设备库命令失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void completeResult(DeviceAgentResult result) {
|
||||
if (result == null || StringUtils.isBlank(result.getRequestId())) {
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isNotBlank(result.getDeviceId())) {
|
||||
sessionRegistry.registerOrHeartbeat(
|
||||
result.getDeviceId(),
|
||||
null,
|
||||
result.getDbConnected(),
|
||||
result.getDbMessage());
|
||||
}
|
||||
CompletableFuture<DeviceAgentResult> future = pending.remove(result.getRequestId());
|
||||
if (future != null) {
|
||||
future.complete(result);
|
||||
} else {
|
||||
log.debug("收到无等待方的设备库回执 requestId={}", result.getRequestId());
|
||||
}
|
||||
}
|
||||
|
||||
public DeviceAgentResult query(String deviceId, String sql, List<Object> params, Integer timeoutMs) {
|
||||
DeviceAgentCommand cmd = new DeviceAgentCommand();
|
||||
cmd.setCmd(DeviceAgentCommand.CMD_QUERY);
|
||||
cmd.setDeviceId(deviceId);
|
||||
cmd.setSql(sql);
|
||||
cmd.setParams(params);
|
||||
cmd.setTimeoutMs(timeoutMs);
|
||||
DeviceAgentResult result = sendAndWait(cmd);
|
||||
ensureSuccess(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public DeviceAgentResult write(String deviceId, String sql, List<Object> params, Integer timeoutMs) {
|
||||
DeviceAgentCommand cmd = new DeviceAgentCommand();
|
||||
cmd.setCmd(DeviceAgentCommand.CMD_WRITE);
|
||||
cmd.setDeviceId(deviceId);
|
||||
cmd.setSql(sql);
|
||||
cmd.setParams(params);
|
||||
cmd.setTimeoutMs(timeoutMs);
|
||||
DeviceAgentResult result = sendAndWait(cmd);
|
||||
ensureSuccess(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public DeviceAgentResult test(String deviceId, Integer timeoutMs) {
|
||||
DeviceAgentCommand cmd = new DeviceAgentCommand();
|
||||
cmd.setCmd(DeviceAgentCommand.CMD_TEST);
|
||||
cmd.setDeviceId(deviceId);
|
||||
cmd.setTimeoutMs(timeoutMs != null ? timeoutMs : 10_000);
|
||||
return sendAndWait(cmd);
|
||||
}
|
||||
|
||||
public DeviceAgentResult syncPull(String deviceId, String table, String sql, List<Object> params, Integer timeoutMs) {
|
||||
DeviceAgentCommand cmd = new DeviceAgentCommand();
|
||||
cmd.setCmd(DeviceAgentCommand.CMD_SYNC_PULL);
|
||||
cmd.setDeviceId(deviceId);
|
||||
cmd.setTable(table);
|
||||
cmd.setSql(sql);
|
||||
cmd.setParams(params);
|
||||
cmd.setTimeoutMs(timeoutMs != null ? timeoutMs : 60_000);
|
||||
DeviceAgentResult result = sendAndWait(cmd);
|
||||
ensureSuccess(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public DeviceAgentResult syncPush(String deviceId, String table, Map<String, Object> payload, Integer timeoutMs) {
|
||||
DeviceAgentCommand cmd = new DeviceAgentCommand();
|
||||
cmd.setCmd(DeviceAgentCommand.CMD_SYNC_PUSH);
|
||||
cmd.setDeviceId(deviceId);
|
||||
cmd.setTable(table);
|
||||
cmd.setPayload(payload);
|
||||
cmd.setTimeoutMs(timeoutMs != null ? timeoutMs : 60_000);
|
||||
DeviceAgentResult result = sendAndWait(cmd);
|
||||
ensureSuccess(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ensureSuccess(DeviceAgentResult result) {
|
||||
if (result == null) {
|
||||
throw new IllegalStateException("桌面设备库无回执");
|
||||
}
|
||||
if (!result.isSuccess()) {
|
||||
throw new IllegalStateException(StringUtils.defaultIfBlank(result.getError(), "桌面设备库执行失败"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.jeecg.modules.device.sync.agent;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 桌面 → MES 设备库命令回执。
|
||||
*/
|
||||
@Data
|
||||
public class DeviceAgentResult implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String requestId;
|
||||
private String deviceId;
|
||||
private String cmd;
|
||||
private boolean success;
|
||||
private String error;
|
||||
private Integer affected;
|
||||
private List<Map<String, Object>> rows;
|
||||
private Boolean dbConnected;
|
||||
private String dbMessage;
|
||||
private Long respondedAt;
|
||||
/** 分块回传时使用 */
|
||||
private Integer chunkIndex;
|
||||
private Integer chunkTotal;
|
||||
private Boolean lastChunk;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.jeecg.modules.device.sync.agent;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 桌面设备代理在线会话(内存)。
|
||||
*/
|
||||
@Data
|
||||
public class DeviceAgentSession implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String deviceId;
|
||||
private String hostName;
|
||||
private boolean online;
|
||||
/** 设备库(SQL Server)是否连通 */
|
||||
private boolean dbConnected;
|
||||
private String dbMessage;
|
||||
private long lastSeenAt;
|
||||
private long registeredAt;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.jeecg.modules.device.sync.agent;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 桌面设备代理会话注册表。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeviceAgentSessionRegistry {
|
||||
|
||||
private static final long ONLINE_TTL_MS = 60_000L;
|
||||
|
||||
private final ConcurrentHashMap<String, DeviceAgentSession> sessions = new ConcurrentHashMap<>();
|
||||
|
||||
public void registerOrHeartbeat(String deviceId, String hostName, Boolean dbConnected, String dbMessage) {
|
||||
if (StringUtils.isBlank(deviceId)) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
sessions.compute(deviceId, (id, old) -> {
|
||||
DeviceAgentSession s = old == null ? new DeviceAgentSession() : old;
|
||||
if (old == null) {
|
||||
s.setRegisteredAt(now);
|
||||
}
|
||||
s.setDeviceId(deviceId);
|
||||
if (StringUtils.isNotBlank(hostName)) {
|
||||
s.setHostName(hostName);
|
||||
}
|
||||
s.setOnline(true);
|
||||
if (dbConnected != null) {
|
||||
s.setDbConnected(dbConnected);
|
||||
}
|
||||
if (dbMessage != null) {
|
||||
s.setDbMessage(dbMessage);
|
||||
}
|
||||
s.setLastSeenAt(now);
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
public void markOffline(String deviceId) {
|
||||
if (StringUtils.isBlank(deviceId)) {
|
||||
return;
|
||||
}
|
||||
DeviceAgentSession s = sessions.get(deviceId);
|
||||
if (s != null) {
|
||||
s.setOnline(false);
|
||||
s.setLastSeenAt(System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
public DeviceAgentSession get(String deviceId) {
|
||||
if (StringUtils.isBlank(deviceId)) {
|
||||
return null;
|
||||
}
|
||||
DeviceAgentSession s = sessions.get(deviceId);
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
refreshOnlineFlag(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
public boolean isOnline(String deviceId) {
|
||||
DeviceAgentSession s = get(deviceId);
|
||||
return s != null && s.isOnline();
|
||||
}
|
||||
|
||||
/**
|
||||
* 选取一个在线代理:优先指定 deviceId,否则取最近心跳的在线设备。
|
||||
*/
|
||||
public DeviceAgentSession pickOnlineAgent(String preferredDeviceId) {
|
||||
if (StringUtils.isNotBlank(preferredDeviceId)) {
|
||||
DeviceAgentSession preferred = get(preferredDeviceId);
|
||||
if (preferred != null && preferred.isOnline()) {
|
||||
return preferred;
|
||||
}
|
||||
}
|
||||
return listOnline().stream()
|
||||
.max(Comparator.comparingLong(DeviceAgentSession::getLastSeenAt))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public List<DeviceAgentSession> listOnline() {
|
||||
List<DeviceAgentSession> list = new ArrayList<>();
|
||||
for (Map.Entry<String, DeviceAgentSession> e : sessions.entrySet()) {
|
||||
DeviceAgentSession s = e.getValue();
|
||||
refreshOnlineFlag(s);
|
||||
if (s.isOnline()) {
|
||||
list.add(s);
|
||||
}
|
||||
}
|
||||
list.sort(Comparator.comparingLong(DeviceAgentSession::getLastSeenAt).reversed());
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<DeviceAgentSession> listAll() {
|
||||
List<DeviceAgentSession> list = new ArrayList<>(sessions.values());
|
||||
list.forEach(this::refreshOnlineFlag);
|
||||
list.sort(Comparator.comparingLong(DeviceAgentSession::getLastSeenAt).reversed());
|
||||
return list;
|
||||
}
|
||||
|
||||
private void refreshOnlineFlag(DeviceAgentSession s) {
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
if (System.currentTimeMillis() - s.getLastSeenAt() > ONLINE_TTL_MS) {
|
||||
s.setOnline(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,29 @@
|
||||
package org.jeecg.modules.device.sync.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.modules.device.sync.agent.DeviceAgentCommand;
|
||||
import org.jeecg.modules.device.sync.agent.DeviceAgentCommandService;
|
||||
import org.jeecg.modules.device.sync.agent.DeviceAgentResult;
|
||||
import org.jeecg.modules.device.sync.agent.DeviceAgentSession;
|
||||
import org.jeecg.modules.device.sync.agent.DeviceAgentSessionRegistry;
|
||||
import org.jeecg.modules.device.sync.entity.DeviceStatus;
|
||||
import org.jeecg.modules.device.sync.mapper.DeviceStatusMapper;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -29,6 +38,8 @@ public class DeviceWebSocketController {
|
||||
|
||||
private final DeviceStatusMapper deviceStatusMapper;
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
private final DeviceAgentSessionRegistry agentSessionRegistry;
|
||||
private final DeviceAgentCommandService agentCommandService;
|
||||
|
||||
@MessageMapping("/device/status")
|
||||
public void receiveDeviceStatus(DeviceStatus payload) {
|
||||
@@ -70,6 +81,8 @@ public class DeviceWebSocketController {
|
||||
if (deviceId.isBlank()) {
|
||||
return;
|
||||
}
|
||||
// 心跳同时刷新设备代理在线状态
|
||||
agentSessionRegistry.registerOrHeartbeat(deviceId, null, null, null);
|
||||
Map<String, Object> pong = new HashMap<>();
|
||||
pong.put("cmd", "PONG_DEVICE");
|
||||
pong.put("deviceId", deviceId);
|
||||
@@ -91,6 +104,8 @@ public class DeviceWebSocketController {
|
||||
commandPayload.put("sentAt", System.currentTimeMillis());
|
||||
|
||||
messagingTemplate.convertAndSendToUser(deviceId, "/queue/command", commandPayload);
|
||||
// 匿名桌面同样订阅 /topic/device/{id}/cmd
|
||||
messagingTemplate.convertAndSend("/topic/device/" + deviceId + "/cmd", commandJson);
|
||||
log.info("下发设备指令成功, deviceId={}", deviceId);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
@@ -98,4 +113,87 @@ public class DeviceWebSocketController {
|
||||
result.put("queued", true);
|
||||
return Result.OK(result);
|
||||
}
|
||||
|
||||
//update-begin---author:cursor ---date:2026-07-20 for:【设备库桥接】代理状态/回执/在线列表-----------
|
||||
/**
|
||||
* 桌面代理上报在线与设备库连通状态。
|
||||
* destination: /app/device/agent/status
|
||||
*/
|
||||
@MessageMapping("/device/agent/status")
|
||||
public void receiveAgentStatus(@org.springframework.messaging.handler.annotation.Payload(required = false) Map<String, Object> payload) {
|
||||
if (payload == null) {
|
||||
return;
|
||||
}
|
||||
String deviceId = String.valueOf(payload.getOrDefault("deviceId", "")).trim();
|
||||
if (deviceId.isBlank()) {
|
||||
return;
|
||||
}
|
||||
String hostName = payload.get("hostName") == null ? null : String.valueOf(payload.get("hostName"));
|
||||
Boolean dbConnected = null;
|
||||
if (payload.get("dbConnected") instanceof Boolean) {
|
||||
dbConnected = (Boolean) payload.get("dbConnected");
|
||||
} else if (payload.get("dbConnected") != null) {
|
||||
dbConnected = Boolean.parseBoolean(String.valueOf(payload.get("dbConnected")));
|
||||
}
|
||||
String dbMessage = payload.get("dbMessage") == null ? null : String.valueOf(payload.get("dbMessage"));
|
||||
agentSessionRegistry.registerOrHeartbeat(deviceId, hostName, dbConnected, dbMessage);
|
||||
messagingTemplate.convertAndSend("/topic/device/" + deviceId + "/status", payload);
|
||||
log.debug("设备代理状态已更新 deviceId={} dbConnected={}", deviceId, dbConnected);
|
||||
}
|
||||
|
||||
/**
|
||||
* 桌面代理回传设备库命令结果。
|
||||
* destination: /app/device/agent/result
|
||||
*/
|
||||
@MessageMapping("/device/agent/result")
|
||||
public void receiveAgentResult(@org.springframework.messaging.handler.annotation.Payload(required = false) Map<String, Object> payload) {
|
||||
if (payload == null) {
|
||||
return;
|
||||
}
|
||||
DeviceAgentResult result = JSON.parseObject(JSON.toJSONString(payload), DeviceAgentResult.class);
|
||||
agentCommandService.completeResult(result);
|
||||
}
|
||||
|
||||
@GetMapping("/agent/online")
|
||||
public Result<List<DeviceAgentSession>> listOnlineAgents() {
|
||||
return Result.OK(agentSessionRegistry.listOnline());
|
||||
}
|
||||
|
||||
@GetMapping("/agent/list")
|
||||
public Result<List<DeviceAgentSession>> listAgents() {
|
||||
return Result.OK(agentSessionRegistry.listAll());
|
||||
}
|
||||
|
||||
@PostMapping("/agent/test")
|
||||
public Result<DeviceAgentResult> testAgentDb(@RequestBody(required = false) Map<String, Object> body) {
|
||||
String deviceId = body == null ? null : String.valueOf(body.getOrDefault("deviceId", "")).trim();
|
||||
if (deviceId == null || deviceId.isBlank()) {
|
||||
DeviceAgentSession picked = agentSessionRegistry.pickOnlineAgent(null);
|
||||
if (picked == null) {
|
||||
return Result.error("无在线桌面代理");
|
||||
}
|
||||
deviceId = picked.getDeviceId();
|
||||
}
|
||||
try {
|
||||
return Result.OK(agentCommandService.test(deviceId, 10_000));
|
||||
} catch (Exception e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/agent/command")
|
||||
public Result<DeviceAgentResult> sendAgentCommand(@RequestBody DeviceAgentCommand command) {
|
||||
try {
|
||||
return Result.OK(agentCommandService.sendAndWait(command));
|
||||
} catch (Exception e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/agent/status")
|
||||
public Result<DeviceAgentSession> agentStatus(@RequestParam(name = "deviceId", required = false) String deviceId) {
|
||||
DeviceAgentSession session = agentSessionRegistry.pickOnlineAgent(deviceId);
|
||||
return Result.OK(session);
|
||||
}
|
||||
//update-end---author:cursor ---date:2026-07-20 for:【设备库桥接】代理状态/回执/在线列表-----------
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user