如何防止设备被重复控制
Posted CodeJames
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何防止设备被重复控制相关的知识,希望对你有一定的参考价值。
1. 引言
在一个物联网的系统中,主要有三部分组成:云端、WiFi
、电控。当用户在APP
上控制设备时,其控制下发链路是:云端>>WIFI
>> 电控。当电控收到控制指令后,执行设备控制,控制成功后,返回结果给云端,并将结果展示在APP
上,其状态上报链路是: 电控 >> WIFI
>> 云端。
在云端和电控交互之间存在着三种指令,其分别是:
- 控制指令:属于云端发下给电控
- 控制返回:电控控制后,将设备的状态返回给云端
- 定时上报:电控端会定时向云端上报自身的状态
在做设备之间联动的时候,云端只解析上报,除了存在定时上报外,当控制指令下发后,也会触发状态上报(其协议头和定时上报是同一个),因此云端只会关注上报。
但是存在一个问题,云端只解析同一种协议,如何区分是控制的上报还是定时的上报的?
2. 场景联动实例
举个相关的例子,在冬天的时候,空调长时间开制热模式会导致空气湿度下降,可能会导致用户皮肤脱皮、流鼻血等情况发生。这时候就可以创建一个空调和加湿器联动的场景,当空调设置为制热模式的时候,就帮助打开用户家里的加湿器,帮助房屋保湿。
其控制逻辑是:当用户控制空调到制热模式时,APP
端通过云端下发指令到电控,电控再去控制空调的模式,当模式改变后,会触发电控端上报,此时就会上报空调此时的状态到云端。
当空调设备的状态上报到云端后,需进行逻辑判断,如果空调状态为:开机、制热模式,云端就去控制和空调绑定的加湿器,这就实现了空调联动加湿器。
在引言中提到,设备的状态存在定时主动上报,按照之前的逻辑,会再次控制加湿器开机。但这并不合理,因为该上报的状态并不是控制产生的,因此云端需进行过滤,以此来解决重复控制设备。
3. 如何解决重复控制设备
在这部分使用Redis
搭建一个去重逻辑:
- 缓存第一次状态,设置过期时间
- 判断之后上报状态与缓存中是否一致
- 如果一致,直接返回;
- 如果不一致,修改缓存,控制设备。
接下来就以代码展示一下,去重逻辑。
3.1 定义一个状态状态的BO
@Data
public class StatusPushBO
private String applianceId;
private String power;
private String mode;
如上所示,BO
中包含设备id
、电源、模式,用于接收电控上报的状态。
3.2 Redis实现缓存
在这个demo
中依旧使用SpringBoot
作为基础框架。
- 引入
Redis
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 创建一个
RedisClient
public class RedisClient<T>
private RedisTemplate<String, T> redisTemplate;
private ValueOperations<String, T> valueOperation;
public RedisClient(RedisTemplate<String, T> redisTemplate)
this.redisTemplate = redisTemplate;
this.valueOperations = redisTemplate.opsForValue();
// 通过key 获取redis中value
public <T> T get(String key)
return this.redisTemplate.opsForValue().get(key);
// 判断redis key是否存在
public Boolean exists(String key)
return redisTemplate.hasKey(key);
// 设置过期时间
public void setExpire(String key, long timeout, TimeUnit unit)
redisTemplate.expire(key, timeout, unit);
// 将数据放入redis中,并且设置过期时间
public void setex(String key, Object value, Long timeout, TimeUnit timeUnit)
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
如上建立了一个RedisClient
类,其中包含四个方法:get
、exists
、 setExpire
, setex
。
- 使用
redis
缓存状态,防止相同状态控制设备。
@Service
public class DemoLinkageService
// 注入redisClient
@Autowired
RedisClient redisClient;
// 联动加湿器, linkage: 联动
public void linkageHumidier(StatusPushBO statusPushBO)
String applianceId = statusPushBO.getApplianceId();
String redisKey = "linkage" + applianceId;
// 判断该key是否已经存在redis中
if (redisClient.exists(redisKey))
// 存在就取值
StatusPushBO statusBOByRedis = redisClient.get(redisKey);
if (statusPushBO.equals(statusBOByRedis))
// 判断上报的状态和缓存中的状态一致,就重新更新redisKey的时间。
redisClient.setExpire(redisKey, 80L, TimeUnit.MINUTES);
// 直接return
return;
// 将首次状态放入redis进行缓存
redisClient.setex(redisKey, StatusPushBO, 80L, TimeUnit.MINUTES);
// TODO 控制设备
如上,如果设备第一次上报状态,此时Redis
里面是没有该设备的状态,就跳过状态相等判断逻辑,之后将这次的状态添加进Redis
缓存一段时间;如果之后的状态没有变换,来自于设备的主动上报,此时就会进入状态是否相同判断逻辑中,并且状态相等,那就重新更新过期时间,并直接返回,不进行后续逻辑处理。
结语
如上使用Redis
中间件,避免了相同的状态,重复控制设备。
如何防止静态资源被映射在 /* 上的前端控制器 servlet 处理
【中文标题】如何防止静态资源被映射在 /* 上的前端控制器 servlet 处理【英文标题】:How to prevent static resources from being handled by front controller servlet which is mapped on /* 【发布时间】:2012-11-11 09:32:12 【问题描述】:我有一个充当前端控制器的 servlet。
@WebServlet("/*")
不过,这也处理 CSS 和图像文件。我怎样才能防止这种情况发生?
【问题讨论】:
【参考方案1】:你有两个选择:
使用更具体的 URL 模式,例如 /app/*
或 *.do
,然后让您的所有页面请求都匹配此 URL 模式。另见Design Patterns web based applications
同1,但是要从请求URL中隐藏servlet映射;然后,您应该将所有静态资源放在一个公共文件夹中,例如/static
或/resources
,并创建一个过滤器来检查请求 URL 是否不匹配,然后转发到 servlet。这是一个示例,假设您的控制器 servlet 是 @WebServlet("/app/*")
,过滤器是 @WebFilter("/*")
,并且您的所有静态资源都在 /resources
文件夹中。
HttpServletRequest req = (HttpServletRequest) request;
String path = req.getRequestURI().substring(req.getContextPath().length());
if (path.startsWith("/resources/"))
chain.doFilter(request, response); // Goes to default servlet.
else
request.getRequestDispatcher("/app" + path).forward(request, response); // Goes to your controller.
另见How to access static resources when mapping a global front controller servlet on /*。
【讨论】:
@BalusC 为什么静态资源在通过控制器加载时会抛出 404。我有一个用 /* 映射的过滤器。加载 jquery 文件时,它会抛出错误 404 - 找不到资源。我使用您上面提供的解决方案解决了它。你能解开我的疑惑吗?【参考方案2】:我知道这是一个老问题,我猜@BalusC 的答案可能很好用。但是我无法修改正在处理的 JSF 应用程序的 URL,所以我只需检查路径并返回是否是静态资源:
String path = request.getRequestURI().substring(request.getContextPath().length());
if (path.contains("/resources/"))
return;
这对我来说很好。
【讨论】:
以上是关于如何防止设备被重复控制的主要内容,如果未能解决你的问题,请参考以下文章