feat(iot): 支持按区域和设备类型查询配置

新增 getConfigByAreaIdAndRelationType 方法,用于跨设备获取配置场景。
例如:工牌设备需要获取该区域的信标配置。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-01-23 13:46:49 +08:00
parent e4d07a5306
commit 06ca3070cd
10 changed files with 1439 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
package com.viewsh.module.ops.environment.service.dispatch;
import com.viewsh.module.ops.api.badge.BadgeDeviceStatusDTO;
import com.viewsh.module.ops.core.dispatch.DispatchEngine;
import com.viewsh.module.ops.core.dispatch.model.OrderDispatchContext;
import com.viewsh.module.ops.enums.PriorityEnum;
import com.viewsh.module.ops.environment.service.badge.BadgeDeviceStatusService;
import com.viewsh.module.ops.environment.service.cleanorder.CleanOrderService;
import com.viewsh.module.ops.environment.test.BadgeDispatchTestConfig;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import jakarta.annotation.Resource;
import static org.junit.jupiter.api.Assertions.*;
/**
* 工牌设备调度流程集成测试
*
* @author lzh
*/
@Slf4j
@SpringJUnitConfig(classes = BadgeDispatchTestConfig.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class})
public class BadgeDeviceDispatchTest {
@Resource
private BadgeDeviceStatusService badgeDeviceStatusService;
@Resource
private CleanOrderService cleanOrderService;
@Resource
private DispatchEngine dispatchEngine;
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 测试区域ID - A座2楼男卫
*/
private static final Long TEST_AREA_ID = 1301L;
/**
* 测试设备ID
*/
private static final Long TEST_DEVICE_ID = 31L;
private static final String TEST_DEVICE_CODE = "09207455611";
/**
* 第二个设备
*/
private static final Long TEST_DEVICE_2 = 34L;
private static final String TEST_DEVICE_2_CODE = "09207457042";
@BeforeEach
void setUp() {
log.info("========================================");
log.info("开始准备测试数据...");
log.info("========================================");
// 清理之前的测试数据
try {
badgeDeviceStatusService.deleteBadgeStatus(TEST_DEVICE_ID);
badgeDeviceStatusService.deleteBadgeStatus(TEST_DEVICE_2);
} catch (Exception e) {
log.warn("清理测试数据失败(可能首次运行): {}", e.getMessage());
}
// 初始化区域设备索引
try {
badgeDeviceStatusService.addToAreaIndex(TEST_DEVICE_ID, TEST_AREA_ID);
badgeDeviceStatusService.addToAreaIndex(TEST_DEVICE_2, TEST_AREA_ID);
} catch (Exception e) {
log.warn("初始化区域索引失败: {}", e.getMessage());
}
log.info("测试数据准备完成: areaId={}, deviceId={}", TEST_AREA_ID, TEST_DEVICE_ID);
}
/**
* 简单测试 - 只测试心跳和状态
*/
@Test
void testHeartbeatAndStatus() {
log.info("========================================");
log.info("测试:心跳和状态");
log.info("========================================");
try {
// 模拟心跳
badgeDeviceStatusService.handleHeartbeatWithArea(
TEST_DEVICE_ID,
TEST_DEVICE_CODE,
75,
TEST_AREA_ID,
"A座2楼男卫"
);
// 查询状态
BadgeDeviceStatusDTO status = badgeDeviceStatusService.getBadgeStatus(TEST_DEVICE_ID);
log.info("========================================");
log.info("心跳和状态测试完成");
log.info("设备状态: status={}, battery={}%, area={}",
status.getStatus(), status.getBatteryLevel(), status.getCurrentAreaName());
log.info("========================================");
assertNotNull(status, "设备状态不应为空");
assertEquals("idle", status.getStatus().getCode());
} catch (Exception e) {
log.error("心跳测试失败", e);
fail("测试失败: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,121 @@
package com.viewsh.module.ops.environment.test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.viewsh.module.ops.api.queue.OrderQueueDTO;
import com.viewsh.module.ops.api.queue.OrderQueueService;
import com.viewsh.module.ops.core.dispatch.DispatchEngine;
import com.viewsh.module.ops.environment.service.badge.BadgeDeviceStatusService;
import com.viewsh.module.ops.environment.service.badge.BadgeDeviceStatusServiceImpl;
import com.viewsh.module.ops.environment.service.dispatch.BadgeDeviceAreaAssignStrategy;
import com.viewsh.module.ops.environment.service.dispatch.BadgeDeviceScheduleStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* 测试配置类
* 使用 Mock 避免外部依赖Redis、数据库等
*
* @author lzh
*/
@Configuration
public class BadgeDispatchTestConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = mock(RedisTemplate.class);
when(template.opsForHash()).thenReturn(mock(org.springframework.data.redis.core.HashOperations.class));
when(template.opsForSet()).thenReturn(mock(org.springframework.data.redis.core.SetOperations.class));
when(template.keys(anyString())).thenReturn(new HashSet<>());
when(template.expire(anyString(), anyLong(), any())).thenReturn(true);
when(template.delete(anyString())).thenReturn(true);
return template;
}
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
@Bean
public BadgeDeviceStatusServiceImpl badgeDeviceStatusServiceImpl(
RedisTemplate<String, Object> redisTemplate,
ObjectMapper objectMapper) {
BadgeDeviceStatusServiceImpl service = new BadgeDeviceStatusServiceImpl();
setField(service, "redisTemplate", redisTemplate);
setField(service, "objectMapper", objectMapper);
try {
service.afterPropertiesSet();
} catch (Exception e) {
// ignore
}
return service;
}
@Bean
public BadgeDeviceStatusService badgeDeviceStatusService(BadgeDeviceStatusServiceImpl impl) {
return impl;
}
@Bean
public OrderQueueService orderQueueService() {
OrderQueueService mockService = mock(OrderQueueService.class);
when(mockService.getWaitingTasksByUserId(anyLong())).thenReturn(Collections.emptyList());
return mockService;
}
@Bean
public DispatchEngine dispatchEngine() {
return new DispatchEngine();
}
@Bean
public BadgeDeviceAreaAssignStrategy badgeDeviceAreaAssignStrategy(
BadgeDeviceStatusService badgeDeviceStatusService,
OrderQueueService orderQueueService,
DispatchEngine dispatchEngine) {
BadgeDeviceAreaAssignStrategy strategy = new BadgeDeviceAreaAssignStrategy();
setField(strategy, "badgeDeviceStatusService", badgeDeviceStatusService);
setField(strategy, "orderQueueService", orderQueueService);
setField(strategy, "dispatchEngine", dispatchEngine);
strategy.init();
return strategy;
}
@Bean
public BadgeDeviceScheduleStrategy badgeDeviceScheduleStrategy(
BadgeDeviceStatusService badgeDeviceStatusService,
OrderQueueService orderQueueService,
DispatchEngine dispatchEngine) {
BadgeDeviceScheduleStrategy strategy = new BadgeDeviceScheduleStrategy();
setField(strategy, "badgeDeviceStatusService", badgeDeviceStatusService);
setField(strategy, "orderQueueService", orderQueueService);
setField(strategy, "dispatchEngine", dispatchEngine);
strategy.init();
return strategy;
}
/**
* 通过反射设置私有字段
*/
private void setField(Object target, String fieldName, Object value) {
try {
java.lang.reflect.Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(target, value);
} catch (Exception e) {
throw new RuntimeException("Failed to set field: " + fieldName, e);
}
}
}

View File

@@ -0,0 +1,25 @@
package com.viewsh.module.ops.environment.test;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
/**
* 测试用 Spring Boot 应用配置
* 排除数据源自动配置,避免数据库连接问题
*
* @author lzh
*/
@SpringBootApplication(
scanBasePackages = {
"com.viewsh.module.ops.environment",
"com.viewsh.module.ops.core"
},
exclude = {
DataSourceAutoConfiguration.class,
RedisAutoConfiguration.class
}
)
public class TestApplication {
}

View File

@@ -0,0 +1,290 @@
-- ============================================
-- 区域设备关联完整配置(三个设备各司其职)
-- ============================================
-- 设备类型说明:
-- BADGE → buttonEvent按键映射配置
-- BEACON → beaconPresence信标MAC + 窗口配置)
-- TRAFFIC_COUNTER → trafficThreshold触发阈值配置
--
-- 已知设备信息:
-- 工牌31(09207455611), 34(09207457042) | 产品: 19 / AOQwO9pJWKgfFTk4
-- 客流32 | 产品: 21 / 82Zr08RUnstRHRO2
-- 信标需要补充设备ID
-- ============================================
-- ============================================
-- 清理旧数据(可选)
-- ============================================
-- DELETE FROM `ops_area_device_relation` WHERE `id` BETWEEN 10000 AND 10999;
-- ============================================
-- 1. A座2楼男卫 (area_id = 1301)
-- ============================================
-- 1.1 工牌设备 - 按键映射配置
INSERT INTO `ops_area_device_relation` (
`id`, `area_id`, `device_id`, `device_key`, `product_id`, `product_key`,
`relation_type`, `config_data`, `enabled`, `creator`, `create_time`,
`updater`, `update_time`, `deleted`, `tenant_id`
) VALUES (
10101, -- id
1301, -- area_id (A座2楼男卫)
31, -- device_id (工牌)
'09207455611', -- device_key
19, -- product_id
'AOQwO9pJWKgfFTk4', -- product_key
'BADGE', -- relation_type
'{
"buttonEvent": {
"enabled": true,
"confirmKeyId": 1,
"queryKeyId": 2
}
}',
1, -- enabled
'system',
'2026-01-23 09:56:22',
'system',
'2026-01-23 09:56:22',
b'0',
1
);
-- 1.2 信标设备 - MAC地址 + 窗口配置
-- 注意device_id 需要替换为实际的信标设备ID
INSERT INTO `ops_area_device_relation` (
`id`, `area_id`, `device_id`, `device_key`, `product_id`, `product_key`,
`relation_type`, `config_data`, `enabled`, `creator`, `create_time`,
`updater`, `update_time`, `deleted`, `tenant_id`
) VALUES (
10102, -- id
1301, -- area_id (A座2楼男卫)
37, -- device_id (信标需替换为实际ID)
'BEACON_MALE_001', -- device_key
21, -- product_id (假设与客流同产品,需确认)
'82Zr08RUnstRHRO2', -- product_key
'BEACON', -- relation_type
'{
"beaconPresence": {
"enabled": true,
"beaconMac": "F0:C8:60:1D:10:BB",
"window": {
"sampleTtlSeconds": 120,
"missingValue": -999
},
"enter": {
"rssiThreshold": -70,
"windowSize": 3,
"hitCount": 2,
"autoArrival": true
},
"exit": {
"weakRssiThreshold": -85,
"windowSize": 5,
"hitCount": 4,
"warningDelayMinutes": 0,
"lossTimeoutMinutes": 10,
"minValidWorkMinutes": 3,
"autoComplete": true
}
}
}',
1, -- enabled
'system',
'2026-01-23 09:56:22',
'system',
'2026-01-23 09:56:22',
b'0',
1
);
-- 1.3 客流计数器 - 触发阈值配置
INSERT INTO `ops_area_device_relation` (
`id`, `area_id`, `device_id`, `device_key`, `product_id`, `product_key`,
`relation_type`, `config_data`, `enabled`, `creator`, `create_time`,
`updater`, `update_time`, `deleted`, `tenant_id`
) VALUES (
10103, -- id
1301, -- area_id (A座2楼男卫)
32, -- device_id (客流计数器)
'11225420037', -- device_key
21, -- product_id
'82Zr08RUnstRHRO2', -- product_key
'TRAFFIC_COUNTER', -- relation_type
'{
"trafficThreshold": {
"threshold": 100,
"timeWindowSeconds": 3600,
"autoCreateOrder": true,
"orderPriority": "P1"
}
}',
1, -- enabled
'system',
'2026-01-23 09:56:22',
'system',
'2026-01-23 09:56:22',
b'0',
1
);
-- ============================================
-- 2. A座2楼女卫 (area_id = 1302)
-- ============================================
-- 2.1 工牌设备 - 按键映射配置
INSERT INTO `ops_area_device_relation` (
`id`, `area_id`, `device_id`, `device_key`, `product_id`, `product_key`,
`relation_type`, `config_data`, `enabled`, `creator`, `create_time`,
`updater`, `update_time`, `deleted`, `tenant_id`
) VALUES (
10201, -- id
1302, -- area_id (A座2楼女卫)
34, -- device_id (工牌)
'09207457042', -- device_key
19, -- product_id
'AOQwO9pJWKgfFTk4', -- product_key
'BADGE', -- relation_type
'{
"buttonEvent": {
"enabled": true,
"confirmKeyId": 1,
"queryKeyId": 2
}
}',
1, -- enabled
'system',
'2026-01-23 09:56:22',
'system',
'2026-01-23 09:56:22',
b'0',
1
);
-- 2.2 信标设备 - MAC地址 + 窗口配置
-- 注意device_id 需要替换为实际的信标设备ID
INSERT INTO `ops_area_device_relation` (
`id`, `area_id`, `device_id`, `device_key`, `product_id`, `product_key`,
`relation_type`, `config_data`, `enabled`, `creator`, `create_time`,
`updater`, `update_time`, `deleted`, `tenant_id`
) VALUES (
10202, -- id
1302, -- area_id (A座2楼女卫)
38, -- device_id (信标需替换为实际ID)
'BEACON_FEMALE_001', -- device_key
21, -- product_id
'82Zr08RUnstRHRO2', -- product_key
'BEACON', -- relation_type
'{
"beaconPresence": {
"enabled": true,
"beaconMac": "F0:C8:60:1D:10:BC",
"window": {
"sampleTtlSeconds": 120,
"missingValue": -999
},
"enter": {
"rssiThreshold": -70,
"windowSize": 3,
"hitCount": 2,
"autoArrival": true
},
"exit": {
"weakRssiThreshold": -85,
"windowSize": 5,
"hitCount": 4,
"warningDelayMinutes": 0,
"lossTimeoutMinutes": 10,
"minValidWorkMinutes": 3,
"autoComplete": true
}
}
}',
1, -- enabled
'system',
'2026-01-23 09:56:22',
'system',
'2026-01-23 09:56:22',
b'0',
1
);
-- 2.3 客流计数器 - 触发阈值配置
INSERT INTO `ops_area_device_relation` (
`id`, `area_id`, `device_id`, `device_key`, `product_id`, `product_key`,
`relation_type`, `config_data`, `enabled`, `creator`, `create_time`,
`updater`, `update_time`, `deleted`, `tenant_id`
) VALUES (
10203, -- id
1302, -- area_id (A座2楼女卫)
33, -- device_id (同一客流计数器)
'11225420051', -- device_key
21, -- product_id
'82Zr08RUnstRHRO2', -- product_key
'TRAFFIC_COUNTER', -- relation_type
'{
"trafficThreshold": {
"threshold": 80, -- 女卫阈值设低一些
"timeWindowSeconds": 3600,
"autoCreateOrder": true,
"orderPriority": "P1"
}
}',
1, -- enabled
'system',
'2026-01-23 09:56:22',
'system',
'2026-01-23 09:56:22',
b'0',
1
);
-- ============================================
-- 3. 验证查询
-- ============================================
-- 查看所有关联(按区域、类型分组)
SELECT
r.id,
r.area_id,
a.area_name,
r.relation_type,
r.device_id,
r.device_key,
r.enabled,
JSON_EXTRACT(r.config_data, '$.buttonEvent') AS button_event,
JSON_EXTRACT(r.config_data, '$.beaconPresence.beaconMac') AS beacon_mac,
JSON_EXTRACT(r.config_data, '$.trafficThreshold.threshold') AS traffic_threshold
FROM `ops_area_device_relation` r
LEFT JOIN `ops_bus_area` a ON r.area_id = a.id
WHERE r.id BETWEEN 10101 AND 10203
ORDER BY r.area_id, FIELD(r.relation_type, 'BADGE', 'BEACON', 'TRAFFIC_COUNTER');
-- 查看某个区域的所有设备
SELECT
r.relation_type,
r.device_id,
r.device_key,
r.config_data
FROM `ops_area_device_relation` r
WHERE r.area_id = 1301 -- 1301=男卫, 1302=女卫
AND r.enabled = 1
AND r.deleted = 0;
-- ============================================
-- 4. 查询你的设备信息确认信标设备ID
-- ============================================
-- 查看所有工牌和客流设备
SELECT id, device_name, nickname, serial_number, product_id, product_key, device_type, state
FROM iot_device
WHERE id IN (31, 32, 34)
ORDER BY id;
-- 查找信标类设备(根据产品或类型)
SELECT id, device_name, nickname, serial_number, product_id, product_key, device_type, state
FROM iot_device
WHERE device_type LIKE '%BEACON%'
OR product_key LIKE '%BEACON%'
OR device_name LIKE '%信标%'
ORDER BY id;

