更新项目配置,新增设备同步模块,优化WebSocket和Swagger配置,增强SCADA系统的免登录接口,支持数据字典项和登录日志的免登录查询与记录。调整Java编译设置,确保更好的开发体验。
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
package org.jeecg.modules.device.sync.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
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 配置。
|
||||
*/
|
||||
@Configuration("deviceSyncWebSocketConfig")
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
registry.enableSimpleBroker("/topic", "/queue");
|
||||
registry.setApplicationDestinationPrefixes("/app");
|
||||
registry.setUserDestinationPrefix("/user");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/ws/device")
|
||||
.setAllowedOriginPatterns("*");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.jeecg.modules.device.sync.controller;
|
||||
|
||||
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.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.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.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 设备 WebSocket 控制器。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/sys/device")
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceWebSocketController {
|
||||
|
||||
private final DeviceStatusMapper deviceStatusMapper;
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
|
||||
@MessageMapping("/device/status")
|
||||
public void receiveDeviceStatus(DeviceStatus payload) {
|
||||
if (payload == null || payload.getDeviceId() == null || payload.getDeviceId().isBlank()) {
|
||||
log.warn("设备状态上报参数无效");
|
||||
return;
|
||||
}
|
||||
Date now = new Date();
|
||||
payload.setLastSeenTime(now);
|
||||
payload.setUpdateTime(now);
|
||||
if (payload.getCreatedTime() == null) {
|
||||
payload.setCreatedTime(now);
|
||||
}
|
||||
|
||||
DeviceStatus exists = deviceStatusMapper.selectOne(new LambdaQueryWrapper<DeviceStatus>()
|
||||
.eq(DeviceStatus::getDeviceId, payload.getDeviceId())
|
||||
.last("limit 1"));
|
||||
if (exists == null) {
|
||||
deviceStatusMapper.insert(payload);
|
||||
} else {
|
||||
payload.setId(exists.getId());
|
||||
payload.setCreatedTime(exists.getCreatedTime());
|
||||
deviceStatusMapper.updateById(payload);
|
||||
}
|
||||
|
||||
messagingTemplate.convertAndSend("/topic/device/" + payload.getDeviceId(), payload);
|
||||
log.debug("设备状态已广播, deviceId={}", payload.getDeviceId());
|
||||
}
|
||||
|
||||
@PostMapping("/command")
|
||||
public Result<Map<String, Object>> sendCommand(@RequestBody Map<String, Object> request) {
|
||||
String deviceId = request == null ? null : String.valueOf(request.getOrDefault("deviceId", ""));
|
||||
if (deviceId == null || deviceId.isBlank()) {
|
||||
return Result.error("deviceId不能为空");
|
||||
}
|
||||
String commandJson = request == null ? "{}" : String.valueOf(request.getOrDefault("commandJson", "{}"));
|
||||
Map<String, Object> commandPayload = new HashMap<>();
|
||||
commandPayload.put("deviceId", deviceId);
|
||||
commandPayload.put("commandJson", commandJson);
|
||||
commandPayload.put("sentAt", System.currentTimeMillis());
|
||||
|
||||
messagingTemplate.convertAndSendToUser(deviceId, "/queue/command", commandPayload);
|
||||
log.info("下发设备指令成功, deviceId={}", deviceId);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("deviceId", deviceId);
|
||||
result.put("queued", true);
|
||||
return Result.OK(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
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.dto.SyncMessageDto;
|
||||
import org.jeecg.modules.device.sync.entity.DeviceRegistry;
|
||||
import org.jeecg.modules.device.sync.entity.DeviceStatus;
|
||||
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.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.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据同步控制器。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/sys/sync")
|
||||
@RequiredArgsConstructor
|
||||
public class SyncController {
|
||||
|
||||
private final SyncIdempotentLogMapper syncIdempotentLogMapper;
|
||||
private final DeviceStatusMapper deviceStatusMapper;
|
||||
private final DeviceRegistryMapper deviceRegistryMapper;
|
||||
|
||||
@PostMapping("/batch")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Map<String, Integer>> batch(@RequestBody List<SyncMessageDto> messages) {
|
||||
int received = messages == null ? 0 : messages.size();
|
||||
int inserted = 0;
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
Map<String, Integer> empty = new HashMap<>();
|
||||
empty.put("received", 0);
|
||||
empty.put("inserted", 0);
|
||||
return Result.OK(empty);
|
||||
}
|
||||
|
||||
for (SyncMessageDto message : messages) {
|
||||
if (message == null || message.getMessageId() == null || message.getMessageId().isBlank()) {
|
||||
continue;
|
||||
}
|
||||
if (syncIdempotentLogMapper.exists(message.getMessageId())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
routeAndPersist(message);
|
||||
SyncIdempotentLog logEntity = new SyncIdempotentLog();
|
||||
logEntity.setMessageId(message.getMessageId());
|
||||
logEntity.setAggregateType(message.getAggregateType());
|
||||
logEntity.setAggregateId(message.getAggregateId());
|
||||
logEntity.setEventType(message.getEventType());
|
||||
logEntity.setCreatedTime(new Date());
|
||||
syncIdempotentLogMapper.insert(logEntity);
|
||||
inserted++;
|
||||
}
|
||||
|
||||
Map<String, Integer> result = new HashMap<>();
|
||||
result.put("received", received);
|
||||
result.put("inserted", inserted);
|
||||
return Result.OK(result);
|
||||
}
|
||||
|
||||
private void routeAndPersist(SyncMessageDto message) {
|
||||
String aggregateType = message.getAggregateType() == null ? "" : message.getAggregateType().trim().toUpperCase();
|
||||
switch (aggregateType) {
|
||||
case "DEVICE_STATUS":
|
||||
saveDeviceStatus(message);
|
||||
break;
|
||||
case "DEVICE_REGISTRY":
|
||||
saveDeviceRegistry(message);
|
||||
break;
|
||||
default:
|
||||
log.debug("未识别aggregateType,按透传记录处理,messageId={}, aggregateType={}", message.getMessageId(), aggregateType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveDeviceStatus(SyncMessageDto message) {
|
||||
DeviceStatus incoming = JSON.parseObject(message.getPayload(), DeviceStatus.class);
|
||||
if (incoming == null || incoming.getDeviceId() == null || incoming.getDeviceId().isBlank()) {
|
||||
return;
|
||||
}
|
||||
Date now = new Date();
|
||||
incoming.setUpdateTime(now);
|
||||
if (incoming.getCreatedTime() == null) {
|
||||
incoming.setCreatedTime(now);
|
||||
}
|
||||
|
||||
DeviceStatus exists = deviceStatusMapper.selectOne(new LambdaQueryWrapper<DeviceStatus>()
|
||||
.eq(DeviceStatus::getDeviceId, incoming.getDeviceId())
|
||||
.last("limit 1"));
|
||||
if (exists == null) {
|
||||
deviceStatusMapper.insert(incoming);
|
||||
} else {
|
||||
incoming.setId(exists.getId());
|
||||
incoming.setCreatedTime(exists.getCreatedTime());
|
||||
deviceStatusMapper.updateById(incoming);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveDeviceRegistry(SyncMessageDto message) {
|
||||
DeviceRegistry incoming = JSON.parseObject(message.getPayload(), DeviceRegistry.class);
|
||||
if (incoming == null || incoming.getDeviceId() == null || incoming.getDeviceId().isBlank()) {
|
||||
return;
|
||||
}
|
||||
Date now = new Date();
|
||||
incoming.setUpdateTime(now);
|
||||
if (incoming.getCreatedTime() == null) {
|
||||
incoming.setCreatedTime(now);
|
||||
}
|
||||
|
||||
DeviceRegistry exists = deviceRegistryMapper.selectOne(new LambdaQueryWrapper<DeviceRegistry>()
|
||||
.eq(DeviceRegistry::getDeviceId, incoming.getDeviceId())
|
||||
.last("limit 1"));
|
||||
if (exists == null) {
|
||||
deviceRegistryMapper.insert(incoming);
|
||||
} else {
|
||||
incoming.setId(exists.getId());
|
||||
incoming.setCreatedTime(exists.getCreatedTime());
|
||||
deviceRegistryMapper.updateById(incoming);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.jeecg.modules.device.sync.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 同步消息 DTO。
|
||||
*/
|
||||
@Data
|
||||
public class SyncMessageDto implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 消息唯一ID(幂等键)。
|
||||
*/
|
||||
private String messageId;
|
||||
|
||||
/**
|
||||
* 聚合类型。
|
||||
*/
|
||||
private String aggregateType;
|
||||
|
||||
/**
|
||||
* 聚合ID。
|
||||
*/
|
||||
private String aggregateId;
|
||||
|
||||
/**
|
||||
* 事件类型。
|
||||
*/
|
||||
private String eventType;
|
||||
|
||||
/**
|
||||
* JSON字符串载荷。
|
||||
*/
|
||||
private String payload;
|
||||
|
||||
/**
|
||||
* 事件发生时间。
|
||||
*/
|
||||
private Date occurredAt;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.jeecg.modules.device.sync.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 设备注册实体。
|
||||
*/
|
||||
@Data
|
||||
@TableName("device_registry")
|
||||
public class DeviceRegistry implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String deviceId;
|
||||
|
||||
private String deviceName;
|
||||
|
||||
private String deviceType;
|
||||
|
||||
private Integer tokenVersion;
|
||||
|
||||
private Integer enabled;
|
||||
|
||||
private Date lastSyncTime;
|
||||
|
||||
private Date createdTime;
|
||||
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.jeecg.modules.device.sync.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 设备状态实体。
|
||||
*/
|
||||
@Data
|
||||
@TableName("device_status")
|
||||
public class DeviceStatus implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String deviceId;
|
||||
|
||||
private Integer onlineStatus;
|
||||
|
||||
private Date lastSeenTime;
|
||||
|
||||
private String statusPayload;
|
||||
|
||||
private Date createdTime;
|
||||
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.jeecg.modules.device.sync.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 同步幂等日志实体。
|
||||
*/
|
||||
@Data
|
||||
@TableName("sync_idempotent_log")
|
||||
public class SyncIdempotentLog implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String messageId;
|
||||
|
||||
private String aggregateType;
|
||||
|
||||
private String aggregateId;
|
||||
|
||||
private String eventType;
|
||||
|
||||
private Date createdTime;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.jeecg.modules.device.sync.filter;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 设备 Token 滑动续签过滤器。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
|
||||
public class DeviceTokenRefreshFilter extends OncePerRequestFilter {
|
||||
private static final long DEVICE_TOKEN_TTL_SECONDS = 30L * 24 * 60 * 60;
|
||||
private static final long DEVICE_TOKEN_EXPIRE_MILLIS = 30L * 24 * 60 * 60 * 1000;
|
||||
private static final String PREFIX_DEVICE_TOKEN = "prefix_device_token:";
|
||||
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
public DeviceTokenRefreshFilter(RedisUtil redisUtil) {
|
||||
this.redisUtil = redisUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
String auth = request.getHeader("Authorization");
|
||||
if (auth != null && auth.startsWith("Bearer ")) {
|
||||
String token = auth.substring(7).trim();
|
||||
try {
|
||||
DecodedJWT jwt = JWT.decode(token);
|
||||
String tokenType = jwt.getClaim("tokenType").asString();
|
||||
if ("device".equalsIgnoreCase(tokenType)) {
|
||||
String deviceId = resolveDeviceId(jwt);
|
||||
if (deviceId != null && !deviceId.isBlank()) {
|
||||
redisUtil.expire(PREFIX_DEVICE_TOKEN + token, DEVICE_TOKEN_TTL_SECONDS);
|
||||
String refreshed = refreshDeviceToken(deviceId);
|
||||
redisUtil.set(PREFIX_DEVICE_TOKEN + refreshed, refreshed, DEVICE_TOKEN_TTL_SECONDS);
|
||||
response.setHeader("X-Refresh-Token", refreshed);
|
||||
log.debug("设备Token已续签, deviceId={}", deviceId);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("设备Token续签跳过: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private String resolveDeviceId(DecodedJWT jwt) {
|
||||
String deviceId = jwt.getClaim("deviceId").asString();
|
||||
if (deviceId == null || deviceId.isBlank()) {
|
||||
deviceId = jwt.getClaim("username").asString();
|
||||
}
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
private String refreshDeviceToken(String deviceId) {
|
||||
Date expiresAt = new Date(System.currentTimeMillis() + DEVICE_TOKEN_EXPIRE_MILLIS);
|
||||
Algorithm algorithm = Algorithm.HMAC256(deviceId + "_device_secret");
|
||||
return JWT.create()
|
||||
.withClaim("username", deviceId)
|
||||
.withClaim("deviceId", deviceId)
|
||||
.withClaim("tokenType", "device")
|
||||
.withExpiresAt(expiresAt)
|
||||
.sign(algorithm);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.jeecg.modules.device.sync.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.device.sync.entity.DeviceRegistry;
|
||||
|
||||
@Mapper
|
||||
public interface DeviceRegistryMapper extends BaseMapper<DeviceRegistry> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.jeecg.modules.device.sync.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.device.sync.entity.DeviceStatus;
|
||||
|
||||
@Mapper
|
||||
public interface DeviceStatusMapper extends BaseMapper<DeviceStatus> {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.jeecg.modules.device.sync.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.jeecg.modules.device.sync.entity.SyncIdempotentLog;
|
||||
|
||||
/**
|
||||
* 同步幂等日志 Mapper。
|
||||
*/
|
||||
@Mapper
|
||||
public interface SyncIdempotentLogMapper extends BaseMapper<SyncIdempotentLog> {
|
||||
|
||||
@Select("SELECT COUNT(1) > 0 FROM sync_idempotent_log WHERE message_id = #{messageId}")
|
||||
boolean exists(@Param("messageId") String messageId);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
CREATE TABLE IF NOT EXISTS sync_idempotent_log (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
message_id VARCHAR(64) NOT NULL COMMENT '消息唯一ID',
|
||||
aggregate_type VARCHAR(64) NOT NULL COMMENT '聚合类型',
|
||||
aggregate_id VARCHAR(64) NOT NULL COMMENT '聚合ID',
|
||||
event_type VARCHAR(64) NOT NULL COMMENT '事件类型',
|
||||
created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_sync_idempotent_log_message_id (message_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同步幂等日志';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS device_status (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
device_id VARCHAR(64) NOT NULL COMMENT '设备ID',
|
||||
online_status TINYINT NOT NULL DEFAULT 0 COMMENT '在线状态:0离线1在线',
|
||||
last_seen_time DATETIME NULL COMMENT '最后在线时间',
|
||||
status_payload JSON NULL COMMENT '状态载荷',
|
||||
created_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_status_device_id (device_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备状态表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS device_registry (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
device_id VARCHAR(64) NOT NULL COMMENT '设备ID',
|
||||
device_name VARCHAR(128) NULL COMMENT '设备名称',
|
||||
device_type VARCHAR(64) NULL COMMENT '设备类型',
|
||||
token_version INT NOT NULL DEFAULT 1 COMMENT '设备令牌版本',
|
||||
enabled TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用:0否1是',
|
||||
last_sync_time DATETIME NULL COMMENT '最后同步时间',
|
||||
created_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_registry_device_id (device_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备注册表';
|
||||
Reference in New Issue
Block a user