新增MES模块,包含供应商、客户、车辆和地磅数据记录管理功能,支持免密接口和数据同步。更新相关控制器、实体、服务和数据库配置,优化权限管理和数据字典支持,确保系统的灵活性和可扩展性。
This commit is contained in:
@@ -1,21 +1,40 @@
|
||||
package org.jeecg.modules.device.sync.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||
|
||||
/**
|
||||
* 设备同步 WebSocket STOMP 配置。
|
||||
*
|
||||
* 端点:/ws/device(纯 WebSocket,不启用 SockJS)。
|
||||
* 客户端连接地址:ws(s)://host/context/ws/device
|
||||
* 心跳:10 s 双向,由 SimpleBroker 通过 taskScheduler 定期发 \n 帧保活。
|
||||
*/
|
||||
@Configuration("deviceSyncWebSocketConfig")
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Bean(name = "stompHeartbeatScheduler")
|
||||
public TaskScheduler stompHeartbeatScheduler() {
|
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||
scheduler.setPoolSize(1);
|
||||
scheduler.setThreadNamePrefix("stomp-hb-");
|
||||
scheduler.initialize();
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
registry.enableSimpleBroker("/topic", "/queue");
|
||||
registry.enableSimpleBroker("/topic", "/queue")
|
||||
// 10 s 双向心跳:服务端每 10 s 向客户端发 \n,客户端亦声明 10 s 发一次
|
||||
.setHeartbeatValue(new long[]{10000, 10000})
|
||||
.setTaskScheduler(stompHeartbeatScheduler());
|
||||
registry.setApplicationDestinationPrefixes("/app");
|
||||
registry.setUserDestinationPrefix("/user");
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 设备 WebSocket 控制器。
|
||||
@@ -57,6 +58,26 @@ public class DeviceWebSocketController {
|
||||
log.debug("设备状态已广播, deviceId={}", payload.getDeviceId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用层 PING:客户端发 {"cmd":"PING_DEVICE","deviceId":"xxx"} 到 /app/device/ping,
|
||||
* 服务端广播 PONG 到 /topic/device/{deviceId}/pong,客户端收到后重置假在线计时器。
|
||||
*/
|
||||
@MessageMapping("/device/ping")
|
||||
public void handlePing(@org.springframework.messaging.handler.annotation.Payload(required = false) Map<String, Object> payload) {
|
||||
String deviceId = Optional.ofNullable(payload)
|
||||
.map(p -> String.valueOf(p.getOrDefault("deviceId", "")))
|
||||
.orElse("");
|
||||
if (deviceId.isBlank()) {
|
||||
return;
|
||||
}
|
||||
Map<String, Object> pong = new HashMap<>();
|
||||
pong.put("cmd", "PONG_DEVICE");
|
||||
pong.put("deviceId", deviceId);
|
||||
pong.put("respondedAt", System.currentTimeMillis());
|
||||
messagingTemplate.convertAndSend("/topic/device/" + deviceId + "/pong", pong);
|
||||
log.debug("PONG 已回复, deviceId={}", deviceId);
|
||||
}
|
||||
|
||||
@PostMapping("/command")
|
||||
public Result<Map<String, Object>> sendCommand(@RequestBody Map<String, Object> request) {
|
||||
String deviceId = request == null ? null : String.valueOf(request.getOrDefault("deviceId", ""));
|
||||
|
||||
@@ -12,12 +12,15 @@ import org.jeecg.modules.device.sync.entity.SyncIdempotentLog;
|
||||
import org.jeecg.modules.device.sync.mapper.DeviceRegistryMapper;
|
||||
import org.jeecg.modules.device.sync.mapper.DeviceStatusMapper;
|
||||
import org.jeecg.modules.device.sync.mapper.SyncIdempotentLogMapper;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
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.RestController;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -35,6 +38,8 @@ public class SyncController {
|
||||
private final SyncIdempotentLogMapper syncIdempotentLogMapper;
|
||||
private final DeviceStatusMapper deviceStatusMapper;
|
||||
private final DeviceRegistryMapper deviceRegistryMapper;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
|
||||
@PostMapping("/batch")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -82,6 +87,9 @@ public class SyncController {
|
||||
case "DEVICE_REGISTRY":
|
||||
saveDeviceRegistry(message);
|
||||
break;
|
||||
case "SYS_USER":
|
||||
handleSysUser(message);
|
||||
break;
|
||||
default:
|
||||
log.debug("未识别aggregateType,按透传记录处理,messageId={}, aggregateType={}", message.getMessageId(), aggregateType);
|
||||
break;
|
||||
@@ -111,6 +119,140 @@ public class SyncController {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void handleSysUser(SyncMessageDto message) {
|
||||
String eventType = message.getEventType() == null ? "" : message.getEventType().trim().toUpperCase();
|
||||
Map<String, Object> payload;
|
||||
try {
|
||||
payload = JSON.parseObject(message.getPayload(), Map.class);
|
||||
} catch (Exception e) {
|
||||
log.warn("SYS_USER payload 解析失败, messageId={}", message.getMessageId(), e);
|
||||
return;
|
||||
}
|
||||
if (payload == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (eventType) {
|
||||
case "CREATE": {
|
||||
String userId = String.valueOf(payload.getOrDefault("userId", "")).trim();
|
||||
String account = String.valueOf(payload.getOrDefault("account", "")).trim();
|
||||
if (userId.isBlank() || account.isBlank()) {
|
||||
return;
|
||||
}
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO sys_user (id, username, realname, sex, birthday, phone, email, status, create_by, create_time, update_by, update_time, del_flag) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0) " +
|
||||
"ON DUPLICATE KEY UPDATE " +
|
||||
"username=VALUES(username), realname=VALUES(realname), sex=VALUES(sex), birthday=VALUES(birthday), phone=VALUES(phone), email=VALUES(email), status=VALUES(status), update_by=VALUES(update_by), update_time=VALUES(update_time), del_flag=0",
|
||||
userId,
|
||||
account,
|
||||
payload.get("realName"),
|
||||
payload.get("sex"),
|
||||
payload.get("birthday"),
|
||||
payload.get("phone"),
|
||||
payload.get("email"),
|
||||
payload.get("status"),
|
||||
payload.get("updateBy"),
|
||||
new Date(),
|
||||
payload.get("updateBy"),
|
||||
new Date()
|
||||
);
|
||||
publishUserChanged("create", userId, null);
|
||||
log.info("SYS_USER CREATE 已同步, userId={}, account={}", userId, account);
|
||||
break;
|
||||
}
|
||||
case "UPDATE": {
|
||||
String userId = String.valueOf(payload.getOrDefault("userId", "")).trim();
|
||||
if (userId.isBlank()) {
|
||||
return;
|
||||
}
|
||||
String account = String.valueOf(payload.getOrDefault("account", "")).trim();
|
||||
jdbcTemplate.update(
|
||||
"UPDATE sys_user SET username=COALESCE(NULLIF(?, ''), username), realname=?, sex=?, birthday=?, phone=?, email=?, status=?, update_by=?, update_time=? WHERE id=?",
|
||||
account,
|
||||
payload.get("realName"),
|
||||
payload.get("sex"),
|
||||
payload.get("birthday"),
|
||||
payload.get("phone"),
|
||||
payload.get("email"),
|
||||
payload.get("status"),
|
||||
payload.get("updateBy"),
|
||||
new Date(),
|
||||
userId
|
||||
);
|
||||
publishUserChanged("edit", userId, null);
|
||||
log.info("SYS_USER UPDATE 已同步, userId={}", userId);
|
||||
break;
|
||||
}
|
||||
case "TOGGLE_STATUS": {
|
||||
String userId = String.valueOf(payload.getOrDefault("userId", "")).trim();
|
||||
if (userId.isBlank()) {
|
||||
return;
|
||||
}
|
||||
Object status = payload.get("status");
|
||||
jdbcTemplate.update(
|
||||
"UPDATE sys_user SET status=?, update_by=?, update_time=? WHERE id=?",
|
||||
status,
|
||||
payload.get("updateBy"),
|
||||
new Date(),
|
||||
userId
|
||||
);
|
||||
publishUserChanged("status", userId, null);
|
||||
log.info("SYS_USER TOGGLE_STATUS 已同步, userId={}, status={}", userId, status);
|
||||
break;
|
||||
}
|
||||
case "DELETE": {
|
||||
String userId = String.valueOf(payload.getOrDefault("userId", "")).trim();
|
||||
if (userId.isBlank()) {
|
||||
return;
|
||||
}
|
||||
jdbcTemplate.update(
|
||||
"UPDATE sys_user SET del_flag=1 WHERE id=?",
|
||||
userId
|
||||
);
|
||||
publishUserChanged("delete", userId, null);
|
||||
log.info("SYS_USER DELETE 已同步, userId={}", userId);
|
||||
break;
|
||||
}
|
||||
case "BATCH_DELETE": {
|
||||
List<String> userIds = (List<String>) payload.get("userIds");
|
||||
if (userIds == null || userIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
String placeholders = String.join(",", Collections.nCopies(userIds.size(), "?"));
|
||||
jdbcTemplate.update(
|
||||
"UPDATE sys_user SET del_flag=1 WHERE id IN (" + placeholders + ")",
|
||||
userIds.toArray()
|
||||
);
|
||||
publishUserChanged("batchDelete", null, userIds);
|
||||
log.info("SYS_USER BATCH_DELETE 已同步, count={}", userIds.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
log.debug("SYS_USER 未知 eventType={}", eventType);
|
||||
}
|
||||
}
|
||||
|
||||
private void publishUserChanged(String action, String userId, List<String> userIds) {
|
||||
try {
|
||||
Map<String, Object> event = new HashMap<>();
|
||||
// 批量事件使用复数命令,单条变更保持兼容旧命令
|
||||
event.put("cmd", (userIds != null && !userIds.isEmpty()) ? "SCADA_USERS_CHANGED" : "SCADA_USER_CHANGED");
|
||||
event.put("action", action);
|
||||
event.put("timestamp", System.currentTimeMillis());
|
||||
if (userId != null && !userId.isBlank()) {
|
||||
event.put("userId", userId);
|
||||
}
|
||||
if (userIds != null && !userIds.isEmpty()) {
|
||||
event.put("userIds", userIds);
|
||||
}
|
||||
messagingTemplate.convertAndSend("/topic/sync/jeecg-users", JSON.toJSONString(event));
|
||||
} catch (Exception e) {
|
||||
log.debug("广播用户同步事件失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void saveDeviceRegistry(SyncMessageDto message) {
|
||||
DeviceRegistry incoming = JSON.parseObject(message.getPayload(), DeviceRegistry.class);
|
||||
if (incoming == null || incoming.getDeviceId() == null || incoming.getDeviceId().isBlank()) {
|
||||
|
||||
Reference in New Issue
Block a user