Compare commits

...

16 Commits

Author SHA1 Message Date
1d11bb7d67 no message 2025-10-23 16:23:22 +08:00
5be5de0147 ... 2025-10-23 16:10:01 +08:00
2cf9e74140 sendd 2025-10-17 16:43:06 +08:00
40a8f81aed add send 2025-10-17 15:44:01 +08:00
618d0d4f0a 下发指令 2025-10-17 15:34:40 +08:00
943aa3e2d0 补充pom 2025-10-16 03:15:50 +08:00
fc5a120d48 在项目内处理电子围栏逻辑 2025-10-16 01:30:02 +08:00
454ae20370 车辆数据推送-暂 2025-10-15 00:21:12 +08:00
f4c9861b4e 改为日志输出 2025-10-14 23:39:40 +08:00
5257539727 kafka消费车辆实时数据 2025-10-14 22:18:20 +08:00
78be6bad44 wait 2025-09-30 16:28:41 +08:00
cb03127593 no message 2025-09-30 15:55:06 +08:00
5b613e3c97 no message 2025-09-30 14:53:03 +08:00
d0e3b9c721 1 2025-09-30 14:24:26 +08:00
f7ef49c5ba no message 2025-09-29 17:13:16 +08:00
32cc5d17a4 更改还车时间取值 2025-09-29 14:14:52 +08:00
23 changed files with 1127 additions and 95 deletions

20
pom.xml
View File

@ -13,7 +13,7 @@
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
<spring-boot.version>2.7.18</spring-boot.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.9.RELEASE</spring-cloud-alibaba.version>
</properties>
@ -179,6 +179,24 @@
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
<!-- kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
</dependencies>
<!-- Build Configuration -->

View File

@ -7,7 +7,6 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@ -25,7 +24,5 @@ public class Application {
public static void main(String[] args) throws IOException {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
Environment environment = context.getBean(Environment.class);
log.info("启动成功后端服务API地址http://{}:{}/swagger-ui.html", ComputerInfo.getIpAddr(), environment.getProperty("server.port"));
}
}

View File

@ -1,38 +1,38 @@
package com.sczx.sync.Task;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.sczx.sync.mapper.DataReceiveRecordMapper;
import com.sczx.sync.mapper.OrderBatteryInfoMapper;
import com.sczx.sync.po.DataReceivePo;
import com.sczx.sync.service.impl.SendDataServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
// 定时任务类
@Component
public class DataResendTask {
@Autowired
private SendDataServiceImpl sendDataService;
@Autowired
private DataReceiveRecordMapper dataReceiveRecordMapper;
@Autowired
private OrderBatteryInfoMapper orderBatteryInfoMapper;
@Scheduled(fixedRate = 15000)
public void resendFailedData() {
// 查询所有发送失败的记录
List<DataReceivePo> failedRecords = dataReceiveRecordMapper.selectList(new QueryWrapper<DataReceivePo>().eq("status", 3).eq("data_type", "batteryorder"));
for (DataReceivePo record : failedRecords) {
String status = orderBatteryInfoMapper.selectOrderStatus(record.getCid());
if (status.equals("RENT_ING")) {
sendDataService.retryForward(record.getId());
}
}
}
}
//package com.sczx.sync.Task;
//
//import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
//import com.sczx.sync.mapper.DataReceiveRecordMapper;
//import com.sczx.sync.mapper.OrderBatteryInfoMapper;
//import com.sczx.sync.po.DataReceivePo;
//import com.sczx.sync.service.impl.SendDataServiceImpl;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.scheduling.annotation.Scheduled;
//import org.springframework.stereotype.Component;
//
//import java.util.List;
//
//// 定时任务类
//@Component
//public class DataResendTask {
//
// @Autowired
// private SendDataServiceImpl sendDataService;
//
// @Autowired
// private DataReceiveRecordMapper dataReceiveRecordMapper;
//
// @Autowired
// private OrderBatteryInfoMapper orderBatteryInfoMapper;
//
// @Scheduled(fixedRate = 15000)
// public void resendFailedData() {
// // 查询所有发送失败的记录
// List<DataReceivePo> failedRecords = dataReceiveRecordMapper.selectList(new QueryWrapper<DataReceivePo>().eq("status", 3).eq("data_type", "batteryorder"));
// for (DataReceivePo record : failedRecords) {
// String status = orderBatteryInfoMapper.selectOrderStatus(record.getCid());
// if (status.equals("RENT_ING")) {
// sendDataService.retryForward(record.getId());
// }
// }
// }
//}

