新增MES模块,包含供应商、客户、车辆和地磅数据记录管理功能,支持免密接口和数据同步。更新相关控制器、实体、服务和数据库配置,优化权限管理和数据字典支持,确保系统的灵活性和可扩展性。

This commit is contained in:
geht
2026-04-30 15:28:20 +08:00
parent 142a0bdaba
commit b03cbeff9b
121 changed files with 10540 additions and 424 deletions

View File

@@ -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");
}

View File

@@ -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", ""));

View File

@@ -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()) {