在项目内处理电子围栏逻辑

This commit is contained in:
2025-10-16 01:30:02 +08:00
parent 454ae20370
commit fc5a120d48
9 changed files with 325 additions and 1 deletions

View File

@ -180,6 +180,11 @@
<version>1.2.83</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- kafka -->
<dependency>

View File

@ -1,5 +1,6 @@
package com.sczx.sync.config;
import com.sczx.sync.service.ElectronicFenceService;
import lombok.extern.slf4j.Slf4j;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sczx.sync.dto.DeviceData;
@ -19,6 +20,10 @@ public class KafkaConsumer {
@Autowired
private ThreadPoolConfig threadPoolConfig;
@Autowired
private ElectronicFenceService electronicFenceService;
@Value("${dataPush.deviceDataUrl}")
private String deviceDataUrl;
@ -54,7 +59,8 @@ public class KafkaConsumer {
log.info("处理1分钟以内的消息: " + data.getClientId() + " at " + data.getTs());
// 调用接口处理逻辑
callExternalApi(data);
//callExternalApi(data);
electronicFenceService.checkCarPositionInFence( data);
} catch (Exception e) {
log.error("Failed to process message: " + message);
e.printStackTrace();

View File

@ -0,0 +1,14 @@
package com.sczx.sync.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sczx.sync.po.BaseUser;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ElectronicFenceMapper extends BaseMapper<BaseUser> {
String selectZcElectronicFenceById(Long id);
}

View File

@ -0,0 +1,37 @@
package com.sczx.sync.po;
import lombok.Data;
/**
* 电池订单信息实体类
*/
@Data
public class EfenceInfo {
/** ID */
private Long id;
/** 名称 */
private String name;
/** 围栏坐标地址 */
private String address;
/** 关联套餐 */
private String ruleName;
/** 部门状态0正常 1停用 */
private String status;
/** 删除标志0代表存在 2代表删除 */
private String delFlag;
private String extend1;
private String extend2;
private String extend3;
}

View File

@ -0,0 +1,8 @@
package com.sczx.sync.service;
import com.sczx.sync.dto.DeviceData;
public interface ElectronicFenceService {
void checkCarPositionInFence(DeviceData data);
}

View File

@ -0,0 +1,155 @@
package com.sczx.sync.service.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.sczx.sync.dto.DeviceData;
import com.sczx.sync.mapper.ElectronicFenceMapper;
import com.sczx.sync.service.ElectronicFenceService;
import com.sczx.sync.utils.Point;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
import com.sczx.sync.utils.RedisUtil;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@Component
public class ElectronicFenceServiceImpl implements ElectronicFenceService {
@Autowired
private RedisUtil redisUtils;
@Autowired
private ElectronicFenceMapper ElectronicFenceMapper;
@Override
public void checkCarPositionInFence(DeviceData deviceLocation) {
String RedisKeyConstants = "sczxOrder:"+"eFence:";
String clientId = deviceLocation.getClientId();
// 获取电子围栏id
String redisEfenceKey = RedisKeyConstants + clientId;
if (redisUtils.get(redisEfenceKey) == null) {
log.info("未找到车辆对应的电子围栏ID" + clientId);
return;
}
String fenceId = redisUtils.get(redisEfenceKey);
// 查询电子围栏
String electronicFenceString = ElectronicFenceMapper.selectZcElectronicFenceById(Long.parseLong(fenceId));
if (electronicFenceString == null) {
return ;
}
try {
// 解析电子围栏坐标点(转换后的wgs84坐标)
JSONArray fencePoints = JSONArray.parseArray(electronicFenceString);
List<Point> polygon = new ArrayList<>();
for (int i = 0; i < fencePoints.size(); i++) {
JSONObject pointObj = fencePoints.getJSONObject(i);
Point point = new Point(pointObj.getDouble("lng"), pointObj.getDouble("lat"));
polygon.add(point);
}
// 判断点是否在多边形内
Point testPoint = new Point(deviceLocation.getLng(), deviceLocation.getLat());
boolean currentlyInFence = isPointInPolygon(testPoint, polygon);
// 出圈时 fuelStatus=油路正常, 就下断电指令; 回到圈内时 fuelStatus=油路断开,就上电
String fuleStatus = deviceLocation.getFuelStatus();
// 状态发生变化时执行相应操作
if (currentlyInFence && "油路断开".equals(fuleStatus)) {
// 进入围栏 - 发出放电指令
sendPowerOnCommand(clientId);
} else if (!currentlyInFence && "油路正常".equals(fuleStatus)) {
// 超出围栏 - 发出断电指令
sendPowerOffCommand(clientId);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 判断点是否在多边形内(射线法)
*
* @param point 测试点
* @param polygon 多边形顶点列表
* @return true-在多边形内false-在多边形外
*/
private boolean isPointInPolygon(Point point, List<Point> polygon) {
int intersectCount = 0;
for (int i = 0; i < polygon.size(); i++) {
Point p1 = polygon.get(i);
Point p2 = polygon.get((i + 1) % polygon.size());
// 检查水平射线是否与边相交
if (rayIntersectsSegment(point, p1, p2)) {
intersectCount++;
}
}
// 奇数个交点表示在多边形内部
return (intersectCount % 2) == 1;
}
/**
* 判断射线是否与线段相交
*
* @param point 射线起点
* @param p1 线段端点1
* @param p2 线段端点2
* @return true-相交false-不相交
*/
private boolean rayIntersectsSegment(Point point, Point p1, Point p2) {
// 确保p1的y坐标小于等于p2的y坐标
if (p1.y > p2.y) {
Point temp = p1;
p1 = p2;
p2 = temp;
}
// 如果点在线段两端点y坐标范围之外则不相交
if (point.y < p1.y || point.y > p2.y) {
return false;
}
// 水平线段特殊处理
if (p1.y == p2.y) {
return (point.x >= Math.min(p1.x, p2.x) && point.x <= Math.max(p1.x, p2.x));
}
// 计算交点x坐标
double xIntersection = (point.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;
// 判断交点是否在射线上
return point.x <= xIntersection;
}
/**
* 发送通电指令
*
* @param clientId 车辆识别号
*/
private void sendPowerOnCommand(String clientId) {
// 实际实现发送通电指令到设备
log.info("发送通电指令给车辆: " + clientId);
}
/**
* 发送断电指令
*
* @param clientId 车辆识别号
*/
private void sendPowerOffCommand(String clientId) {
// 实际实现发送断电指令到设备
log.info("发送断电指令给车辆: " + clientId);
}
}

View File

@ -0,0 +1,12 @@
package com.sczx.sync.utils;
public class Point {
public double x, y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
}

View File

@ -0,0 +1,74 @@
package com.sczx.sync.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @Author: 张黎
* @Date: 2025/07/06/14:21
* @Description:
*/
@Slf4j
@Component
public class RedisUtil {
@Autowired
private StringRedisTemplate redisTemplate;
public void set(String key, String value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
public String get(String key) {
return redisTemplate.opsForValue().get(key);
}
public void delete(String key) {
redisTemplate.delete(key);
}
private static final Long DEFAULT_EXPIRE = 120L;
public boolean getRedisLock(String key, String lockName) {
log.info("获取锁 - {}", key);
return getRedisLockWithTimeout(key, lockName, 120L);
}
/**
* 获取redislock
* 加锁并设置过期时间
*
* @param key
* @param lockName
* @return
*/
public boolean getRedisLockWithTimeout(String key, String lockName, Long lockExpire) {
boolean lock = false;
if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, lockName))) {
redisTemplate.expire(key, lockExpire, TimeUnit.SECONDS);
return true;
}
if (redisTemplate.getExpire(key) == -1) {
//保证锁一定设置过期时间
redisTemplate.expire(key, DEFAULT_EXPIRE, TimeUnit.SECONDS);
}
log.info("未获取到锁:" + lock + ", lockName={},key={}", lockName, key);
return lock;
}
public void deleteRedisLock(String key) {
log.info("释放锁 - {}", key);
redisTemplate.delete(key);
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sczx.sync.mapper.ElectronicFenceMapper">
<select id="selectZcElectronicFenceById" parameterType="Long" resultType="java.lang.String">
select a.extend1
from zc_electronic_fence a
left join zc_electronic_fence_rule b on b.electronic_fence_id = a.id
where a.id = #{id}
</select>
</mapper>