feat(controlled, controller): 新增配对码生成与兑换功能
被控端:新增生成一次性配对码并弹窗展示,支持倒计时与复制;控制端:新增输入配对码兑换绑定,刷新设备列表,并在设备列表中显示在线状态和点击连接。同时添加统一 401 拦截器,令牌失效时触发重新激活。
This commit is contained in:
@@ -447,6 +447,25 @@ public class AdminController {
|
||||
"total", deviceIdentityService.getAllowlist().size());
|
||||
}
|
||||
|
||||
// ==================== 用户绑定关系 ====================
|
||||
|
||||
/** 列出某用户账号绑定的全部终端设备(含已撤销),并附在线状态。 */
|
||||
@GetMapping("/users/{userId}/bindings")
|
||||
public List<Map<String, Object>> userBindings(@PathVariable String userId) {
|
||||
return bindingService.listByUser(userId).stream().map(b -> {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("bindingId", b.getBindingId());
|
||||
item.put("deviceUid", b.getDeviceUid());
|
||||
item.put("userId", b.getUserId());
|
||||
item.put("role", b.getRole().name());
|
||||
item.put("alias", b.getAlias());
|
||||
item.put("status", b.getStatus().name());
|
||||
item.put("online", sessionManager.isDeviceOnline(b.getDeviceUid()));
|
||||
item.put("boundAt", b.getBoundAt() == null ? null : b.getBoundAt().toEpochMilli());
|
||||
return item;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// ==================== 绑定 / 黑名单管理 ====================
|
||||
|
||||
/** 列出某设备的全部绑定关系(含已撤销)。 */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ttstd.signaling.controller;
|
||||
|
||||
import com.ttstd.signaling.config.BearerAuthFilter;
|
||||
import com.ttstd.signaling.manager.SessionManager;
|
||||
import com.ttstd.signaling.model.AccountStatus;
|
||||
import com.ttstd.signaling.model.AuthPrincipal;
|
||||
import com.ttstd.signaling.model.DeviceAccount;
|
||||
@@ -23,6 +24,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -50,6 +52,7 @@ public class ClientController {
|
||||
private final PairingService pairingService;
|
||||
private final TurnCredentialService turnCredentialService;
|
||||
private final AbuseReportService abuseReportService;
|
||||
private final SessionManager sessionManager;
|
||||
|
||||
public ClientController(JwtService jwtService,
|
||||
SecurityProperties properties,
|
||||
@@ -57,7 +60,8 @@ public class ClientController {
|
||||
BindingService bindingService,
|
||||
PairingService pairingService,
|
||||
TurnCredentialService turnCredentialService,
|
||||
AbuseReportService abuseReportService) {
|
||||
AbuseReportService abuseReportService,
|
||||
SessionManager sessionManager) {
|
||||
this.jwtService = jwtService;
|
||||
this.properties = properties;
|
||||
this.deviceIdentityService = deviceIdentityService;
|
||||
@@ -65,6 +69,7 @@ public class ClientController {
|
||||
this.pairingService = pairingService;
|
||||
this.turnCredentialService = turnCredentialService;
|
||||
this.abuseReportService = abuseReportService;
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
// ==================== 令牌校验 ====================
|
||||
@@ -336,6 +341,32 @@ public class ClientController {
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主控端查看当前「已绑定且在线」的设备列表,便于快速发起连接。
|
||||
* 仅返回当前在信令网络中有活跃 WebSocket 会话的被控端。
|
||||
*/
|
||||
@GetMapping("/devices/online")
|
||||
public Map<String, Object> onlineDevices(HttpServletRequest request) {
|
||||
AuthPrincipal principal = requirePrincipal(request);
|
||||
if (!principal.isUser()) {
|
||||
throw AuthException.forbidden("仅用户令牌可调用");
|
||||
}
|
||||
List<Map<String, Object>> online = bindingService.listByUser(principal.principalId()).stream()
|
||||
.filter(b -> b.getStatus() == DeviceBinding.BindingStatus.ACTIVE)
|
||||
.filter(b -> sessionManager.isDeviceOnline(b.getDeviceUid()))
|
||||
.map(b -> {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("deviceUid", b.getDeviceUid());
|
||||
m.put("alias", b.getAlias());
|
||||
m.put("online", true);
|
||||
return m;
|
||||
})
|
||||
.toList();
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
r.put("devices", online);
|
||||
return r;
|
||||
}
|
||||
|
||||
private Map<String, Object> bindingView(DeviceBinding b) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("bindingId", b.getBindingId());
|
||||
@@ -344,6 +375,7 @@ public class ClientController {
|
||||
m.put("role", b.getRole().name());
|
||||
m.put("alias", b.getAlias());
|
||||
m.put("status", b.getStatus().name());
|
||||
m.put("online", sessionManager.isDeviceOnline(b.getDeviceUid()));
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,6 +140,8 @@ public class SignalWebSocketHandler extends TextWebSocketHandler {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("type", "REGISTER_SUCCESS");
|
||||
response.put("deviceId", principal.deviceId());
|
||||
// 与既有信令消息协议一致:下发的设备ID也写入 fromDeviceId,便于客户端统一解析。
|
||||
response.put("fromDeviceId", principal.deviceId());
|
||||
response.put("deviceType", principal.deviceType().name());
|
||||
response.put("displayName", principal.displayName());
|
||||
sendToSession(session, response);
|
||||
@@ -286,6 +288,7 @@ public class SignalWebSocketHandler extends TextWebSocketHandler {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("type", "REGISTER_SUCCESS");
|
||||
response.put("deviceId", principal.deviceId());
|
||||
response.put("fromDeviceId", principal.deviceId());
|
||||
response.put("deviceType", principal.deviceType().name());
|
||||
response.put("displayName", principal.displayName());
|
||||
sendToSession(session, response);
|
||||
|
||||
Reference in New Issue
Block a user