View File

@@ -0,0 +1,303 @@
-- ============================================
-- 工牌设备调度流程测试数据
-- ============================================
-- 说明:
-- 1. 先执行此 SQL 插入测试数据
-- 2. 运行 BadgeDeviceDispatchTest 测试类
-- 3. device_id 需要在 iot_device 表中存在,如不存在需先创建设备
-- ============================================
-- ============================================
-- 1. 区域测试数据 (ops_bus_area)
-- ============================================
-- 清理旧测试数据
DELETE FROM ops_bus_area WHERE id BETWEEN 1000 AND 1999;
-- 插入测试区域
INSERT INTO `ops_bus_area` (`id`, `parent_id`, `parent_path`, `area_name`, `area_code`, `area_type`, `function_type`, `floor_no`, `cleaning_frequency`, `standard_duration`, `area_level`, `is_active`, `sort`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES
-- 园区级别
(1000, NULL, '/1000', '测试科技园', 'TEST_PARK', 'PARK', NULL, NULL, 1, 30, 'MEDIUM', 1, 1, 'system', NOW(), 'system', NOW(), 0, 1),
-- 楼栋级别
(1100, 1000, '/1000/1100', 'A座写字楼', 'BLDG_A', 'BUILDING', NULL, NULL, 1, 30, 'MEDIUM', 1, 1, 'system', NOW(), 'system', NOW(), 0, 1),
(1101, 1000, '/1000/1101', 'B座写字楼', 'BLDG_B', 'BUILDING', NULL, NULL, 1, 30, 'MEDIUM', 1, 2, 'system', NOW(), 'system', NOW(), 0, 1),
-- 楼层级别 - A座
(1200, 1100, '/1000/1100/1200', 'A座1楼', 'A_F1', 'FLOOR', NULL, 1, 2, 30, 'MEDIUM', 1, 1, 'system', NOW(), 'system', NOW(), 0, 1),
(1201, 1100, '/1000/1100/1201', 'A座2楼', 'A_F2', 'FLOOR', NULL, 2, 2, 30, 'MEDIUM', 1, 2, 'system', NOW(), 'system', NOW(), 0, 1),
(1202, 1100, '/1000/1100/1202', 'A座3楼', 'A_F3', 'FLOOR', NULL, 3, 1, 20, 'LOW', 1, 3, 'system', NOW(), 'system', NOW(), 0, 1),
-- 楼层级别 - B座
(1203, 1101, '/1000/1101/1203', 'B座1楼', 'B_F1', 'FLOOR', NULL, 1, 2, 30, 'HIGH', 1, 1, 'system', NOW(), 'system', NOW(), 0, 1),
(1204, 1101, '/1000/1101/1204', 'B座2楼', 'B_F2', 'FLOOR', NULL, 2, 2, 30, 'MEDIUM', 1, 2, 'system', NOW(), 'system', NOW(), 0, 1),
-- 功能区域 - A座2楼主要测试区域
(1300, 1201, '/1000/1100/1201/1300', 'A座2楼电梯厅', 'A_F2_ELEVATOR', 'FUNCTION', 'ELEVATOR', 2, 4, 15, 'HIGH', 1, 1, 'system', NOW(), 'system', NOW(), 0, 1),
(1301, 1201, '/1000/1100/1201/1301', 'A座2楼男卫', 'A_F2_MALE_TOILET', 'FUNCTION', 'MALE_TOILET', 2, 4, 20, 'HIGH', 1, 2, 'system', NOW(), 'system', NOW(), 0, 1),
(1302, 1201, '/1000/1100/1201/1302', 'A座2楼女卫', 'A_F2_FEMALE_TOILET', 'FUNCTION', 'FEMALE_TOILET', 2, 4, 20, 'HIGH', 1, 3, 'system', NOW(), 'system', NOW(), 0, 1),
(1303, 1201, '/1000/1100/1201/1303', 'A座2楼走廊', 'A_F2_CORRIDOR', 'FUNCTION', 'PUBLIC', 2, 2, 30, 'MEDIUM', 1, 4, 'system', NOW(), 'system', NOW(), 0, 1),
(1304, 1201, '/1000/1100/1201/1304', 'A座2楼会议室', 'A_F2_MEETING', 'FUNCTION', 'PUBLIC', 2, 1, 15, 'LOW', 1, 5, 'system', NOW(), 'system', NOW(), 0, 1),
-- 功能区域 - B座1楼
(1305, 1203, '/1000/1101/1203/1305', 'B座1楼大堂', 'B_F1_LOBBY', 'FUNCTION', 'PUBLIC', 1, 6, 45, 'HIGH', 1, 1, 'system', NOW(), 'system', NOW(), 0, 1),
(1306, 1203, '/1000/1101/1203/1306', 'B座1楼电梯厅', 'B_F1_ELEVATOR', 'FUNCTION', 'ELEVATOR', 1, 4, 15, 'HIGH', 1, 2, 'system', NOW(), 'system', NOW(), 0, 1);
-- ============================================
-- 2. 区域设备关联测试数据 (ops_area_device_relation)
-- ============================================
-- config_data 按照 CleanOrderIntegrationConfig 文档配置
-- 清理旧测试数据
DELETE FROM ops_area_device_relation WHERE id BETWEEN 10000 AND 10999;
DELETE FROM ops_area_device_relation WHERE device_id IN (2011, 2012, 2013, 2014, 2015, 2021, 2022);
-- 假设产品ID根据实际情况调整或使用现有产品ID
SET @TEST_PRODUCT_ID = 1;
-- 插入测试设备关联
-- BADGE 类型设备配置buttonEvent按键事件+ beaconPresence信标检测
INSERT INTO `ops_area_device_relation` (`id`, `area_id`, `device_id`, `device_key`, `product_id`, `product_key`, `relation_type`, `config_data`, `enabled`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES
-- A座2楼电梯厅设备主要测试区域 - area_id=1300
(10000, 1300, 2011, 'BADGE_A2_E1', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 'BADGE',
'{
"buttonEvent": {
"enabled": true,
"confirmKeyId": 1,
"queryKeyId": 2
},
"beaconPresence": {
"enabled": true,
"beaconMac": "F0:C8:60:1D:10:BB",
"window": {
"sampleTtlSeconds": 300,
"missingValue": -999
},
"enter": {
"rssiThreshold": -70,
"windowSize": 3,
"hitCount": 2,
"autoArrival": true
},
"exit": {
"weakRssiThreshold": -85,
"windowSize": 5,
"hitCount": 4,
"warningDelayMinutes": 0,
"lossTimeoutMinutes": 5,
"minValidWorkMinutes": 2,
"autoComplete": true
}
}
}', 1, 'system', NOW(), 'system', NOW(), 0, 1),
(10001, 1300, 2012, 'BADGE_A2_E2', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 'BADGE',
'{
"buttonEvent": {
"enabled": true,
"confirmKeyId": 1,
"queryKeyId": 2
},
"beaconPresence": {
"enabled": true,
"beaconMac": "F0:C8:60:1D:10:BC",
"window": {
"sampleTtlSeconds": 300,
"missingValue": -999
},
"enter": {
"rssiThreshold": -70,
"windowSize": 3,
"hitCount": 2,
"autoArrival": true
},
"exit": {
"weakRssiThreshold": -85,
"windowSize": 5,
"hitCount": 4,
"warningDelayMinutes": 0,
"lossTimeoutMinutes": 5,
"minValidWorkMinutes": 2,
"autoComplete": true
}
}
}', 1, 'system', NOW(), 'system', NOW(), 0, 1),
(10002, 1300, 2013, 'BADGE_A2_E3', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 'BADGE',
'{
"buttonEvent": {
"enabled": true,
"confirmKeyId": 1,
"queryKeyId": 2
},
"beaconPresence": {
"enabled": false,
"beaconMac": null,
"window": null,
"enter": null,
"exit": null
}
}', 1, 'system', NOW(), 'system', NOW(), 0, 1),
-- A座2楼其他功能区域设备
(10003, 1301, 2014, 'BADGE_A2_M1', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 'BADGE',
'{
"buttonEvent": {
"enabled": true,
"confirmKeyId": 1,
"queryKeyId": 2
},
"beaconPresence": {
"enabled": true,
"beaconMac": "F0:C8:60:1D:10:BD",
"window": {
"sampleTtlSeconds": 300,
"missingValue": -999
},
"enter": {
"rssiThreshold": -70,
"windowSize": 3,
"hitCount": 2,
"autoArrival": true
},
"exit": {
"weakRssiThreshold": -85,
"windowSize": 5,
"hitCount": 4,
"warningDelayMinutes": 0,
"lossTimeoutMinutes": 5,
"minValidWorkMinutes": 2,
"autoComplete": true
}
}
}', 1, 'system', NOW(), 'system', NOW(), 0, 1),
(10004, 1302, 2015, 'BADGE_A2_F1', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 'BADGE',
'{
"buttonEvent": {
"enabled": true,
"confirmKeyId": 1,
"queryKeyId": 2
},
"beaconPresence": {
"enabled": true,
"beaconMac": "F0:C8:60:1D:10:BE",
"window": {
"sampleTtlSeconds": 300,
"missingValue": -999
},
"enter": {
"rssiThreshold": -70,
"windowSize": 3,
"hitCount": 2,
"autoArrival": true
},
"exit": {
"weakRssiThreshold": -85,
"windowSize": 5,
"hitCount": 4,
"warningDelayMinutes": 0,
"lossTimeoutMinutes": 5,
"minValidWorkMinutes": 2,
"autoComplete": true
}
}
}', 1, 'system', NOW(), 'system', NOW(), 0, 1),
-- B座1楼设备用于跨区域测试
(10010, 1305, 2021, 'BADGE_B1_L1', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 'BADGE',
'{
"buttonEvent": {
"enabled": true,
"confirmKeyId": 1,
"queryKeyId": 2
},
"beaconPresence": {
"enabled": false,
"beaconMac": null,
"window": null,
"enter": null,
"exit": null
}
}', 1, 'system', NOW(), 'system', NOW(), 0, 1),
(10011, 1306, 2022, 'BADGE_B1_E1', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 'BADGE',
'{
"buttonEvent": {
"enabled": true,
"confirmKeyId": 1,
"queryKeyId": 2
},
"beaconPresence": {
"enabled": true,
"beaconMac": "F0:C8:60:1D:10:BF",
"window": {
"sampleTtlSeconds": 300,
"missingValue": -999
},
"enter": {
"rssiThreshold": -70,
"windowSize": 3,
"hitCount": 2,
"autoArrival": true
},
"exit": {
"weakRssiThreshold": -85,
"windowSize": 5,
"hitCount": 4,
"warningDelayMinutes": 0,
"lossTimeoutMinutes": 5,
"minValidWorkMinutes": 2,
"autoComplete": true
}
}
}', 1, 'system', NOW(), 'system', NOW(), 0, 1);
-- ============================================
-- 3. IoT设备测试数据 (iot_device)
-- ============================================
-- 清理旧测试设备
DELETE FROM iot_device WHERE id BETWEEN 2011 AND 2030;
-- 创建测试工牌设备
INSERT INTO `iot_device` (`id`, `device_name`, `nickname`, `serial_number`, `product_id`, `product_key`, `device_type`, `state`, `active_time`, `tenant_id`) VALUES
(2011, '测试工牌_A2_E1', 'A座2楼工牌1', 'SN2011001', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 10, 3, NOW(), 1),
(2012, '测试工牌_A2_E2', 'A座2楼工牌2', 'SN2011002', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 10, 3, NOW(), 1),
(2013, '测试工牌_A2_E3', 'A座2楼工牌3', 'SN2011003', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 10, 3, NOW(), 1),
(2014, '测试工牌_A2_M1', 'A座2楼男卫工牌', 'SN2011004', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 10, 3, NOW(), 1),
(2015, '测试工牌_A2_F1', 'A座2楼女卫工牌', 'SN2011005', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 10, 3, NOW(), 1),
(2021, '测试工牌_B1_L1', 'B座1楼大堂工牌', 'SN2011006', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 10, 3, NOW(), 1),
(2022, '测试工牌_B1_E1', 'B座1楼电梯工牌', 'SN2011007', @TEST_PRODUCT_ID, 'BADGE_PRODUCT', 10, 3, NOW(), 1);
-- ============================================
-- 4. 验证查询
-- ============================================
-- 查看插入的区域
SELECT id, parent_id, parent_path, area_name, area_code, area_type, function_type, floor_no
FROM ops_bus_area
WHERE id BETWEEN 1000 AND 1999
ORDER BY parent_path, id;
-- 查看插入的设备关联(含配置)
SELECT r.id, r.area_id, a.area_name, r.device_id, r.device_key, r.relation_type, r.config_data
FROM ops_area_device_relation r
LEFT JOIN ops_bus_area a ON r.area_id = a.id
WHERE r.id BETWEEN 10000 AND 10999
ORDER BY r.area_id, r.id;
-- 查看插入的IoT设备
SELECT id, device_name, nickname, serial_number, state
FROM iot_device
WHERE id BETWEEN 2011 AND 2030
ORDER BY id;
-- 查看某区域下的所有工牌设备(含配置)
SELECT r.device_id, r.device_key, d.device_name, r.config_data
FROM ops_area_device_relation r
LEFT JOIN iot_device d ON r.device_id = d.id
WHERE r.area_id = 1300 -- A座2楼电梯厅
AND r.relation_type = 'BADGE'
AND r.enabled = 1
AND r.deleted = 0;