View File

@ -0,0 +1,89 @@
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;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class KafkaConsumer {
@Autowired
private ThreadPoolConfig threadPoolConfig;
@Autowired
private ElectronicFenceService electronicFenceService;
@Value("${dataPush.deviceDataUrl}")
private String deviceDataUrl;
@KafkaListener(topics = "jt808_forward_prod")
public void listen(String message) {
// 将消息处理任务提交到线程池
threadPoolConfig.kafkaMessageExecutor().execute(() -> {
processMessage(message);
});
}
private void processMessage(String message) {
// 实际的消息处理逻辑
log.info("Received message: " + message);
try {
ObjectMapper objectMapper = new ObjectMapper();
DeviceData data = objectMapper.readValue(message, DeviceData.class);
// 检查时间戳是否在1分钟以内
LocalDateTime messageTime = data.getTimestamp();
LocalDateTime currentTime = LocalDateTime.now();
long minutesDiff = java.time.Duration.between(messageTime, currentTime).toMinutes();
if (minutesDiff > 1) {
// 消息超过1分钟跳过处理
log.info("消息超过1分钟跳过处理 " + data.getTs());
return;
}
// 处理1分钟以内的消息
log.info("处理1分钟以内的消息: " + data.getClientId() + " at " + data.getTs());
// 调用接口处理逻辑
//callExternalApi(data);
electronicFenceService.checkCarPositionInFence( data);
} catch (Exception e) {
log.error("Failed to process message: " + message);
e.printStackTrace();
}
}
private void callExternalApi(DeviceData data) {
try {
// 构造请求参数
Map<String, Object> requestData = new HashMap<>();
requestData.put("clientId", data.getClientId());
requestData.put("latitude", data.getLat());
requestData.put("longitude", data.getLng());
requestData.put("fuelStatus", data.getFuelStatus());
// 发送HTTP请求调用添加接口
RestTemplate restTemplate = new RestTemplate();
restTemplate.postForObject(deviceDataUrl, requestData, String.class);
} catch (Exception e) {
log.error("Failed to call external API for device: " + data.getClientId());
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,102 @@
package com.sczx.sync.config;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.config.SaslConfigs;
import org.apache.kafka.common.config.SslConfigs;
import org.apache.kafka.common.security.auth.SecurityProtocol;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
@Configuration
@EnableKafka
public class KafkaConsumerConfig {
@Value("${kafka.bootstrap-servers}")
private String bootstrapServers;
@Value("${kafka.consumer.group-id}")
private String groupId;
@Value("${kafka.properties.sasl.jaas.config}")
private String kafkaPropertiesConfig;
@Bean
public DefaultKafkaConsumerFactory<String, String> consumerFactory() throws IOException {
Map<String, Object> props = new HashMap<>();
// 设置接入点
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
// 设置SSL信任库
InputStream truststoreStream = KafkaConsumerConfig.class.getClassLoader().getResourceAsStream("mix.4096.client.truststore.jks");
if (truststoreStream != null) {
Path tempFile = Files.createTempFile("truststore", ".jks");
Files.copy(truststoreStream, tempFile, StandardCopyOption.REPLACE_EXISTING);
props.put("ssl.truststore.location", tempFile.toString());
} else {
System.err.println("Truststore not found in classpath");
}
props.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, "KafkaOnsClient");
props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
// SASL 配置
String saslMechanism = "PLAIN";
props.put(SaslConfigs.SASL_MECHANISM, saslMechanism);
props.put("sasl.jaas.config",kafkaPropertiesConfig);
props.put(SaslConfigs.SASL_MECHANISM, saslMechanism);
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 30000);
props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, 32000);
props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, 32000);
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 30);
// 明确指定反序列化器
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
props.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
// 修复点:显式指定泛型类型
return new DefaultKafkaConsumerFactory<>(props);
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();
try {
factory.setConsumerFactory(consumerFactory());
} catch (IOException e) {
throw new RuntimeException("Failed to create Kafka consumer factory", e);
}
return factory;
}
}

