-
Java
-
微服务
-
设备接入
-
MQTT
-
物联网
-
SpringBoot categories:
-
后端开发 keywords: 设备服务接入系统,微服务架构,Java实战,设备接入,MQTT,设备注册,设备认证,设备连接管理,设备指令下发,设备数据上报,SpringBoot description: 深入解析设备服务接入系统的Java微服务架构设计,从设备注册认证、MQTT连接管理、设备指令下发、设备数据上报到心跳检测和状态管理,提供完整的企业级设备接入解决方案
设备服务接入系统Java微服务后端架构实战
1. 架构概述
设备服务接入系统是IoT平台的核心基础设施,需要支持海量设备的接入、认证、连接管理、指令下发和数据上报。本篇文章将深入讲解如何基于Java微服务架构实现一个高性能、高可用、可扩展的设备服务接入系统。
1.1 系统架构图
go
物理设备 → 接入网关 → 设备服务 → 数据库/缓存
↓
设备注册/认证
↓
建立连接(MQTT/WebSocket)
↓
心跳检测
↓
设备指令下发
↓
设备数据上报
↓
设备状态管理
设备服务 → 消息队列 → 业务服务
↓
设备数据转发
↓
设备事件通知
1.2 核心组件
-
接入网关(Access Gateway):负责设备连接接入、协议解析、消息路由
-
设备服务(Device Service):负责设备注册、设备认证、设备管理、设备状态维护
-
MQTT Broker:负责MQTT协议连接管理和消息转发
-
设备连接管理器(Device Connection Manager):负责设备连接状态管理、心跳检测
-
设备指令服务(Device Command Service):负责设备指令下发、指令状态跟踪
-
设备数据服务(Device Data Service):负责设备数据接收、数据解析、数据转发
-
数据库(MySQL):持久化设备信息、设备连接记录、设备指令记录
-
缓存(Redis):缓存设备连接状态、设备在线状态、设备会话信息
-
消息队列(Kafka/RocketMQ):异步处理设备数据、设备事件通知
2. 接入网关服务实现
2.1 MQTT接入网关核心代码
go
/**
* MQTT接入网关
* 负责设备MQTT连接接入、协议解析、消息路由
*/
@Component
@Slf4j
public class MqttAccessGateway {
@Autowired
private DeviceServiceClient deviceServiceClient;
@Autowired
private DeviceConnectionManager deviceConnectionManager;
@Autowired
private DeviceCommandService deviceCommandService;
@Autowired
private DeviceDataService deviceDataService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
/**
* 处理设备连接请求
* 流程:设备连接 → 设备认证 → 建立连接 → 注册设备连接
*/
public MqttConnectResult handleConnect(MqttConnectMessage message, Channel channel) {
try {
String clientId = message.payload().clientIdentifier();
String username = message.payload().userName();
String password = message.payload().passwordInBytes() != null ?
new String(message.payload().passwordInBytes()) : null;
// 1. 设备认证
DeviceAuthResult authResult = deviceServiceClient.authenticateDevice(
clientId, username, password);
if (!authResult.isSuccess()) {
log.warn("设备认证失败: clientId={}, username={}, reason={}",
clientId, username, authResult.getMessage());
return MqttConnectResult.failed(authResult.getMessage());
}
// 2. 注册设备连接
DeviceConnection connection = deviceConnectionManager.registerConnection(
clientId, channel, authResult.getDeviceInfo());
// 3. 更新设备在线状态
updateDeviceOnlineStatus(clientId, true);
// 4. 发送设备上线事件
sendDeviceOnlineEvent(authResult.getDeviceInfo());
log.info("设备连接成功: clientId={}, deviceId={}, deviceType={}",
clientId, authResult.getDeviceInfo().getDeviceId(),
authResult.getDeviceInfo().getDeviceType());
return MqttConnectResult.success(connection);
} catch (Exception e) {
log.error("处理设备连接失败: error={}", e.getMessage(), e);
return MqttConnectResult.failed("设备连接处理失败: " + e.getMessage());
}
}
/**
* 处理设备断开连接
*/
public void handleDisconnect(String clientId, Channel channel) {
try {
// 1. 移除设备连接
deviceConnectionManager.removeConnection(clientId, channel);
// 2. 更新设备离线状态
updateDeviceOnlineStatus(clientId, false);
// 3. 发送设备离线事件
DeviceInfo deviceInfo = deviceServiceClient.getDeviceInfo(clientId);
if (deviceInfo != null) {
sendDeviceOfflineEvent(deviceInfo);
}
log.info("设备断开连接: clientId={}", clientId);
} catch (Exception e) {
log.error("处理设备断开连接失败: clientId={}, error={}",
clientId, e.getMessage(), e);
}
}
/**
* 处理设备发布消息(数据上报)
*/
public void handlePublish(String clientId, String topic, MqttPublishMessage message) {
try {
// 1. 解析设备数据
byte[] payload = message.payload().array();
DeviceData deviceData = parseDeviceData(clientId, topic, payload);
// 2. 更新设备心跳时间
updateDeviceHeartbeat(clientId);
// 3. 处理设备数据
deviceDataService.processDeviceData(deviceData);
log.debug("处理设备数据上报: clientId={}, topic={}, dataSize={}",
clientId, topic, payload.length);
} catch (Exception e) {
log.error("处理设备数据上报失败: clientId={}, topic={}, error={}",
clientId, topic, e.getMessage(), e);
}
}
/**
* 处理设备订阅请求
*/
public void handleSubscribe(String clientId, MqttSubscribeMessage message) {
try {
// 1. 验证订阅权限
List<MqttTopicSubscription> subscriptions = message.payload().topicSubscriptions();
for (MqttTopicSubscription subscription : subscriptions) {
String topic = subscription.topicName();
if (!validateSubscribePermission(clientId, topic)) {
log.warn("设备订阅权限不足: clientId={}, topic={}", clientId, topic);
continue;
}
// 2. 记录订阅关系
recordSubscription(clientId, topic);
}
log.info("设备订阅成功: clientId={}, subscriptions={}",
clientId, subscriptions.size());
} catch (Exception e) {
log.error("处理设备订阅失败: clientId={}, error={}",
clientId, e.getMessage(), e);
}
}
/**
* 下发设备指令
*/
public void sendDeviceCommand(String clientId, DeviceCommand command) {
try {
// 1. 获取设备连接
DeviceConnection connection = deviceConnectionManager.getConnection(clientId);
if (connection == null || !connection.isActive()) {
throw new BusinessException("设备未连接: " + clientId);
}
// 2. 构建MQTT消息
String topic = buildCommandTopic(clientId, command.getCommandType());
MqttPublishMessage mqttMessage = buildMqttPublishMessage(topic, command);
// 3. 发送MQTT消息
connection.getChannel().writeAndFlush(mqttMessage);
// 4. 记录指令下发
deviceCommandService.recordCommandSent(clientId, command);
log.info("设备指令下发成功: clientId={}, commandId={}, commandType={}",
clientId, command.getCommandId(), command.getCommandType());
} catch (Exception e) {
log.error("设备指令下发失败: clientId={}, commandId={}, error={}",
clientId, command.getCommandId(), e.getMessage(), e);
throw new BusinessException("设备指令下发失败: " + e.getMessage());
}
}
/**
* 更新设备在线状态
*/
private void updateDeviceOnlineStatus(String clientId, boolean online) {
String statusKey = "device:status:" + clientId;
redisTemplate.opsForValue().set(statusKey, online ? "ONLINE" : "OFFLINE",
30, TimeUnit.MINUTES);
}
/**
* 更新设备心跳时间
*/
private void updateDeviceHeartbeat(String clientId) {
String heartbeatKey = "device:heartbeat:" + clientId;
redisTemplate.opsForValue().set(heartbeatKey, System.currentTimeMillis(),
5, TimeUnit.MINUTES);
}
/**
* 发送设备上线事件
*/
private void sendDeviceOnlineEvent(DeviceInfo deviceInfo) {
DeviceOnlineEvent event = new DeviceOnlineEvent();
event.setDeviceId(deviceInfo.getDeviceId());
event.setDeviceType(deviceInfo.getDeviceType());
event.setOnlineTime(LocalDateTime.now());
kafkaTemplate.send("device.online", deviceInfo.getDeviceId(),
JSON.toJSONString(event));
}
/**
* 发送设备离线事件
*/
private void sendDeviceOfflineEvent(DeviceInfo deviceInfo) {
DeviceOfflineEvent event = new DeviceOfflineEvent();
event.setDeviceId(deviceInfo.getDeviceId());
event.setDeviceType(deviceInfo.getDeviceType());
event.setOfflineTime(LocalDateTime.now());
kafkaTemplate.send("device.offline", deviceInfo.getDeviceId(),
JSON.toJSONString(event));
}
/**
* 解析设备数据
*/
private DeviceData parseDeviceData(String clientId, String topic, byte[] payload) {
DeviceData deviceData = new DeviceData();
deviceData.setClientId(clientId);
deviceData.setTopic(topic);
deviceData.setPayload(payload);
deviceData.setReceiveTime(LocalDateTime.now());
// 根据topic解析数据
if (topic.contains("/data/")) {
// 数据上报
deviceData.setDataType("DATA_REPORT");
deviceData.setDataContent(JSON.parseObject(new String(payload), Map.class));
} else if (topic.contains("/status/")) {
// 状态上报
deviceData.setDataType("STATUS_REPORT");
deviceData.setDataContent(JSON.parseObject(new String(payload), Map.class));
} else if (topic.contains("/event/")) {
// 事件上报
deviceData.setDataType("EVENT_REPORT");
deviceData.setDataContent(JSON.parseObject(new String(payload), Map.class));
}
return deviceData;
}
/**
* 验证订阅权限
*/
private boolean validateSubscribePermission(String clientId, String topic) {
// 设备只能订阅自己的指令主题
return topic.startsWith("/device/" + clientId + "/command/");
}
/**
* 记录订阅关系
*/
private void recordSubscription(String clientId, String topic) {
String subscriptionKey = "device:subscription:" + clientId + ":" + topic;
redisTemplate.opsForValue().set(subscriptionKey, "SUBSCRIBED",
24, TimeUnit.HOURS);
}
/**
* 构建指令主题
*/
private String buildCommandTopic(String clientId, String commandType) {
return "/device/" + clientId + "/command/" + commandType;
}
/**
* 构建MQTT发布消息
*/
private MqttPublishMessage buildMqttPublishMessage(String topic, DeviceCommand command) {
MqttFixedHeader fixedHeader = new MqttFixedHeader(
MqttMessageType.PUBLISH, false, MqttQoS.AT_LEAST_ONCE, false, 0);
MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(
topic, 0);
byte[] payload = JSON.toJSONString(command).getBytes();
return new MqttPublishMessage(fixedHeader, variableHeader,
Unpooled.wrappedBuffer(payload));
}
}
2.2 Netty MQTT服务器启动
go
/**
* MQTT服务器启动类
*/
@Component
@Slf4j
public class MqttServer {
@Autowired
private MqttAccessGateway mqttAccessGateway;
@Value("${mqtt.server.port:1883}")
private int mqttPort;
@Value("${mqtt.server.boss-threads:1}")
private int bossThreads;
@Value("${mqtt.server.worker-threads:4}")
private int workerThreads;
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
private Channel serverChannel;
@PostConstruct
public void start() throws InterruptedException {
bossGroup = new NioEventLoopGroup(bossThreads);
workerGroup = new NioEventLoopGroup(workerThreads);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.SO_REUSEADDR, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childHandler(new MqttChannelInitializer(mqttAccessGateway));
ChannelFuture future = bootstrap.bind(mqttPort).sync();
serverChannel = future.channel();
log.info("MQTT服务器启动成功: port={}", mqttPort);
}
@PreDestroy
public void stop() {
if (serverChannel != null) {
serverChannel.close();
}
if (workerGroup != null) {
workerGroup.shutdownGracefully();
}
if (bossGroup != null) {
bossGroup.shutdownGracefully();
}
log.info("MQTT服务器已关闭");
}
}
/**
* MQTT通道初始化器
*/
public class MqttChannelInitializer extends ChannelInitializer<SocketChannel> {
private final MqttAccessGateway mqttAccessGateway;
public MqttChannelInitializer(MqttAccessGateway mqttAccessGateway) {
this.mqttAccessGateway = mqttAccessGateway;
}
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// MQTT编解码器
pipeline.addLast(new MqttDecoder(1024 * 1024));
pipeline.addLast(new MqttEncoder());
// MQTT协议处理器
pipeline.addLast(new MqttProtocolHandler(mqttAccessGateway));
// 空闲检测
pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new MqttIdleHandler());
}
}
3. 设备服务实现
3.1 设备认证服务
go
/**
* 设备认证服务
* 负责设备注册、设备认证
*/
@Service
@Slf4j
public class DeviceAuthService {
@Autowired
private DeviceMapper deviceMapper;
@Autowired
private DeviceCredentialMapper deviceCredentialMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 设备认证
*/
public DeviceAuthResult authenticateDevice(String clientId, String username, String password) {
try {
// 1. 查询设备凭证
DeviceCredential credential = deviceCredentialMapper.selectByClientId(clientId);
if (credential == null) {
return DeviceAuthResult.failed("设备不存在");
}
// 2. 验证用户名
if (!credential.getUsername().equals(username)) {
return DeviceAuthResult.failed("用户名错误");
}
// 3. 验证密码
if (!validatePassword(password, credential.getPassword())) {
return DeviceAuthResult.failed("密码错误");
}
// 4. 查询设备信息
Device device = deviceMapper.selectById(credential.getDeviceId());
if (device == null) {
return DeviceAuthResult.failed("设备信息不存在");
}
// 5. 检查设备状态
if (!"ACTIVE".equals(device.getStatus())) {
return DeviceAuthResult.failed("设备未激活");
}
// 6. 构建设备信息
DeviceInfo deviceInfo = convertToDeviceInfo(device);
log.info("设备认证成功: clientId={}, deviceId={}", clientId, device.getDeviceId());
return DeviceAuthResult.success(deviceInfo);
} catch (Exception e) {
log.error("设备认证失败: clientId={}, error={}", clientId, e.getMessage(), e);
return DeviceAuthResult.failed("设备认证失败: " + e.getMessage());
}
}
/**
* 设备注册
*/
@Transactional(rollbackFor = Exception.class)
public DeviceRegisterResult registerDevice(DeviceRegisterRequest request) {
try {
// 1. 检查设备是否已存在
Device existingDevice = deviceMapper.selectByDeviceNo(request.getDeviceNo());
if (existingDevice != null) {
return DeviceRegisterResult.failed("设备已存在");
}
// 2. 创建设备信息
Device device = new Device();
device.setDeviceNo(request.getDeviceNo());
device.setDeviceName(request.getDeviceName());
device.setDeviceType(request.getDeviceType());
device.setManufacturer(request.getManufacturer());
device.setModel(request.getModel());
device.setStatus("INACTIVE");
device.setCreateTime(LocalDateTime.now());
device.setUpdateTime(LocalDateTime.now());
deviceMapper.insert(device);
// 3. 创建设备凭证
String clientId = generateClientId(device.getId());
String username = generateUsername(device.getId());
String password = generatePassword();
DeviceCredential credential = new DeviceCredential();
credential.setDeviceId(device.getId());
credential.setClientId(clientId);
credential.setUsername(username);
credential.setPassword(encryptPassword(password));
credential.setCreateTime(LocalDateTime.now());
credential.setUpdateTime(LocalDateTime.now());
deviceCredentialMapper.insert(credential);
log.info("设备注册成功: deviceId={}, deviceNo={}, clientId={}",
device.getId(), device.getDeviceNo(), clientId);
DeviceRegisterResult result = new DeviceRegisterResult();
result.setSuccess(true);
result.setDeviceId(device.getId());
result.setClientId(clientId);
result.setUsername(username);
result.setPassword(password);
result.setMessage("设备注册成功");
return result;
} catch (Exception e) {
log.error("设备注册失败: deviceNo={}, error={}",
request.getDeviceNo(), e.getMessage(), e);
return DeviceRegisterResult.failed("设备注册失败: " + e.getMessage());
}
}
/**
* 验证密码
*/
private boolean validatePassword(String inputPassword, String storedPassword) {
// 使用BCrypt或其他加密算法验证密码
return BCrypt.checkpw(inputPassword, storedPassword);
}
/**
* 加密密码
*/
private String encryptPassword(String password) {
return BCrypt.hashpw(password, BCrypt.gensalt());
}
/**
* 生成客户端ID
*/
private String generateClientId(Long deviceId) {
return "DEVICE_" + deviceId + "_" + System.currentTimeMillis();
}
/**
* 生成用户名
*/
private String generateUsername(Long deviceId) {
return "USER_" + deviceId;
}
/**
* 生成密码
*/
private String generatePassword() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* 转换为设备信息
*/
private DeviceInfo convertToDeviceInfo(Device device) {
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.setDeviceId(device.getId());
deviceInfo.setDeviceNo(device.getDeviceNo());
deviceInfo.setDeviceName(device.getDeviceName());
deviceInfo.setDeviceType(device.getDeviceType());
deviceInfo.setManufacturer(device.getManufacturer());
deviceInfo.setModel(device.getModel());
deviceInfo.setStatus(device.getStatus());
return deviceInfo;
}
}
3.2 设备连接管理器
go
/**
* 设备连接管理器
* 负责设备连接状态管理、心跳检测
*/
@Service
@Slf4j
public class DeviceConnectionManager {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private DeviceMapper deviceMapper;
private final ConcurrentHashMap<String, DeviceConnection> connections = new ConcurrentHashMap<>();
/**
* 注册设备连接
*/
public DeviceConnection registerConnection(String clientId, Channel channel, DeviceInfo deviceInfo) {
DeviceConnection connection = new DeviceConnection();
connection.setClientId(clientId);
connection.setDeviceId(deviceInfo.getDeviceId());
connection.setChannel(channel);
connection.setDeviceInfo(deviceInfo);
connection.setConnectTime(LocalDateTime.now());
connection.setLastHeartbeatTime(LocalDateTime.now());
connection.setActive(true);
connections.put(clientId, connection);
// 缓存连接信息
String connectionKey = "device:connection:" + clientId;
redisTemplate.opsForValue().set(connectionKey, connection,
30, TimeUnit.MINUTES);
log.info("设备连接注册成功: clientId={}, deviceId={}",
clientId, deviceInfo.getDeviceId());
return connection;
}
/**
* 移除设备连接
*/
public void removeConnection(String clientId, Channel channel) {
DeviceConnection connection = connections.remove(clientId);
if (connection != null) {
connection.setActive(false);
connection.setDisconnectTime(LocalDateTime.now());
// 删除缓存
String connectionKey = "device:connection:" + clientId;
redisTemplate.delete(connectionKey);
log.info("设备连接移除成功: clientId={}", clientId);
}
}
/**
* 获取设备连接
*/
public DeviceConnection getConnection(String clientId) {
// 1. 从内存获取
DeviceConnection connection = connections.get(clientId);
if (connection != null && connection.isActive()) {
return connection;
}
// 2. 从缓存获取
String connectionKey = "device:connection:" + clientId;
connection = (DeviceConnection) redisTemplate.opsForValue().get(connectionKey);
return connection;
}
/**
* 更新设备心跳
*/
public void updateHeartbeat(String clientId) {
DeviceConnection connection = connections.get(clientId);
if (connection != null) {
connection.setLastHeartbeatTime(LocalDateTime.now());
// 更新缓存
String connectionKey = "device:connection:" + clientId;
redisTemplate.opsForValue().set(connectionKey, connection,
30, TimeUnit.MINUTES);
}
}
/**
* 检查设备心跳超时
*/
@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void checkHeartbeatTimeout() {
try {
LocalDateTime now = LocalDateTime.now();
LocalDateTime timeoutThreshold = now.minusMinutes(5); // 5分钟超时
List<String> timeoutClients = new ArrayList<>();
for (Map.Entry<String, DeviceConnection> entry : connections.entrySet()) {
String clientId = entry.getKey();
DeviceConnection connection = entry.getValue();
if (connection.getLastHeartbeatTime().isBefore(timeoutThreshold)) {
timeoutClients.add(clientId);
}
}
// 处理超时设备
for (String clientId : timeoutClients) {
handleHeartbeatTimeout(clientId);
}
if (!timeoutClients.isEmpty()) {
log.warn("检测到心跳超时设备: count={}, clients={}",
timeoutClients.size(), timeoutClients);
}
} catch (Exception e) {
log.error("检查设备心跳超时失败: error={}", e.getMessage(), e);
}
}
/**
* 处理心跳超时
*/
private void handleHeartbeatTimeout(String clientId) {
DeviceConnection connection = connections.get(clientId);
if (connection != null) {
// 关闭连接
if (connection.getChannel() != null && connection.getChannel().isActive()) {
connection.getChannel().close();
}
// 移除连接
removeConnection(clientId, connection.getChannel());
// 更新设备状态为离线
updateDeviceOfflineStatus(connection.getDeviceId());
log.warn("设备心跳超时,断开连接: clientId={}, deviceId={}",
clientId, connection.getDeviceId());
}
}
/**
* 更新设备离线状态
*/
private void updateDeviceOfflineStatus(Long deviceId) {
Device device = deviceMapper.selectById(deviceId);
if (device != null) {
device.setStatus("OFFLINE");
device.setUpdateTime(LocalDateTime.now());
deviceMapper.updateById(device);
}
}
}
4. 设备指令服务实现
4.1 设备指令服务核心代码
go
/**
* 设备指令服务
* 负责设备指令下发、指令状态跟踪
*/
@Service
@Slf4j
public class DeviceCommandService {
@Autowired
private DeviceCommandMapper deviceCommandMapper;
@Autowired
private MqttAccessGateway mqttAccessGateway;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 下发设备指令
*/
public DeviceCommandResult sendCommand(DeviceCommandRequest request) {
try {
// 1. 创建设备指令记录
DeviceCommand command = createDeviceCommand(request);
deviceCommandMapper.insert(command);
// 2. 下发指令到设备
mqttAccessGateway.sendDeviceCommand(request.getClientId(), command);
// 3. 记录指令下发
recordCommandSent(command);
// 4. 构建返回结果
DeviceCommandResult result = new DeviceCommandResult();
result.setSuccess(true);
result.setCommandId(command.getId());
result.setCommandNo(command.getCommandNo());
result.setStatus("SENT");
result.setMessage("指令下发成功");
log.info("设备指令下发成功: commandId={}, clientId={}, commandType={}",
command.getId(), request.getClientId(), request.getCommandType());
return result;
} catch (Exception e) {
log.error("设备指令下发失败: clientId={}, commandType={}, error={}",
request.getClientId(), request.getCommandType(), e.getMessage(), e);
return DeviceCommandResult.failed("指令下发失败: " + e.getMessage());
}
}
/**
* 处理设备指令响应
*/
@KafkaListener(topics = "device.command.response", groupId = "device-command-group")
public void handleCommandResponse(String message) {
try {
DeviceCommandResponse response = JSON.parseObject(message, DeviceCommandResponse.class);
// 1. 查询指令记录
DeviceCommand command = deviceCommandMapper.selectByCommandNo(
response.getCommandNo());
if (command == null) {
log.warn("设备指令记录不存在: commandNo={}", response.getCommandNo());
return;
}
// 2. 更新指令状态
command.setStatus(response.isSuccess() ? "SUCCESS" : "FAILED");
command.setResponseTime(LocalDateTime.now());
command.setResponseData(response.getResponseData());
command.setErrorMessage(response.getErrorMessage());
command.setUpdateTime(LocalDateTime.now());
deviceCommandMapper.updateById(command);
// 3. 发送指令响应事件
sendCommandResponseEvent(command, response);
log.info("设备指令响应处理成功: commandId={}, commandNo={}, status={}",
command.getId(), command.getCommandNo(), command.getStatus());
} catch (Exception e) {
log.error("处理设备指令响应失败: error={}", e.getMessage(), e);
}
}
/**
* 创建设备指令
*/
private DeviceCommand createDeviceCommand(DeviceCommandRequest request) {
DeviceCommand command = new DeviceCommand();
command.setCommandNo(generateCommandNo());
command.setClientId(request.getClientId());
command.setDeviceId(request.getDeviceId());
command.setCommandType(request.getCommandType());
command.setCommandData(request.getCommandData());
command.setStatus("PENDING");
command.setCreateTime(LocalDateTime.now());
command.setUpdateTime(LocalDateTime.now());
return command;
}
/**
* 记录指令下发
*/
private void recordCommandSent(DeviceCommand command) {
String commandKey = "device:command:" + command.getCommandNo();
redisTemplate.opsForValue().set(commandKey, command,
24, TimeUnit.HOURS);
}
/**
* 发送指令响应事件
*/
private void sendCommandResponseEvent(DeviceCommand command, DeviceCommandResponse response) {
DeviceCommandResponseEvent event = new DeviceCommandResponseEvent();
event.setCommandId(command.getId());
event.setCommandNo(command.getCommandNo());
event.setClientId(command.getClientId());
event.setDeviceId(command.getDeviceId());
event.setCommandType(command.getCommandType());
event.setSuccess(response.isSuccess());
event.setResponseData(response.getResponseData());
event.setResponseTime(LocalDateTime.now());
kafkaTemplate.send("device.command.response.event",
command.getCommandNo(), JSON.toJSONString(event));
}
/**
* 生成指令编号
*/
private String generateCommandNo() {
return "CMD_" + System.currentTimeMillis() + "_" +
UUID.randomUUID().toString().substring(0, 8);
}
}
5. 设备数据服务实现
5.1 设备数据服务核心代码
go
/**
* 设备数据服务
* 负责设备数据接收、数据解析、数据转发
*/
@Service
@Slf4j
public class DeviceDataService {
@Autowired
private DeviceDataMapper deviceDataMapper;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 处理设备数据
*/
@Async("deviceDataExecutor")
public void processDeviceData(DeviceData deviceData) {
try {
// 1. 保存设备数据
saveDeviceData(deviceData);
// 2. 根据数据类型处理
switch (deviceData.getDataType()) {
case "DATA_REPORT":
processDataReport(deviceData);
break;
case "STATUS_REPORT":
processStatusReport(deviceData);
break;
case "EVENT_REPORT":
processEventReport(deviceData);
break;
default:
log.warn("未知的设备数据类型: dataType={}", deviceData.getDataType());
}
log.debug("设备数据处理成功: clientId={}, dataType={}",
deviceData.getClientId(), deviceData.getDataType());
} catch (Exception e) {
log.error("处理设备数据失败: clientId={}, dataType={}, error={}",
deviceData.getClientId(), deviceData.getDataType(), e.getMessage(), e);
}
}
/**
* 保存设备数据
*/
private void saveDeviceData(DeviceData deviceData) {
DeviceDataRecord record = new DeviceDataRecord();
record.setClientId(deviceData.getClientId());
record.setDeviceId(deviceData.getDeviceId());
record.setDataType(deviceData.getDataType());
record.setTopic(deviceData.getTopic());
record.setDataContent(JSON.toJSONString(deviceData.getDataContent()));
record.setReceiveTime(deviceData.getReceiveTime());
record.setCreateTime(LocalDateTime.now());
deviceDataMapper.insert(record);
}
/**
* 处理数据上报
*/
private void processDataReport(DeviceData deviceData) {
// 1. 发送到数据上报主题
DeviceDataReportEvent event = new DeviceDataReportEvent();
event.setClientId(deviceData.getClientId());
event.setDeviceId(deviceData.getDeviceId());
event.setDataContent(deviceData.getDataContent());
event.setReceiveTime(deviceData.getReceiveTime());
kafkaTemplate.send("device.data.report",
deviceData.getClientId(), JSON.toJSONString(event));
// 2. 更新设备数据缓存
updateDeviceDataCache(deviceData);
}
/**
* 处理状态上报
*/
private void processStatusReport(DeviceData deviceData) {
// 1. 发送到状态上报主题
DeviceStatusReportEvent event = new DeviceStatusReportEvent();
event.setClientId(deviceData.getClientId());
event.setDeviceId(deviceData.getDeviceId());
event.setStatusData(deviceData.getDataContent());
event.setReportTime(deviceData.getReceiveTime());
kafkaTemplate.send("device.status.report",
deviceData.getClientId(), JSON.toJSONString(event));
// 2. 更新设备状态缓存
updateDeviceStatusCache(deviceData);
}
/**
* 处理事件上报
*/
private void processEventReport(DeviceData deviceData) {
// 发送到事件上报主题
DeviceEventReportEvent event = new DeviceEventReportEvent();
event.setClientId(deviceData.getClientId());
event.setDeviceId(deviceData.getDeviceId());
event.setEventData(deviceData.getDataContent());
event.setEventTime(deviceData.getReceiveTime());
kafkaTemplate.send("device.event.report",
deviceData.getClientId(), JSON.toJSONString(event));
}
/**
* 更新设备数据缓存
*/
private void updateDeviceDataCache(DeviceData deviceData) {
String cacheKey = "device:data:latest:" + deviceData.getClientId();
redisTemplate.opsForValue().set(cacheKey, deviceData.getDataContent(),
1, TimeUnit.HOURS);
}
/**
* 更新设备状态缓存
*/
private void updateDeviceStatusCache(DeviceData deviceData) {
String cacheKey = "device:status:latest:" + deviceData.getClientId();
redisTemplate.opsForValue().set(cacheKey, deviceData.getDataContent(),
30, TimeUnit.MINUTES);
}
}
6. 数据模型定义
6.1 设备实体
go
/**
* 设备实体
*/
@Data
@TableName("t_device")
public class Device {
/**
* 设备ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 设备编号
*/
private String deviceNo;
/**
* 设备名称
*/
private String deviceName;
/**
* 设备类型
*/
private String deviceType;
/**
* 制造商
*/
private String manufacturer;
/**
* 型号
*/
private String model;
/**
* 设备状态:INACTIVE-未激活, ACTIVE-激活, OFFLINE-离线, ONLINE-在线
*/
private String status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
6.2 设备凭证实体
go
/**
* 设备凭证实体
*/
@Data
@TableName("t_device_credential")
public class DeviceCredential {
/**
* 凭证ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 设备ID
*/
private Long deviceId;
/**
* 客户端ID
*/
private String clientId;
/**
* 用户名
*/
private String username;
/**
* 密码(加密)
*/
private String password;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
6.3 设备指令实体
go
/**
* 设备指令实体
*/
@Data
@TableName("t_device_command")
public class DeviceCommand {
/**
* 指令ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 指令编号
*/
private String commandNo;
/**
* 客户端ID
*/
private String clientId;
/**
* 设备ID
*/
private Long deviceId;
/**
* 指令类型
*/
private String commandType;
/**
* 指令数据
*/
private String commandData;
/**
* 指令状态:PENDING-待发送, SENT-已发送, SUCCESS-成功, FAILED-失败
*/
private String status;
/**
* 响应时间
*/
private LocalDateTime responseTime;
/**
* 响应数据
*/
private String responseData;
/**
* 错误信息
*/
private String errorMessage;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
6.4 设备数据记录实体
go
/**
* 设备数据记录实体
*/
@Data
@TableName("t_device_data")
public class DeviceDataRecord {
/**
* 记录ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 客户端ID
*/
private String clientId;
/**
* 设备ID
*/
private Long deviceId;
/**
* 数据类型:DATA_REPORT-数据上报, STATUS_REPORT-状态上报, EVENT_REPORT-事件上报
*/
private String dataType;
/**
* 主题
*/
private String topic;
/**
* 数据内容(JSON)
*/
private String dataContent;
/**
* 接收时间
*/
private LocalDateTime receiveTime;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
7. 数据库设计
7.1 设备表
go
CREATE TABLE `t_device` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '设备ID',
`device_no` VARCHAR(64) NOT NULL COMMENT '设备编号',
`device_name` VARCHAR(128) NOT NULL COMMENT '设备名称',
`device_type` VARCHAR(32) NOT NULL COMMENT '设备类型',
`manufacturer` VARCHAR(64) DEFAULT NULL COMMENT '制造商',
`model` VARCHAR(64) DEFAULT NULL COMMENT '型号',
`status` VARCHAR(32) NOT NULL COMMENT '设备状态:INACTIVE-未激活, ACTIVE-激活, OFFLINE-离线, ONLINE-在线',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_device_no` (`device_no`),
KEY `idx_device_type` (`device_type`),
KEY `idx_status` (`status`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备表';
7.2 设备凭证表
go
CREATE TABLE `t_device_credential` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '凭证ID',
`device_id` BIGINT(20) NOT NULL COMMENT '设备ID',
`client_id` VARCHAR(128) NOT NULL COMMENT '客户端ID',
`username` VARCHAR(64) NOT NULL COMMENT '用户名',
`password` VARCHAR(255) NOT NULL COMMENT '密码(加密)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_client_id` (`client_id`),
UNIQUE KEY `uk_username` (`username`),
KEY `idx_device_id` (`device_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备凭证表';
7.3 设备指令表
go
CREATE TABLE `t_device_command` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '指令ID',
`command_no` VARCHAR(64) NOT NULL COMMENT '指令编号',
`client_id` VARCHAR(128) NOT NULL COMMENT '客户端ID',
`device_id` BIGINT(20) NOT NULL COMMENT '设备ID',
`command_type` VARCHAR(32) NOT NULL COMMENT '指令类型',
`command_data` TEXT COMMENT '指令数据',
`status` VARCHAR(32) NOT NULL COMMENT '指令状态:PENDING-待发送, SENT-已发送, SUCCESS-成功, FAILED-失败',
`response_time` DATETIME DEFAULT NULL COMMENT '响应时间',
`response_data` TEXT COMMENT '响应数据',
`error_message` VARCHAR(512) DEFAULT NULL COMMENT '错误信息',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_command_no` (`command_no`),
KEY `idx_client_id` (`client_id`),
KEY `idx_device_id` (`device_id`),
KEY `idx_command_type` (`command_type`),
KEY `idx_status` (`status`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备指令表';
7.4 设备数据表
go
CREATE TABLE `t_device_data` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`client_id` VARCHAR(128) NOT NULL COMMENT '客户端ID',
`device_id` BIGINT(20) NOT NULL COMMENT '设备ID',
`data_type` VARCHAR(32) NOT NULL COMMENT '数据类型:DATA_REPORT-数据上报, STATUS_REPORT-状态上报, EVENT_REPORT-事件上报',
`topic` VARCHAR(256) NOT NULL COMMENT '主题',
`data_content` TEXT COMMENT '数据内容(JSON)',
`receive_time` DATETIME NOT NULL COMMENT '接收时间',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_client_id` (`client_id`),
KEY `idx_device_id` (`device_id`),
KEY `idx_data_type` (`data_type`),
KEY `idx_receive_time` (`receive_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备数据表';
8. Mapper实现
8.1 设备Mapper
go
/**
* 设备Mapper
*/
@Mapper
public interface DeviceMapper extends BaseMapper<Device> {
/**
* 根据设备编号查询设备
*/
@Select("SELECT * FROM t_device WHERE device_no = #{deviceNo}")
Device selectByDeviceNo(@Param("deviceNo") String deviceNo);
/**
* 根据设备ID查询设备
*/
@Select("SELECT * FROM t_device WHERE id = #{deviceId}")
Device selectById(@Param("deviceId") Long deviceId);
}
8.2 设备凭证Mapper
go
/**
* 设备凭证Mapper
*/
@Mapper
public interface DeviceCredentialMapper extends BaseMapper<DeviceCredential> {
/**
* 根据客户端ID查询凭证
*/
@Select("SELECT * FROM t_device_credential WHERE client_id = #{clientId}")
DeviceCredential selectByClientId(@Param("clientId") String clientId);
/**
* 根据设备ID查询凭证
*/
@Select("SELECT * FROM t_device_credential WHERE device_id = #{deviceId}")
DeviceCredential selectByDeviceId(@Param("deviceId") Long deviceId);
}
8.3 设备指令Mapper
go
/**
* 设备指令Mapper
*/
@Mapper
public interface DeviceCommandMapper extends BaseMapper<DeviceCommand> {
/**
* 根据指令编号查询指令
*/
@Select("SELECT * FROM t_device_command WHERE command_no = #{commandNo}")
DeviceCommand selectByCommandNo(@Param("commandNo") String commandNo);
}
9. 配置类
9.1 MQTT配置
go
# application.yml
mqtt:
server:
port: 1883
boss-threads: 1
worker-threads: 4
max-message-size: 1048576
keep-alive: 60
9.2 异步任务配置
go
/**
* 异步任务配置
*/
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("deviceDataExecutor")
public Executor deviceDataExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(1000);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("device-data-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
9.3 Kafka配置
go
# application.yml
spring:
kafka:
bootstrap-servers: localhost:9092
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
acks: all
retries: 3
batch-size: 16384
linger-ms: 1
buffer-memory: 33554432
consumer:
group-id: device-access-group
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
auto-offset-reset: latest
enable-auto-commit: true
10. 性能优化策略
10.1 连接池优化
go
/**
* Netty连接池配置
*/
@Configuration
public class NettyConfig {
@Bean
public EventLoopGroup bossGroup() {
return new NioEventLoopGroup(1);
}
@Bean
public EventLoopGroup workerGroup() {
return new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 2);
}
}
10.2 缓存优化
go
/**
* 设备连接缓存服务
*/
@Service
@Slf4j
public class DeviceConnectionCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 缓存设备连接信息
*/
public void cacheConnection(String clientId, DeviceConnection connection) {
String cacheKey = "device:connection:" + clientId;
redisTemplate.opsForValue().set(cacheKey, connection,
30, TimeUnit.MINUTES);
}
/**
* 获取缓存的设备连接
*/
public DeviceConnection getCachedConnection(String clientId) {
String cacheKey = "device:connection:" + clientId;
return (DeviceConnection) redisTemplate.opsForValue().get(cacheKey);
}
}
10.3 批量处理优化
go
/**
* 设备数据批量处理服务
*/
@Service
@Slf4j
public class DeviceDataBatchService {
@Autowired
private DeviceDataMapper deviceDataMapper;
private final List<DeviceDataRecord> batchBuffer = new ArrayList<>();
private final int BATCH_SIZE = 100;
/**
* 批量保存设备数据
*/
public synchronized void batchSave(DeviceDataRecord record) {
batchBuffer.add(record);
if (batchBuffer.size() >= BATCH_SIZE) {
flushBatch();
}
}
/**
* 刷新批次
*/
private void flushBatch() {
if (batchBuffer.isEmpty()) {
return;
}
try {
// 批量插入
deviceDataMapper.insertBatch(batchBuffer);
batchBuffer.clear();
log.debug("批量保存设备数据成功: count={}", BATCH_SIZE);
} catch (Exception e) {
log.error("批量保存设备数据失败: error={}", e.getMessage(), e);
}
}
/**
* 定时刷新批次
*/
@Scheduled(fixedRate = 5000) // 每5秒刷新一次
public void scheduledFlush() {
flushBatch();
}
}
11. 监控告警
11.1 业务指标监控
go
/**
* 业务指标监控
*/
@Component
@Slf4j
public class DeviceAccessMetricsMonitor {
@Autowired
private MeterRegistry meterRegistry;
/**
* 记录设备连接
*/
public void recordDeviceConnect(String deviceType) {
Counter.builder("device.connect.total")
.tag("device_type", deviceType)
.register(meterRegistry)
.increment();
}
/**
* 记录设备断开
*/
public void recordDeviceDisconnect(String deviceType) {
Counter.builder("device.disconnect.total")
.tag("device_type", deviceType)
.register(meterRegistry)
.increment();
}
/**
* 记录设备数据上报
*/
public void recordDeviceDataReport(String dataType) {
Counter.builder("device.data.report.total")
.tag("data_type", dataType)
.register(meterRegistry)
.increment();
}
/**
* 记录设备指令下发
*/
public void recordDeviceCommandSent(String commandType) {
Counter.builder("device.command.sent.total")
.tag("command_type", commandType)
.register(meterRegistry)
.increment();
}
/**
* 记录设备心跳超时
*/
public void recordHeartbeatTimeout() {
Counter.builder("device.heartbeat.timeout.total")
.register(meterRegistry)
.increment();
}
}
11.2 告警规则配置
go
# prometheus-alert-rules.yml
groups:
- name: device_access_alerts
rules:
- alert: DeviceConnectFailureRateHigh
expr: rate(device_connect_total{status="failed"}[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "设备连接失败率过高"
description: "设备连接失败率超过10%,当前值: {{ $value }}"
- alert: DeviceHeartbeatTimeoutHigh
expr: rate(device_heartbeat_timeout_total[5m]) > 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "设备心跳超时率过高"
description: "设备心跳超时率超过5%,当前值: {{ $value }}"
- alert: DeviceCommandFailureRateHigh
expr: rate(device_command_sent_total{status="failed"}[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "设备指令下发失败率过高"
description: "设备指令下发失败率超过10%,当前值: {{ $value }}"
12. 总结
本文深入讲解了设备服务接入系统的Java微服务后端架构实战,涵盖了以下核心内容:
-
系统架构设计:采用微服务架构,通过接入网关、设备服务、指令服务、数据服务协同完成设备接入管理
-
MQTT协议支持:基于Netty实现MQTT协议服务器,支持设备连接、认证、消息发布订阅
-
设备注册认证:支持设备注册、设备凭证管理、设备认证
-
连接管理:实现设备连接状态管理、心跳检测、连接超时处理
-
指令下发:支持设备指令下发、指令状态跟踪、指令响应处理
-
数据上报:支持设备数据接收、数据解析、数据转发
-
性能优化:通过连接池、缓存、批量处理提升系统性能
-
监控告警:通过业务指标监控、告警规则保障系统稳定运行
通过本文的学习,读者可以掌握如何基于Java微服务架构实现一个高性能、高可用、可扩展的设备服务接入系统,为实际项目开发提供参考和指导。