diff --git a/pom.xml b/pom.xml index 8944a80..a8b5bf2 100644 --- a/pom.xml +++ b/pom.xml @@ -180,6 +180,11 @@ 1.2.83 + + + org.springframework.boot + spring-boot-starter-data-redis + diff --git a/src/main/java/com/sczx/sync/config/KafkaConsumer.java b/src/main/java/com/sczx/sync/config/KafkaConsumer.java index 196527e..aaa338f 100644 --- a/src/main/java/com/sczx/sync/config/KafkaConsumer.java +++ b/src/main/java/com/sczx/sync/config/KafkaConsumer.java @@ -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(); diff --git a/src/main/java/com/sczx/sync/mapper/ElectronicFenceMapper.java b/src/main/java/com/sczx/sync/mapper/ElectronicFenceMapper.java new file mode 100644 index 0000000..9465eba --- /dev/null +++ b/src/main/java/com/sczx/sync/mapper/ElectronicFenceMapper.java @@ -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 { + + String selectZcElectronicFenceById(Long id); + + +} diff --git a/src/main/java/com/sczx/sync/po/EfenceInfo.java b/src/main/java/com/sczx/sync/po/EfenceInfo.java new file mode 100644 index 0000000..619e81e --- /dev/null +++ b/src/main/java/com/sczx/sync/po/EfenceInfo.java @@ -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; +} diff --git a/src/main/java/com/sczx/sync/service/ElectronicFenceService.java b/src/main/java/com/sczx/sync/service/ElectronicFenceService.java new file mode 100644 index 0000000..49e9c91 --- /dev/null +++ b/src/main/java/com/sczx/sync/service/ElectronicFenceService.java @@ -0,0 +1,8 @@ +package com.sczx.sync.service; + +import com.sczx.sync.dto.DeviceData; + +public interface ElectronicFenceService { + + void checkCarPositionInFence(DeviceData data); +} diff --git a/src/main/java/com/sczx/sync/service/impl/ElectronicFenceServiceImpl.java b/src/main/java/com/sczx/sync/service/impl/ElectronicFenceServiceImpl.java new file mode 100644 index 0000000..c5b972d --- /dev/null +++ b/src/main/java/com/sczx/sync/service/impl/ElectronicFenceServiceImpl.java @@ -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 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 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); + } +} diff --git a/src/main/java/com/sczx/sync/utils/Point.java b/src/main/java/com/sczx/sync/utils/Point.java new file mode 100644 index 0000000..e240d6d --- /dev/null +++ b/src/main/java/com/sczx/sync/utils/Point.java @@ -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; + } + +} diff --git a/src/main/java/com/sczx/sync/utils/RedisUtil.java b/src/main/java/com/sczx/sync/utils/RedisUtil.java new file mode 100644 index 0000000..1b64543 --- /dev/null +++ b/src/main/java/com/sczx/sync/utils/RedisUtil.java @@ -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); + } + +} diff --git a/src/main/resources/mapper/ElectronicFenceMapper.xml b/src/main/resources/mapper/ElectronicFenceMapper.xml new file mode 100644 index 0000000..6b8c945 --- /dev/null +++ b/src/main/resources/mapper/ElectronicFenceMapper.xml @@ -0,0 +1,13 @@ + + + + + + + +