View File

@ -0,0 +1,23 @@
package com.sczx.sync.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class ThreadPoolConfig {
public Executor kafkaMessageExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(70);
executor.setMaxPoolSize(70);
executor.setQueueCapacity(700);
executor.setThreadNamePrefix("kafka-message-processor-");
executor.setKeepAliveSeconds(60);
executor.initialize();
return executor;
}
}

View File

@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.sczx.sync.common.Result;
import com.sczx.sync.dto.DataRceiveReq;
import com.sczx.sync.dto.meituan;
import com.sczx.sync.mapper.CompanyStoreMapper;
import com.sczx.sync.service.SendDataService;
import io.swagger.annotations.Api;
@ -82,6 +83,13 @@ public class SendDataController {
// return sendDataService.sendSubOrderToBattery(Long.parseLong(id));
// }
@ApiOperation(value = "接收充电同步数据接口")
@GetMapping("/OrderMeal/{id}")
public Map<String,String> sendUserMeal(@PathVariable String id){
log.info("/OrderMeal 接收用户订单套餐数据:{}",id);
return sendDataService.sendUserMeal(Long.parseLong(id));
}
@ApiOperation(value = "异步接收充电同步数据接口")
@GetMapping("/subOrder/{id}")
@ -89,7 +97,7 @@ public class SendDataController {
log.info("/subOrder 接收充电数据:{}",id);
return CompletableFuture
.supplyAsync(() -> {
try {sleep(1000);
try {sleep(5000);
return sendDataService.sendSubOrderToBattery(Long.parseLong(id));
} catch (Exception e) {
log.error("处理用户数据时发生异常", e);
@ -112,6 +120,17 @@ public class SendDataController {
});
}
@ApiOperation(value = "接收需同步数据接口")
@GetMapping("/authorization")
public Result authorization(@RequestParam("code") String code,@RequestParam("sign") String sign,@RequestParam("developerId") Long developerId,@RequestParam("businessId") int businessId,
@RequestParam("state") String state){
log.info("接收美团授权数据 - code: {}, sign: {}, developerId: {}, businessId: {}, state: {}",
code, sign, developerId, businessId, state);
return null;
}
//

View File

@ -0,0 +1,12 @@
package com.sczx.sync.dto;
import lombok.Data;
@Data
public class CommandDTO {
private String clientId;
private Integer command;
private Integer providerId;
}

View File

@ -0,0 +1,259 @@
package com.sczx.sync.dto;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DeviceData {
private String longitudeDirection;
private String armStatus;
private int altitude;
private String beidouUsed;
private int speed;
private String locationStatus;
private int providerId;
private String chargingStatus;
private String hallStatus;
private double lat;
private String deviceWorkStatus;
private int direction;
private int statusBit;
private int warnBit;
private String clientId;
private double lng;
private String alarms;
private String accStatus;
private String galileoUsed;
private String movingStatus;
private String latitudeDirection;
private String softAccStatus;
private String glonassUsed;
private String gpsUsed;
private String fuelStatus;
private String fakePowerStatus;
private String ts;
// 时间格式化器
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 获取 LocalDateTime 对象
public LocalDateTime getTimestamp() {
return LocalDateTime.parse(this.ts, FORMATTER);
}
public String getLongitudeDirection() {
return longitudeDirection;
}
public void setLongitudeDirection(String longitudeDirection) {
this.longitudeDirection = longitudeDirection;
}
public String getArmStatus() {
return armStatus;
}
public void setArmStatus(String armStatus) {
this.armStatus = armStatus;
}
public int getAltitude() {
return altitude;
}
public void setAltitude(int altitude) {
this.altitude = altitude;
}
public String getBeidouUsed() {
return beidouUsed;
}
public void setBeidouUsed(String beidouUsed) {
this.beidouUsed = beidouUsed;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public String getLocationStatus() {
return locationStatus;
}
public void setLocationStatus(String locationStatus) {
this.locationStatus = locationStatus;
}
public int getProviderId() {
return providerId;
}
public void setProviderId(int providerId) {
this.providerId = providerId;
}
public String getChargingStatus() {
return chargingStatus;
}
public void setChargingStatus(String chargingStatus) {
this.chargingStatus = chargingStatus;
}
public String getHallStatus() {
return hallStatus;
}
public void setHallStatus(String hallStatus) {
this.hallStatus = hallStatus;
}
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
public String getDeviceWorkStatus() {
return deviceWorkStatus;
}
public void setDeviceWorkStatus(String deviceWorkStatus) {
this.deviceWorkStatus = deviceWorkStatus;
}
public int getDirection() {
return direction;
}
public void setDirection(int direction) {
this.direction = direction;
}
public int getStatusBit() {
return statusBit;
}
public void setStatusBit(int statusBit) {
this.statusBit = statusBit;
}
public int getWarnBit() {
return warnBit;
}
public void setWarnBit(int warnBit) {
this.warnBit = warnBit;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public double getLng() {
return lng;
}
public void setLng(double lng) {
this.lng = lng;
}
public String getAlarms() {
return alarms;
}
public void setAlarms(String alarms) {
this.alarms = alarms;
}
public String getAccStatus() {
return accStatus;
}
public void setAccStatus(String accStatus) {
this.accStatus = accStatus;
}
public String getGalileoUsed() {
return galileoUsed;
}
public void setGalileoUsed(String galileoUsed) {
this.galileoUsed = galileoUsed;
}
public String getMovingStatus() {
return movingStatus;
}
public void setMovingStatus(String movingStatus) {
this.movingStatus = movingStatus;
}
public String getLatitudeDirection() {
return latitudeDirection;
}
public void setLatitudeDirection(String latitudeDirection) {
this.latitudeDirection = latitudeDirection;
}
public String getSoftAccStatus() {
return softAccStatus;
}
public void setSoftAccStatus(String softAccStatus) {
this.softAccStatus = softAccStatus;
}
public String getGlonassUsed() {
return glonassUsed;
}
public void setGlonassUsed(String glonassUsed) {
this.glonassUsed = glonassUsed;
}
public String getGpsUsed() {
return gpsUsed;
}
public void setGpsUsed(String gpsUsed) {
this.gpsUsed = gpsUsed;
}
public String getFuelStatus() {
return fuelStatus;
}
public void setFuelStatus(String fuelStatus) {
this.fuelStatus = fuelStatus;
}
public String getFakePowerStatus() {
return fakePowerStatus;
}
public void setFakePowerStatus(String fakePowerStatus) {
this.fakePowerStatus = fakePowerStatus;
}
public String getTs() {
return ts;
}
public void setTs(String ts) {
this.ts = ts;
}
}

View File

@ -0,0 +1,26 @@
package com.sczx.sync.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Map;
@Data
@ApiModel
public class meituan {
@ApiModelProperty("传入数据类型")
private String code;
private String sign;
@ApiModelProperty("租车平台对应表主键ID")
private Long developerId;
private int businessId;
@ApiModelProperty("传入数据Map")
private String state;
}

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);
String selectIotBrandById(String id);
}

View File

@ -21,4 +21,6 @@ public interface OrderBatteryInfoMapper extends BaseMapper<OrderBatteryInfo> {
OrderBatteryInfo selectOrderBatteryInfoById(@Param("id") Long id);
String selectOrderStatus(@Param("suborderId") Long suborderId);
OrderBatteryInfo selectUserBatteryMealById(@Param("id") 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

@ -21,6 +21,8 @@ public interface SendDataService {
Map<String, String> sendStoreInfoToBattery(Long id);
Map<String,String> sendSubOrderToBattery(Long id);
Map<String,String> sendUserMeal(Long id);
/**
* 异步转发数据到第三方平台
*

View File

@ -0,0 +1,257 @@
package com.sczx.sync.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.sczx.sync.dto.CommandDTO;
import com.sczx.sync.dto.DeviceData;
import com.sczx.sync.mapper.ElectronicFenceMapper;
import com.sczx.sync.service.ElectronicFenceService;
import com.sczx.sync.service.ThirdPartyForwardService;
import com.sczx.sync.utils.Point;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.sczx.sync.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Value;
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;
@Value("${iot-url}")
private String URL;
@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));
String iotBrands = electronicFenceMapper.selectIotBrandById(clientId);
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();
log.info("fuleStatus" + fuleStatus);
// 状态发生变化时执行相应操作
if (currentlyInFence && "油路断开".equals(fuleStatus)) {
// 进入围栏 - 发出放电指令
log.info("发送通电指令给车辆");
sendPowerOnCommand(clientId,iotBrands);
} else if (!currentlyInFence && "油路正常".equals(fuleStatus)) {
// 超出围栏 - 发出断电指令
log.info("发送断电指令给车辆");
sendPowerOffCommand(clientId,iotBrands);
}
} 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;
}
/**
* 发送通电指令
*指令: 1-设备复位, 2-断油电, 3-恢复油电, 4-立即定位
* @param clientId 车辆识别号
*/
private void sendPowerOnCommand(String clientId,String iotBrands) {
// 实际实现发送通电指令到设备
CommandDTO commandDTO = new CommandDTO();
commandDTO.setClientId(clientId);
commandDTO.setCommand(3);
commandDTO.setProviderId(Integer.parseInt(iotBrands));
//String response = "";
String response = forwardData(URL+"/device/8304", JSON.toJSONString(commandDTO));
log.info("发送通电指令给车辆: {} , 响应: {}" + clientId ,response);
}
/**
* 发送断电指令
*指令: 1-设备复位, 2-断油电, 3-恢复油电, 4-立即定位
* @param clientId 车辆识别号
*/
private void sendPowerOffCommand(String clientId,String iotBrands) {
// 实际实现发送断电指令到设备
CommandDTO commandDTO = new CommandDTO();
commandDTO.setClientId(clientId);
commandDTO.setCommand(2);
commandDTO.setProviderId(Integer.parseInt(iotBrands));
//String response = "";
String response = forwardData(URL+"/device/8304", JSON.toJSONString(commandDTO));
log.info("发送断电指令给车辆: {} , 响应: {}" + clientId ,response);
}
private String forwardData(String url,String data) {
CloseableHttpClient httpClient = null;
CloseableHttpResponse response = null;
try {
// 创建HTTP客户端
httpClient = HttpClients.createDefault();
// 创建POST请求
HttpPost httpPost = new HttpPost(url);
// 设置请求头
httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
httpPost.setHeader("User-Agent", "SczxSync/1.0");
StringEntity entity = new StringEntity(data, StandardCharsets.UTF_8);
httpPost.setEntity(entity);
// 设置超时配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(30000)
.setSocketTimeout(30000)
.build();
httpPost.setConfig(requestConfig);
log.info("开始转发数据到第三方平台: {}, 数据: {}", url, data);
// 执行请求
response = httpClient.execute(httpPost);
HttpEntity responseEntity = response.getEntity();
String responseBody = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("数据转发成功,响应: {}", responseBody);
return responseBody;
} else {
log.error("数据转发失败,状态码: {}, 响应: {}", statusCode, responseBody);
throw new RuntimeException("第三方平台响应异常,状态码: " + statusCode);
}
} catch (Exception e) {
log.error("数据转发异常", e);
throw new RuntimeException("数据转发异常: " + e.getMessage(), e);
} finally {
// 关闭资源
try {
if (response != null) {
response.close();
}
if (httpClient != null) {
httpClient.close();
}
} catch (Exception e) {
log.error("关闭HTTP客户端异常", e);
}
}
}
}

View File

@ -268,62 +268,104 @@ public class SendDataServiceImpl implements SendDataService {
OrderBatteryInfo orderBatteryInfo = orderBatteryInfoMapper.selectOrderBatteryInfoById(id);
Map<String, String> map = new HashMap<>();
if(orderBatteryInfo == null){
map.put("msg","未找到此租电订单信息");
map.put("code","500");
return map;
}
if (orderBatteryInfo.getPhone() == null || orderBatteryInfo.getPhone().isEmpty()){
map.put("msg","租电订单手机号不能为空");
map.put("code","500");
return map;
}
if (orderBatteryInfo.getStart_date() == null || orderBatteryInfo.getStart_date().isEmpty()){
map.put("msg","租电订单开始时间不能为空");
map.put("code","500");
return map;
}
if(orderBatteryInfo.getEnd_date() == null || orderBatteryInfo.getEnd_date().isEmpty()){
map.put("msg","租电订单结束时间不能为空");
map.put("code","500");
return map;
}
if (orderBatteryInfo.getCity() == null ) {
map.put("msg","租电订单城市不能为空");
map.put("code","500");
return map;
}
if (orderBatteryInfo.getStore() == null){
map.put("msg","租电订单门店不能为空");
map.put("code","500");
return map;
}
if (orderBatteryInfo.getCategory() == null){
map.put("msg","电池类别不能为空");
map.put("code","500");
return map;
}
if (orderBatteryInfo.getOperator_id() == null){
map.put("msg","运营商ID不能为空");
map.put("code","500");
return map;
}
if (orderBatteryInfo.getOrder_no() == null){
map.put("msg","租电订单编号不能为空");
map.put("code","500");
return map;
}
// if(orderBatteryInfo == null){
// map.put("msg","未找到此租电订单信息");
// map.put("code","500");
// return map;
// }
// if (orderBatteryInfo.getPhone() == null || orderBatteryInfo.getPhone().isEmpty()){
// map.put("msg","租电订单手机号不能为空");
// map.put("code","500");
// return map;
// }
// if (orderBatteryInfo.getStart_date() == null || orderBatteryInfo.getStart_date().isEmpty()){
// map.put("msg","租电订单开始时间不能为空");
// map.put("code","500");
// return map;
// }
// if(orderBatteryInfo.getEnd_date() == null || orderBatteryInfo.getEnd_date().isEmpty()){
// map.put("msg","租电订单结束时间不能为空");
// map.put("code","500");
// return map;
// }
// if (orderBatteryInfo.getCity() == null ) {
// map.put("msg","租电订单城市不能为空");
// map.put("code","500");
// return map;
// }
// if (orderBatteryInfo.getStore() == null){
// map.put("msg","租电订单门店不能为空");
// map.put("code","500");
// return map;
// }
// if (orderBatteryInfo.getCategory() == null){
// map.put("msg","电池类别不能为空");
// map.put("code","500");
// return map;
// }
// if (orderBatteryInfo.getOperator_id() == null){
// map.put("msg","运营商ID不能为空");
// map.put("code","500");
// return map;
// }
// if (orderBatteryInfo.getOrder_no() == null){
// map.put("msg","租电订单编号不能为空");
// map.put("code","500");
// return map;
// }
SyncRequest syncRequest = new SyncRequest();
syncRequest.setData(orderBatteryInfo);
if (saveRecord(syncRequest,URL+"/batteryorder",id,"batteryorder")){
String response = thirdPartyForwardService.forwardData(URL+"/batteryorder",JSON.toJSONString(orderBatteryInfo));
JSONObject jsonObject = JSON.parseObject(response);
String status = jsonObject.get("status").toString();
if (status.equals("40001") || status.equals("40002") || status.equals("40003") || status.equals("40005") || status.equals("40006")){
map.put("code",status);
map.put("msg",jsonObject.get("msg").toString());
return map;
}else if (status.equals("200")){
String bOrdNo = jsonObject.get("order_no").toString();
companyStoreMapper.updateOdId(bOrdNo,id);
map.put("code",status);
map.put("msg","发送成功");
map.put("code","200");
return map;
}else if (status.equals("401")){
map.put("code",status);
map.put("msg","鉴权失败");
return map;
}
else {
map.put("msg","发送失败");
map.put("code","500");
map.put("code","500");
map.put("msg","系统异常");
return map;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String,String> sendUserMeal(Long id) {
OrderBatteryInfo orderBatteryInfo = orderBatteryInfoMapper.selectUserBatteryMealById(id);
Map<String, String> map = new HashMap<>();
String response = thirdPartyForwardService.forwardData(URL+"/check_meal",JSON.toJSONString(orderBatteryInfo));
JSONObject jsonObject = JSON.parseObject(response);
String status = jsonObject.get("status").toString();
if (status.equals("40001") || status.equals("40002") || status.equals("40003") || status.equals("40005") || status.equals("40006")){
map.put("code",status);
map.put("msg",jsonObject.get("msg").toString());
return map;
}else if (status.equals("200")){
map.put("code",status);
map.put("msg","发送成功");
return map;
}else if (status.equals("401")){
map.put("code",status);
map.put("msg","鉴权失败");
return map;
}
map.put("code","500");
map.put("msg","系统异常");
return map;
}

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

@ -1,4 +1,7 @@
spring:
application:
name: sczx-sync # 微服务名称
name: sczx-sync # 微服务名称
dataPush:
deviceDataUrl: http://127.0.0.1:8098/sczxWeb/fuleStatusApi/device-data

View File

@ -0,0 +1,17 @@
<?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>
<select id="selectIotBrandById" parameterType="String" resultType="java.lang.String">
select lot_brand from zc_car where lot_number = #{id}
</select>
</mapper>

View File

@ -22,7 +22,7 @@
SELECT
zom.customer_phone AS phone,
zrb.meal_id AS meal_id,
zom.pick_car_time AS start_date,
zom.start_rent_time AS start_date,
zom.end_rent_time AS end_date,
zcs.city_id as city,
zcs.id as store,
@ -46,4 +46,23 @@
<select id="selectOrderStatus" parameterType="java.lang.Long" resultType="java.lang.String">
select m.order_status from zc_order_main m,zc_order_sub s where s.order_id = m.order_id and s.suborder_id = #{suborderId}
</select>
<select id="selectUserBatteryMealById" resultMap="OrderBatteryInfoMap">
SELECT
zom.customer_phone AS phone,
zrb.meal_id AS meal_id
FROM
zc_order_main AS zom,
zc_order_sub AS zos,
zc_rent_battey_rule AS zrb,
zc_company_store AS zcs
WHERE
zom.rent_battey_rule_id = zrb.id
AND zom.order_id = zos.order_id
AND suborder_type = 'RENTBATTEY'
and zom.store_id = zcs.id
and zos.suborder_id = #{id}
</select>
</mapper>

Binary file not shown.