From 3ce9757effca99095cb2c964afa9932460ff931f Mon Sep 17 00:00:00 2001 From: xuqiuyun <1113560936@qq.com> Date: Thu, 11 Dec 2025 17:32:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90=E8=BD=A8?= =?UTF-8?q?=E8=BF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 +- pc-cattle-transportation/src/api/abroad.js | 6 +- .../earlywarning/warningDetailDialog.vue | 54 +- .../src/views/shipping/loadingOrder.vue | 87 +- tradeCattle/aiotagro-cattle-trade/pom.xml | 7 + .../controller/DeliveryController.java | 41 +- .../dto/openapi/OpenApiTrackPoint.java | 45 + .../dto/openapi/OpenApiTrackResponse.java | 37 + .../dto/yingyan/YingyanStayPoint.java | 30 - .../dto/yingyan/YingyanTrackPoint.java | 30 - .../cattletrade/business/entity/Delivery.java | 15 +- .../business/mapper/JbqClientLogMapper.java | 2 +- .../business/mapper/JbqServerLogMapper.java | 2 +- .../business/mapper/XqClientLogMapper.java | 2 +- .../business/service/BaiduYingyanService.java | 829 ------------------ .../service/DeliveryYingyanSyncService.java | 418 --------- .../business/service/IDeliveryService.java | 7 +- .../service/IotDeviceLogSyncService.java | 5 +- .../business/service/OpenApiTrackService.java | 333 +++++++ .../service/impl/DeliveryServiceImpl.java | 300 ++----- .../cattletrade/job/BaiduYingyanSyncJob.java | 34 - .../properties/OpenApiProperties.java | 54 ++ .../core/constant/BaiduYingyanConstants.java | 28 - .../src/main/resources/api/.classpath | 6 + .../src/main/resources/api/.project | 23 + .../main/resources/api/META-INF/MANIFEST.MF | 2 + .../src/main/resources/api/build.xml | 32 + .../src/main/resources/api/openapi-sdk.iml | 51 ++ .../src/main/resources/api/version.txt | 11 + .../src/main/resources/application.yml | 8 +- 30 files changed, 799 insertions(+), 1703 deletions(-) create mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/openapi/OpenApiTrackPoint.java create mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/openapi/OpenApiTrackResponse.java delete mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/yingyan/YingyanStayPoint.java delete mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/yingyan/YingyanTrackPoint.java delete mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/BaiduYingyanService.java delete mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/DeliveryYingyanSyncService.java create mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/OpenApiTrackService.java delete mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/job/BaiduYingyanSyncJob.java create mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/properties/OpenApiProperties.java delete mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/common/core/constant/BaiduYingyanConstants.java create mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/resources/api/.classpath create mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/resources/api/.project create mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/resources/api/META-INF/MANIFEST.MF create mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/resources/api/build.xml create mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/resources/api/openapi-sdk.iml create mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/resources/api/version.txt diff --git a/.vscode/settings.json b/.vscode/settings.json index 7b016a8..e012065 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "java.compile.nullAnalysis.mode": "automatic" + "java.compile.nullAnalysis.mode": "automatic", + "java.configuration.updateBuildConfiguration": "interactive" } \ No newline at end of file diff --git a/pc-cattle-transportation/src/api/abroad.js b/pc-cattle-transportation/src/api/abroad.js index fd66f32..c5863c4 100644 --- a/pc-cattle-transportation/src/api/abroad.js +++ b/pc-cattle-transportation/src/api/abroad.js @@ -49,10 +49,10 @@ export function inspectionList(data) { }); } -// 查询百度鹰眼轨迹与停留点 -export function getYingyanTrack(data) { +// 查询运单轨迹(基于车牌号,使用 OpenAPI) +export function getDeliveryTrack(data) { return request({ - url: '/delivery/yingyan/track', + url: '/delivery/track', method: 'POST', data, }); diff --git a/pc-cattle-transportation/src/views/earlywarning/warningDetailDialog.vue b/pc-cattle-transportation/src/views/earlywarning/warningDetailDialog.vue index fffea32..1301516 100644 --- a/pc-cattle-transportation/src/views/earlywarning/warningDetailDialog.vue +++ b/pc-cattle-transportation/src/views/earlywarning/warningDetailDialog.vue @@ -369,14 +369,6 @@ -
- - {{ yingyanMeta.entityName }} - {{ formatTimestamp(yingyanMeta.startTime) }} - {{ formatTimestamp(yingyanMeta.endTime) }} - -
-

最新定位

@@ -461,7 +453,7 @@ import { ref, reactive, computed, nextTick, onUnmounted } from 'vue'; import { ElMessage } from 'element-plus'; import { Location, VideoPlay, Refresh, InfoFilled, Connection, DataLine, Loading } from '@element-plus/icons-vue'; import { BMPGL } from '@/utils/loadBmap.js'; -import { pageDeviceList, getCollarLogs, getEarTagLogs, getHostLogs, getYingyanTrack, waybillDetail } from '@/api/abroad.js'; +import { pageDeviceList, getCollarLogs, getEarTagLogs, getHostLogs, getDeliveryTrack, waybillDetail } from '@/api/abroad.js'; const dialogVisible = ref(false); const warningData = reactive({ @@ -510,11 +502,6 @@ const trackBMapGL = ref(null); // 保存 BMapGL 实例,避免重复加载 const stayPoints = ref([]); // 停留点列表 const latestPoint = ref(null); // 最新轨迹点 const segmentStats = ref([]); -const yingyanMeta = reactive({ - entityName: '', - startTime: null, - endTime: null -}); // 计算属性:判断预警类型 const isTemperatureWarning = computed(() => { @@ -939,16 +926,8 @@ const handleTrackClick = async () => { return; } - await loadYingyanTrack(); - - if (trackPath.value.length === 0) { + // TODO: 接入新的轨迹服务 trackLoading.value = false; - return; - } - - // 初始化地图 - await nextTick(); - await initTrackMap(); }; // 获取运送清单运输状态 @@ -981,16 +960,13 @@ const getDeliveryStatus = async () => { } }; -// 加载百度鹰眼轨迹与停留点 +// 加载运单轨迹(基于车牌号,使用 OpenAPI) const loadYingyanTrack = async () => { stayPoints.value = []; trackPath.value = []; latestPoint.value = null; segmentStats.value = []; trackMapShow.value = false; - yingyanMeta.entityName = ''; - yingyanMeta.startTime = null; - yingyanMeta.endTime = null; if (!warningData.deliveryId) { ElMessage.warning('运单ID缺失,无法查询轨迹'); @@ -998,11 +974,10 @@ const loadYingyanTrack = async () => { } try { - const res = await getYingyanTrack({ deliveryId: warningData.deliveryId }); + const res = await getDeliveryTrack({ deliveryId: warningData.deliveryId }); console.info('[TRACK] 后端轨迹接口响应', res); + if (res.code === 200 && res.data) { - segmentStats.value = Array.isArray(res.data.segmentStats) ? res.data.segmentStats : []; - console.info('[TRACK] 分段统计', segmentStats.value); const rawPoints = Array.isArray(res.data.trackPoints) ? res.data.trackPoints : []; trackPath.value = rawPoints .map(item => { @@ -1019,30 +994,18 @@ const loadYingyanTrack = async () => { }) .filter(Boolean); - stayPoints.value = Array.isArray(res.data.stayPoints) ? res.data.stayPoints : []; - yingyanMeta.entityName = res.data.entityName || ''; - yingyanMeta.startTime = res.data.startTime || null; - yingyanMeta.endTime = res.data.endTime || null; - latestPoint.value = parseLatestPoint(res.data.latestPoint); console.info('[TRACK] 轨迹点数量', trackPath.value.length); if (trackPath.value.length > 0) { trackMapShow.value = true; - } else if (latestPoint.value) { - trackPath.value.push({ - lng: latestPoint.value.lng, - lat: latestPoint.value.lat, - locTime: latestPoint.value.locTime || null, - }); - trackMapShow.value = true; } else { - ElMessage.warning('暂无有效轨迹点'); + ElMessage.warning(res.data.message || '暂无有效轨迹点'); } } else { ElMessage.warning(res.msg || '暂无轨迹数据'); } } catch (error) { - console.error('[TRACK] 加载百度鹰眼轨迹失败:', error); + console.error('[TRACK] 加载轨迹失败:', error); ElMessage.error('加载轨迹数据失败'); } }; @@ -1345,9 +1308,6 @@ const handleTrackDialogClose = () => { trackPath.value = []; trackMapShow.value = false; stayPoints.value = []; - yingyanMeta.entityName = ''; - yingyanMeta.startTime = null; - yingyanMeta.endTime = null; }; // 组件卸载时清理 diff --git a/pc-cattle-transportation/src/views/shipping/loadingOrder.vue b/pc-cattle-transportation/src/views/shipping/loadingOrder.vue index ab4e272..9a0b879 100644 --- a/pc-cattle-transportation/src/views/shipping/loadingOrder.vue +++ b/pc-cattle-transportation/src/views/shipping/loadingOrder.vue @@ -142,9 +142,17 @@
- - 轨迹点数:{{ trackPath.length }} - +
+ 轨迹点数:{{ trackPath.length }} + 总里程:{{ trackMileage }} km + 停车次数:{{ trackParkSize }} +
+
+ 停车列表: + + {{ p.name }} ({{ p.lng }}, {{ p.lat }}) + +
@@ -162,7 +170,7 @@ import baseSearch from '@/components/common/searchCustom/index.vue'; import Pagination from '@/components/Pagination/index.vue'; import { orderPageQuery, orderDelete, orderBatchImport } from '@/api/shipping.js'; import { memberListByType, userAdd } from '@/api/userManage.js'; -import { getYingyanTrack, waybillDetail } from '@/api/abroad.js'; +import { getDeliveryTrack, waybillDetail } from '@/api/abroad.js'; import { BMPGL } from '@/utils/loadBmap.js'; import { nextTick } from 'vue'; import OrderDialog from './orderDialog.vue'; @@ -178,6 +186,9 @@ const fileInputRef = ref(); const trackDialogVisible = ref(false); const trackLoading = ref(false); const trackPath = ref([]); +const trackMileage = ref(''); // 总里程 km +const trackParkSize = ref(0); // 停车次数 +const trackParks = ref([]); // 停车列表 const deliveryStatus = ref(null); const trackMapInstance = ref(null); const formItemList = reactive([ @@ -398,34 +409,86 @@ const viewTrack = async (deliveryId, status) => { trackDialogVisible.value = true; trackLoading.value = true; trackPath.value = []; + trackMileage.value = ''; + trackParkSize.value = 0; + trackParks.value = []; deliveryStatus.value = status; try { - const res = await getYingyanTrack({ deliveryId: deliveryId }); + const res = await getDeliveryTrack({ deliveryId: deliveryId }); + console.info('[track] response data:', res?.data); if (res.code === 200 && res.data) { - const rawPoints = Array.isArray(res.data.trackPoints) ? res.data.trackPoints : []; - trackPath.value = rawPoints + const data = res.data; + + // 尝试解析原始响应(兜底 trackArray/parkArray) + let rawTrackFromResp = []; + let rawParkFromResp = []; + if (!Array.isArray(data.trackArray) && data.rawResponse) { + try { + const rawObj = JSON.parse(data.rawResponse); + rawTrackFromResp = Array.isArray(rawObj?.result?.trackArray) ? rawObj.result.trackArray : []; + rawParkFromResp = Array.isArray(rawObj?.result?.parkArray) ? rawObj.result.parkArray : []; + console.info('[track] parsed rawResponse trackArray len:', rawTrackFromResp.length, 'parkArray len:', rawParkFromResp.length); + } catch (e) { + console.warn('解析 rawResponse 失败', e); + } + } + + // 里程/停车 + trackMileage.value = data.mileage || ''; + trackParkSize.value = Number(data.parkSize || 0); + const parkArray = Array.isArray(data.parkArray) ? data.parkArray : rawParkFromResp; + trackParks.value = parkArray + .map((p, idx) => { + const lng = parseFloat(p.lon ?? p.lng ?? p.longitude ?? 0); + const lat = parseFloat(p.lat ?? p.latitude ?? 0); + return { + name: `停车点${idx + 1}`, + lng, + lat + }; + }) + .filter(p => !Number.isNaN(p.lng) && !Number.isNaN(p.lat) && p.lng !== 0 && p.lat !== 0); + + // 轨迹 + const rawTrack = Array.isArray(data.trackArray) + ? data.trackArray + : (Array.isArray(data.trackPoints) ? data.trackPoints : rawTrackFromResp); + console.info('[track] source arrays len -> trackArray:', Array.isArray(data.trackArray) ? data.trackArray.length : 0, + 'trackPoints:', Array.isArray(data.trackPoints) ? data.trackPoints.length : 0, + 'rawTrackFromResp:', rawTrackFromResp.length); + trackPath.value = rawTrack .map(item => { - const lng = parseFloat(item.longitude ?? item.lng ?? 0); - const lat = parseFloat(item.latitude ?? item.lat ?? 0); + let lng = parseFloat(item.lon ?? item.lng ?? item.longitude ?? 0); + let lat = parseFloat(item.lat ?? item.latitude ?? 0); + // 如果疑似放大(>180/90),尝试除以600000 + if (Math.abs(lng) > 180 || Math.abs(lat) > 90) { + lng = lng / 600000; + lat = lat / 600000; + } if (Number.isNaN(lng) || Number.isNaN(lat) || lng === 0 || lat === 0) { return null; } return { lng, lat, - locTime: item.locTime + locTime: item.locTime || item.timestamp || item.utc || item.gtm || '', + speed: item.spd || item.speed || '', + direction: item.agl || item.direction || '', + altitude: item.hgt || item.altitude || '', + mileage: item.mlg || '' }; }) .filter(Boolean); + console.info('[track] normalized trackPath len:', trackPath.value.length, + 'sample:', trackPath.value.slice(0, 3)); if (trackPath.value.length === 0) { - ElMessage.warning('暂无有效轨迹点'); + ElMessage.warning(data.message || '暂无有效轨迹点'); trackLoading.value = false; return; } - // 初始化地图 await nextTick(); await initTrackMap(); } else { diff --git a/tradeCattle/aiotagro-cattle-trade/pom.xml b/tradeCattle/aiotagro-cattle-trade/pom.xml index 8b31452..ac97e05 100644 --- a/tradeCattle/aiotagro-cattle-trade/pom.xml +++ b/tradeCattle/aiotagro-cattle-trade/pom.xml @@ -192,6 +192,13 @@ xxl-job-core 2.4.1 + + + + com.openapi + openapi-sdk + 6.0 + diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/DeliveryController.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/DeliveryController.java index f7c7fd8..96506ca 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/DeliveryController.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/DeliveryController.java @@ -13,7 +13,6 @@ import com.aiotagro.cattletrade.business.entity.Delivery; import com.aiotagro.cattletrade.business.entity.DeliveryDevice; import com.aiotagro.cattletrade.business.entity.JbqClient; import com.aiotagro.cattletrade.business.entity.XqClient; -import com.aiotagro.cattletrade.business.service.BaiduYingyanService; import com.aiotagro.cattletrade.business.service.IDeliveryService; import com.aiotagro.cattletrade.business.service.IDeliveryDeviceService; import com.aiotagro.cattletrade.business.service.IJbqClientService; @@ -87,11 +86,6 @@ public class DeliveryController { @Autowired private OrderMapper orderMapper; - @Autowired - private BaiduYingyanService baiduYingyanService; - - - /** * 小程序运送清单-分页查询 * @@ -604,33 +598,6 @@ public class DeliveryController { if (StringUtils.isBlank(existDelivery.getLicensePlate())) { return AjaxResult.error("车牌号为空,无法开启运输状态"); } - // 去除车牌号中的空格,确保格式一致 - String licensePlate = existDelivery.getLicensePlate().trim(); - String entityName = StringUtils.defaultIfBlank(existDelivery.getYingyanEntityName(), licensePlate); - // 如果终端名称与车牌号不一致,使用车牌号(去除空格) - if (!licensePlate.equals(entityName)) { - entityName = licensePlate; - } - boolean ensureResult = baiduYingyanService.ensureEntity(entityName); - - Delivery syncInfo = new Delivery(); - syncInfo.setId(id); - syncInfo.setYingyanEntityName(entityName); - if (existDelivery.getYingyanLastSyncTime() == null) { - Date syncStart = existDelivery.getEstimatedDeliveryTime(); - if (syncStart == null) { - syncStart = existDelivery.getEstimatedDepartureTime(); - } - if (syncStart == null) { - syncStart = existDelivery.getCreateTime(); - } - syncInfo.setYingyanLastSyncTime(syncStart); - } - deliveryService.updateById(syncInfo); - - if (!ensureResult) { - logger.warn("运单 {} 创建百度鹰眼终端失败,将在后台重试", existDelivery.getDeliveryNumber()); - } } else if (status == 3) { Delivery arrivalUpdate = new Delivery(); arrivalUpdate.setId(id); @@ -657,12 +624,12 @@ public class DeliveryController { } /** - * 查询运送清单百度鹰眼轨迹/停留点 + * 查询运单轨迹(基于车牌号,使用 OpenAPI) */ @SaCheckPermission("delivery:view") - @PostMapping("/yingyan/track") - public AjaxResult getYingyanTrack(@Validated @RequestBody DeliveryTrackQueryDto dto) { - return deliveryService.queryYingyanTrack(dto.getDeliveryId()); + @PostMapping("/track") + public AjaxResult getTrack(@Validated @RequestBody DeliveryTrackQueryDto dto) { + return deliveryService.queryTrack(dto.getDeliveryId()); } /** diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/openapi/OpenApiTrackPoint.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/openapi/OpenApiTrackPoint.java new file mode 100644 index 0000000..a117b78 --- /dev/null +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/openapi/OpenApiTrackPoint.java @@ -0,0 +1,45 @@ +package com.aiotagro.cattletrade.business.dto.openapi; + +import lombok.Data; + +/** + * OpenAPI 轨迹点 DTO + * 表示从 OpenAPI 返回的单个轨迹点数据 + * + * @author System + * @date 2025-01-20 + */ +@Data +public class OpenApiTrackPoint { + + /** + * 纬度 + */ + private Double latitude; + + /** + * 经度 + */ + private Double longitude; + + /** + * 定位时间(Unix 时间戳,秒) + */ + private Long locTime; + + /** + * 速度(米/秒) + */ + private Double speed; + + /** + * 方向角(度) + */ + private Double direction; + + /** + * 海拔(米) + */ + private Double altitude; +} + diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/openapi/OpenApiTrackResponse.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/openapi/OpenApiTrackResponse.java new file mode 100644 index 0000000..444a9bb --- /dev/null +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/openapi/OpenApiTrackResponse.java @@ -0,0 +1,37 @@ +package com.aiotagro.cattletrade.business.dto.openapi; + +import lombok.Data; + +import java.util.List; + +/** + * OpenAPI 轨迹查询响应 DTO + * 根据实际 API 返回格式定义字段 + * + * @author System + * @date 2025-01-20 + */ +@Data +public class OpenApiTrackResponse { + + /** + * 响应状态码(0 表示成功) + */ + private Integer status; + + /** + * 响应消息 + */ + private String message; + + /** + * 轨迹点列表 + */ + private List data; + + /** + * 其他可能的字段(根据实际 API 返回格式添加) + */ + private Object other; +} + diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/yingyan/YingyanStayPoint.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/yingyan/YingyanStayPoint.java deleted file mode 100644 index 0fd6716..0000000 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/yingyan/YingyanStayPoint.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.aiotagro.cattletrade.business.dto.yingyan; - -import lombok.Data; - -/** - * 百度鹰眼停留点 - */ -@Data -public class YingyanStayPoint { - - private double latitude; - - private double longitude; - - /** - * 停留开始时间(Unix 秒) - */ - private long startTime; - - /** - * 停留结束时间(Unix 秒) - */ - private long endTime; - - /** - * 停留时长(秒) - */ - private long duration; -} - diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/yingyan/YingyanTrackPoint.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/yingyan/YingyanTrackPoint.java deleted file mode 100644 index 313426b..0000000 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/yingyan/YingyanTrackPoint.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.aiotagro.cattletrade.business.dto.yingyan; - -import lombok.Data; - -/** - * 百度鹰眼轨迹点 - */ -@Data -public class YingyanTrackPoint { - - private double latitude; - - private double longitude; - - /** - * 轨迹点定位时间(Unix 秒) - */ - private long locTime; - - /** - * 米/秒 - */ - private Double speed; - - /** - * 航向角 - */ - private Double direction; -} - diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/entity/Delivery.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/entity/Delivery.java index 5a3db21..1713e5f 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/entity/Delivery.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/entity/Delivery.java @@ -180,20 +180,7 @@ public class Delivery implements Serializable { private Date estimatedDeliveryTime; /** - * 百度鹰眼终端名称(默认为车牌) - */ - @TableField("yingyan_entity_name") - private String yingyanEntityName; - - /** - * 百度鹰眼最后同步时间 - */ - @TableField("yingyan_last_sync_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") - private Date yingyanLastSyncTime; - - /** - * 实际到达时间(鹰眼自动回写) + * 实际到达时间 */ @TableField("arrival_time") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/JbqClientLogMapper.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/JbqClientLogMapper.java index 63d53b2..551e1e2 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/JbqClientLogMapper.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/JbqClientLogMapper.java @@ -27,7 +27,7 @@ public interface JbqClientLogMapper extends BaseMapper { int batchInsert(@Param("list") List logList); /** - * 供百度鹰眼同步使用的增量查询 + * 查询耳标轨迹日志 */ List listLogsForYingyan(@Param("deviceIds") List deviceIds, @Param("startTime") Date startTime, diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/JbqServerLogMapper.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/JbqServerLogMapper.java index bbef37e..cd6aeb1 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/JbqServerLogMapper.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/JbqServerLogMapper.java @@ -28,7 +28,7 @@ public interface JbqServerLogMapper extends BaseMapper { int batchInsert(@Param("list") List logList); /** - * 查询用于百度鹰眼推送的主机轨迹 + * 查询主机轨迹日志 */ List listLogsForYingyan(@Param("deviceIds") List deviceIds, @Param("startTime") Date startTime, diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/XqClientLogMapper.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/XqClientLogMapper.java index fb3ebce..d27ecd9 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/XqClientLogMapper.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/XqClientLogMapper.java @@ -27,7 +27,7 @@ public interface XqClientLogMapper extends BaseMapper { int batchInsert(@Param("list") List logList); /** - * 查询用于百度鹰眼推送的项圈轨迹 + * 查询项圈轨迹日志 */ List listLogsForYingyan(@Param("deviceIds") List deviceIds, @Param("startTime") Date startTime, diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/BaiduYingyanService.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/BaiduYingyanService.java deleted file mode 100644 index ceb62e7..0000000 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/BaiduYingyanService.java +++ /dev/null @@ -1,829 +0,0 @@ -package com.aiotagro.cattletrade.business.service; - -import com.aiotagro.cattletrade.business.dto.yingyan.YingyanStayPoint; -import com.aiotagro.cattletrade.business.dto.yingyan.YingyanTrackPoint; -import com.aiotagro.common.core.constant.BaiduYingyanConstants; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; - -import java.time.Duration; -import java.util.Date; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 百度鹰眼 API 封装 - */ -@Service -public class BaiduYingyanService { - - private static final Logger logger = LoggerFactory.getLogger(BaiduYingyanService.class); - - private final RestTemplate restTemplate; - private final ObjectMapper objectMapper; - - public BaiduYingyanService(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - this.restTemplate = buildRestTemplate(); - } - - /** - * 确保终端存在(幂等) - * 返回值:true-终端存在或创建成功,false-创建失败(终端不存在且创建失败) - */ - public boolean ensureEntity(String entityName) { - if (StringUtils.isBlank(entityName)) { - logger.warn("ensureEntity 失败:终端名称为空"); - return false; - } - - // ✅ 去除空格,确保格式一致 - String cleanEntityName = entityName.trim(); - - // ✅ 验证 entityName 不是 "entity" 字符串 - if ("entity".equals(cleanEntityName) || "entity_name".equals(cleanEntityName)) { - logger.error("ensureEntity 失败:entityName 参数错误,值为 '{}',这可能是参数传递错误", cleanEntityName); - return false; - } - - try { - MultiValueMap form = baseForm(); - form.add("entity_name", cleanEntityName); - // entity_desc 为非必填项,且命名规则限制:只支持中文、英文字母、下划线、连字符、数字 - // 为避免参数错误,不传递 entity_desc 参数 - - logger.debug("确保终端存在 - entity={}, service_id={}", cleanEntityName, BaiduYingyanConstants.SERVICE_ID); - JsonNode result = postForm("/entity/add", form); - int status = result.path("status").asInt(-1); - String message = result.path("message").asText(); - - // ✅ 修复:status=0(创建成功)、3005(终端已存在)、3006(其他成功状态)都视为成功 - if (status == 0) { - logger.info("✅ 鹰眼终端创建成功, entityName={}, service_id={}", cleanEntityName, BaiduYingyanConstants.SERVICE_ID); - return true; - } else if (status == 3005) { - // ✅ 终端已存在,这是正常情况,视为成功 - logger.info("✅ 鹰眼终端已存在, entityName={}, service_id={}, message={}", - cleanEntityName, BaiduYingyanConstants.SERVICE_ID, message); - return true; - } else if (status == 3006) { - logger.info("✅ 鹰眼终端操作成功, entityName={}, service_id={}, status={}", - cleanEntityName, BaiduYingyanConstants.SERVICE_ID, status); - return true; - } - - // 其他状态码视为失败 - logger.warn("❌ 鹰眼创建终端失败, entityName={}, service_id={}, status={}, message={}", - cleanEntityName, BaiduYingyanConstants.SERVICE_ID, status, message); - } catch (Exception e) { - logger.error("❌ 鹰眼创建终端异常, entityName={}", cleanEntityName, e); - } - return false; - } - - /** - * 推送单条轨迹点 - */ - public boolean pushTrackPoint(String entityName, double latitude, double longitude, long locTime) { - if (StringUtils.isBlank(entityName)) { - logger.warn("鹰眼上传轨迹失败:终端名称为空"); - return false; - } - - // ✅ 验证经纬度有效性 - if (latitude == 0 && longitude == 0) { - logger.warn("鹰眼上传轨迹失败:经纬度无效 (0,0), entity={}", entityName); - return false; - } - if (latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180) { - logger.warn("鹰眼上传轨迹失败:经纬度超出有效范围, entity={}, lat={}, lon={}", - entityName, latitude, longitude); - return false; - } - - try { - MultiValueMap form = baseForm(); - form.add("entity_name", entityName); - form.add("latitude", String.valueOf(latitude)); - form.add("longitude", String.valueOf(longitude)); - form.add("loc_time", String.valueOf(locTime)); - form.add("coord_type_input", "wgs84"); - - logger.debug("鹰眼上传轨迹点 - entity={}, lat={}, lon={}, locTime={}", - entityName, latitude, longitude, locTime); - - JsonNode result = postForm("/track/addpoint", form); - int status = result.path("status").asInt(-1); - String message = result.path("message").asText(); - - if (status == 0) { - logger.debug("鹰眼上传轨迹成功 - entity={}, lat={}, lon={}", - entityName, latitude, longitude); - return true; - } - - // ✅ 详细记录失败原因 - logger.warn("鹰眼上传轨迹失败 - entity={}, status={}, message={}, lat={}, lon={}, locTime={}", - entityName, status, message, latitude, longitude, locTime); - - // 如果是常见错误,记录更详细的信息 - if (status == 3001) { - logger.error("鹰眼上传轨迹失败:参数错误,请检查经纬度和时间格式"); - } else if (status == 3002) { - logger.error("鹰眼上传轨迹失败:服务不存在或未启用"); - } else if (status == 3003) { - logger.error("鹰眼上传轨迹失败:终端不存在"); - } else if (status == 3004) { - logger.error("鹰眼上传轨迹失败:轨迹点时间格式错误"); - } - - } catch (Exception e) { - logger.error("鹰眼上传轨迹异常 - entity={}, lat={}, lon={}, locTime={}", - entityName, latitude, longitude, locTime, e); - } - return false; - } - - /** - * 批量推送轨迹点(使用 /track/addpoints 接口) - * @param entityName 终端名称 - * @param points 轨迹点列表 - * @return 成功上传的轨迹点数量 - */ - public int pushTrackPoints(String entityName, List points) { - if (StringUtils.isBlank(entityName)) { - logger.warn("鹰眼批量上传轨迹失败:终端名称为空"); - return 0; - } - if (points == null || points.isEmpty()) { - logger.warn("鹰眼批量上传轨迹失败:轨迹点列表为空, entity={}", entityName); - return 0; - } - - try { - // ✅ 确保终端存在 - if (!ensureEntity(entityName)) { - logger.warn("鹰眼批量上传轨迹失败:终端不存在或创建失败, entity={}", entityName); - return 0; - } - - // ✅ 构建 point_list JSON 数组字符串 - List> pointList = new ArrayList<>(); - for (YingyanTrackPoint point : points) { - // 验证经纬度有效性 - if (point.getLatitude() == 0 && point.getLongitude() == 0) { - logger.warn("鹰眼批量上传:跳过无效轨迹点 (0,0), entity={}", entityName); - continue; - } - if (point.getLatitude() < -90 || point.getLatitude() > 90 || - point.getLongitude() < -180 || point.getLongitude() > 180) { - logger.warn("鹰眼批量上传:跳过超出范围的轨迹点, entity={}, lat={}, lon={}", - entityName, point.getLatitude(), point.getLongitude()); - continue; - } - - Map pointMap = new HashMap<>(); - pointMap.put("entity_name", entityName); - pointMap.put("coord_type_input", "wgs84"); // 根据数据源调整坐标类型 - pointMap.put("loc_time", point.getLocTime()); - pointMap.put("longitude", point.getLongitude()); - pointMap.put("latitude", point.getLatitude()); - if (point.getSpeed() != null && point.getSpeed() > 0) { - pointMap.put("speed", point.getSpeed()); - } - if (point.getDirection() != null) { - pointMap.put("direction", point.getDirection()); - } - pointList.add(pointMap); - } - - if (pointList.isEmpty()) { - logger.warn("鹰眼批量上传:过滤后无有效轨迹点, entity={}, 原始数量={}", entityName, points.size()); - return 0; - } - - // ✅ 将 pointList 转换为 JSON 字符串 - String pointListJson = objectMapper.writeValueAsString(pointList); - - // ✅ 构建 POST 请求参数 - MultiValueMap form = baseForm(); - form.add("point_list", pointListJson); - - logger.info("鹰眼批量上传轨迹点 - entity={}, 轨迹点数量={}", entityName, pointList.size()); - - JsonNode result = postForm("/track/addpoints", form); - int status = result.path("status").asInt(-1); - String message = result.path("message").asText(); - - if (status == 0) { - // ✅ 批量上传成功,返回成功数量 - int successCount = pointList.size(); - logger.info("鹰眼批量上传轨迹成功 - entity={}, 成功数量={}", entityName, successCount); - return successCount; - } - - // ✅ 详细记录失败原因 - logger.warn("鹰眼批量上传轨迹失败 - entity={}, status={}, message={}, 轨迹点数量={}", - entityName, status, message, pointList.size()); - - // 如果是常见错误,记录更详细的信息 - if (status == 3001) { - logger.error("鹰眼批量上传轨迹失败:参数错误,请检查轨迹点格式"); - } else if (status == 3002) { - logger.error("鹰眼批量上传轨迹失败:服务不存在或未启用"); - } else if (status == 3003) { - logger.error("鹰眼批量上传轨迹失败:终端不存在"); - } else if (status == 3004) { - logger.error("鹰眼批量上传轨迹失败:轨迹点时间格式错误"); - } - - } catch (Exception e) { - logger.error("鹰眼批量上传轨迹异常 - entity={}, 轨迹点数量={}", entityName, points.size(), e); - } - return 0; - } - - /** - * 查询轨迹(单次查询,限制24小时内) - */ - public List queryTrack(String entityName, long startTime, long endTime) { - if (StringUtils.isBlank(entityName)) { - return Collections.emptyList(); - } - - // ✅ 查询前再次确保终端存在,避免查询时终端不存在 - if (!ensureEntity(entityName)) { - logger.warn("查询轨迹前终端不存在或创建失败,无法查询 - entity={}", entityName); - return Collections.emptyList(); - } - - try { - // ✅ 去除空格,确保格式一致 - String cleanEntityName = entityName.trim(); - - Map params = new HashMap<>(); - params.put("entity_name", cleanEntityName); - // ✅ 所有参数统一为 String 类型 - params.put("start_time", String.valueOf(startTime)); - params.put("end_time", String.valueOf(endTime)); - params.put("is_processed", "1"); - // ✅ 根据用户测试成功的 URL,添加 process_option 参数 - // 格式:need_denoise=1,need_vacuate=1,need_mapmatch=0 - params.put("process_option", "need_denoise=1,need_vacuate=1,need_mapmatch=0"); - // ✅ 可选参数,根据官方文档添加 - params.put("coord_type_output", "bd09ll"); - params.put("page_size", "5000"); - params.put("page_index", "1"); - params.put("sort_type", "asc"); - - logger.debug("查询轨迹 - entity={}, startTime={} ({}), endTime={} ({})", - entityName, startTime, new Date(startTime * 1000), endTime, new Date(endTime * 1000)); - - JsonNode result = get("/track/gettrack", params); - int status = result.path("status").asInt(-1); - String message = result.path("message").asText(); - - logger.info("鹰眼查询轨迹响应 - entity={}, status={}, message={}, startTime={}, endTime={}", - entityName, status, message, new Date(startTime * 1000), new Date(endTime * 1000)); - - if (!isSuccess(result)) { - // ✅ status=3003 可能是"终端不存在"或"指定时间范围内无轨迹数据" - // 如果终端确实存在(通过 ensureEntity 确认),则可能是时间范围内无数据 - if (status == 3003) { - logger.info("鹰眼查询轨迹:指定时间范围内可能无轨迹数据, entity={}, startTime={}, endTime={}, message={}", - entityName, new Date(startTime * 1000), new Date(endTime * 1000), message); - } else { - logger.warn("鹰眼查询轨迹失败, entity={}, status={}, message={}, startTime={}, endTime={}, raw={}", - entityName, status, message, new Date(startTime * 1000), new Date(endTime * 1000), result.toString()); - } - return Collections.emptyList(); - } - - // 文档返回结构存在两种形式:直接 points 字段 或 track.points - JsonNode pointsNode = result.path("points"); - if (pointsNode == null || pointsNode.isMissingNode()) { - pointsNode = result.path("track").path("points"); - } - if (pointsNode == null || pointsNode.isMissingNode()) { - logger.warn("鹰眼响应缺少 points 字段,entity={}, raw={}", entityName, result.toString()); - return Collections.emptyList(); - } - if (!pointsNode.isArray()) { - logger.warn("鹰眼响应 points 字段不是数组,entity={}, raw={}", entityName, result.toString()); - return Collections.emptyList(); - } - if (pointsNode.size() == 0) { - logger.warn("鹰眼返回0个轨迹点,entity={}, startTime={}, endTime={}, raw={}", - entityName, new Date(startTime * 1000), new Date(endTime * 1000), result.toString()); - return Collections.emptyList(); - } - - logger.info("鹰眼返回轨迹点数量:entity={}, size={}", entityName, pointsNode.size()); - List points = new ArrayList<>(pointsNode.size()); - pointsNode.forEach(node -> { - if (!node.hasNonNull("latitude") || !node.hasNonNull("longitude")) { - return; - } - YingyanTrackPoint point = new YingyanTrackPoint(); - point.setLatitude(node.path("latitude").asDouble()); - point.setLongitude(node.path("longitude").asDouble()); - point.setLocTime(node.path("loc_time").asLong()); - if (node.has("speed")) { - point.setSpeed(node.path("speed").asDouble()); - } - if (node.has("direction")) { - point.setDirection(node.path("direction").asDouble()); - } - points.add(point); - }); - return points; - } catch (Exception e) { - logger.error("鹰眼查询轨迹异常, entity={}", entityName, e); - return Collections.emptyList(); - } - } - - /** - * 查询最新轨迹点(实时位置) - */ - public YingyanTrackPoint queryLatestPoint(String entityName) { - if (StringUtils.isBlank(entityName)) { - logger.warn("查询最新轨迹点失败:终端名称为空"); - return null; - } - - try { - // ✅ 查询前再次确保终端存在 - if (!ensureEntity(entityName)) { - logger.warn("查询最新轨迹点前终端不存在或创建失败,entity={}", entityName); - return null; - } - - Map params = new HashMap<>(); - params.put("entity_name", entityName); - params.put("coord_type_output", "bd09ll"); - // ✅ 根据官方文档,process_option 为可选参数,暂时移除避免格式错误 - // params.put("process_option", "denoise_grade=1,radius_threshold=20,need_mapmatch=1,transport_mode=driving"); - - JsonNode result = get("/track/getlatestpoint", params); - if (!isSuccess(result)) { - int status = result.path("status").asInt(-1); - String message = result.path("message").asText(); - logger.warn("鹰眼查询最新轨迹点失败, entity={}, status={}, message={}", entityName, status, message); - return null; - } - - JsonNode pointNode = result.path("latest_point"); - if (pointNode == null || pointNode.isMissingNode() || pointNode.isNull()) { - logger.warn("鹰眼查询最新轨迹点成功但无数据, entity={}", entityName); - return null; - } - - if (!pointNode.hasNonNull("latitude") || !pointNode.hasNonNull("longitude")) { - logger.warn("鹰眼最新轨迹点缺少经纬度, entity={}", entityName); - return null; - } - - double latitude = pointNode.path("latitude").asDouble(); - double longitude = pointNode.path("longitude").asDouble(); - if (latitude == 0 && longitude == 0) { - logger.warn("鹰眼最新轨迹点经纬度无效 (0,0), entity={}", entityName); - return null; - } - - YingyanTrackPoint latestPoint = new YingyanTrackPoint(); - latestPoint.setLatitude(latitude); - latestPoint.setLongitude(longitude); - latestPoint.setLocTime(pointNode.path("loc_time").asLong(0)); - - if (pointNode.has("speed") && !pointNode.path("speed").isNull()) { - latestPoint.setSpeed(pointNode.path("speed").asDouble()); - } - if (pointNode.has("direction") && !pointNode.path("direction").isNull()) { - latestPoint.setDirection(pointNode.path("direction").asDouble()); - } - - logger.debug("查询最新轨迹点成功 - entity={}, lat={}, lon={}, locTime={}", - entityName, latitude, longitude, latestPoint.getLocTime()); - return latestPoint; - } catch (Exception e) { - logger.error("鹰眼查询最新轨迹点异常, entity={}", entityName, e); - return null; - } - } - - /** - * 分段查询轨迹(支持超过24小时的查询) - * 按照24小时为间隔分段查询,然后拼接结果 - * - * @param entityName 终端名称 - * @param startTime 开始时间(秒级时间戳) - * @param endTime 结束时间(秒级时间戳,不能超过当前时间) - * @return 拼接后的轨迹点列表,按时间排序 - */ - public List queryTrackSegmented(String entityName, long startTime, long endTime) { - if (StringUtils.isBlank(entityName)) { - return Collections.emptyList(); - } - - // ✅ 首先确保终端存在 - if (!ensureEntity(entityName)) { - logger.warn("查询轨迹前终端不存在或创建失败,无法查询 - entity={}", entityName); - return Collections.emptyList(); - } - - // 验证时间范围 - long currentTime = System.currentTimeMillis() / 1000; - if (endTime > currentTime) { - logger.warn("结束时间不能超过当前时间,自动调整为当前时间。entity={}, endTime={}, currentTime={}", - entityName, endTime, currentTime); - endTime = currentTime; - } - - if (startTime >= endTime) { - logger.warn("开始时间不能大于等于结束时间。entity={}, startTime={}, endTime={}", - entityName, startTime, endTime); - return Collections.emptyList(); - } - - // 计算时间差(秒) - long timeDiff = endTime - startTime; - // 24小时 = 86400秒 - final long SEGMENT_INTERVAL = 86400L; - - // 如果时间差小于等于24小时,直接查询 - if (timeDiff <= SEGMENT_INTERVAL) { - logger.debug("时间范围在24小时内,直接查询。entity={}, startTime={}, endTime={}, diff={}秒", - entityName, startTime, endTime, timeDiff); - return queryTrack(entityName, startTime, endTime); - } - - // 需要分段查询 - logger.info("开始分段查询轨迹 - entity={}, startTime={}, endTime={}, 总时长={}小时", - entityName, startTime, endTime, timeDiff / 3600); - - List allPoints = new ArrayList<>(); - long segmentStart = startTime; - int segmentIndex = 1; - - while (segmentStart < endTime) { - // 计算当前段的结束时间(不超过endTime,且不超过24小时) - long segmentEnd = Math.min(segmentStart + SEGMENT_INTERVAL, endTime); - - logger.debug("查询第{}段轨迹 - entity={}, segmentStart={}, segmentEnd={}, 时长={}小时", - segmentIndex, entityName, segmentStart, segmentEnd, (segmentEnd - segmentStart) / 3600); - - try { - // ✅ 在查询前确保终端存在(每段都检查,因为可能在某些时间段终端还未创建) - if (!ensureEntity(entityName)) { - logger.warn("第{}段查询前终端不存在或创建失败,跳过该段 - entity={}", segmentIndex, entityName); - segmentStart = segmentEnd; - segmentIndex++; - continue; - } - - List segmentPoints = queryTrack(entityName, segmentStart, segmentEnd); - if (!segmentPoints.isEmpty()) { - allPoints.addAll(segmentPoints); - logger.debug("第{}段查询成功,获得{}个轨迹点", segmentIndex, segmentPoints.size()); - } else { - logger.debug("第{}段无轨迹点", segmentIndex); - } - } catch (Exception e) { - logger.error("第{}段轨迹查询异常 - entity={}, segmentStart={}, segmentEnd={}", - segmentIndex, entityName, segmentStart, segmentEnd, e); - // 继续查询下一段,不中断 - } - - // 移动到下一段(下一段的开始时间 = 当前段的结束时间) - segmentStart = segmentEnd; - segmentIndex++; - - // 避免无限循环 - if (segmentIndex > 100) { - logger.error("分段查询超过100段,可能存在逻辑错误,停止查询。entity={}", entityName); - break; - } - } - - // 按时间排序并去重(基于locTime) - if (!allPoints.isEmpty()) { - // 按时间排序 - allPoints.sort(Comparator.comparingLong(YingyanTrackPoint::getLocTime)); - - // 去重:相同时间戳的轨迹点只保留一个 - List uniquePoints = new ArrayList<>(); - long lastLocTime = -1; - for (YingyanTrackPoint point : allPoints) { - if (point.getLocTime() != lastLocTime) { - uniquePoints.add(point); - lastLocTime = point.getLocTime(); - } - } - - logger.info("分段查询完成 - entity={}, 总段数={}, 原始轨迹点数={}, 去重后轨迹点数={}", - entityName, segmentIndex - 1, allPoints.size(), uniquePoints.size()); - - return uniquePoints; - } - - logger.warn("分段查询未获得任何轨迹点 - entity={}, startTime={}, endTime={}", - entityName, startTime, endTime); - return Collections.emptyList(); - } - - /** - * 查询停留点(单次查询,限制24小时内) - */ - public List queryStayPoints(String entityName, long startTime, long endTime, int stayTimeSeconds) { - if (StringUtils.isBlank(entityName)) { - logger.warn("查询停留点失败:终端名称为空"); - return Collections.emptyList(); - } - - // ✅ 验证 entityName 不是 "entity" 字符串 - if ("entity".equals(entityName) || "entity_name".equals(entityName)) { - logger.error("查询停留点失败:entityName 参数错误,值为 '{}',这可能是参数传递错误", entityName); - return Collections.emptyList(); - } - - try { - Map params = new HashMap<>(); - params.put("entity_name", entityName); - // ✅ 所有参数统一为 String 类型 - params.put("start_time", String.valueOf(startTime)); - params.put("end_time", String.valueOf(endTime)); - params.put("stay_time", String.valueOf(stayTimeSeconds)); - params.put("coord_type_output", "bd09ll"); - - logger.debug("查询停留点 - entity={}, startTime={} ({}), endTime={} ({}), stayTimeSeconds={}", - entityName, startTime, new Date(startTime * 1000), endTime, new Date(endTime * 1000), stayTimeSeconds); - - JsonNode result = get("/analysis/staypoint", params); - int status = result.path("status").asInt(-1); - String message = result.path("message").asText(); - - if (!isSuccess(result)) { - // ✅ status=3003 可能是"终端不存在"或"指定时间范围内无停留点数据" - // 如果终端确实存在(通过 ensureEntity 确认),则可能是时间范围内无数据 - if (status == 3003) { - logger.debug("鹰眼查询停留点:指定时间范围内可能无停留点数据, entity={}, startTime={}, endTime={}, message={}", - entityName, new Date(startTime * 1000), new Date(endTime * 1000), message); - } else { - logger.warn("鹰眼查询停留点失败, entity={}, status={}, message={}, startTime={}, endTime={}", - entityName, status, message, new Date(startTime * 1000), new Date(endTime * 1000)); - } - return Collections.emptyList(); - } - - JsonNode stayNode = result.path("stay_points"); - if (stayNode == null || !stayNode.isArray() || stayNode.size() == 0) { - return Collections.emptyList(); - } - - List stayPoints = new ArrayList<>(stayNode.size()); - stayNode.forEach(node -> { - if (!node.hasNonNull("latitude") || !node.hasNonNull("longitude")) { - return; - } - YingyanStayPoint stayPoint = new YingyanStayPoint(); - stayPoint.setLatitude(node.path("latitude").asDouble()); - stayPoint.setLongitude(node.path("longitude").asDouble()); - stayPoint.setStartTime(node.path("start_time").asLong()); - stayPoint.setEndTime(node.path("end_time").asLong()); - stayPoint.setDuration(node.path("duration").asLong()); - stayPoints.add(stayPoint); - }); - return stayPoints; - } catch (Exception e) { - logger.error("鹰眼查询停留点异常, entity={}", entityName, e); - return Collections.emptyList(); - } - } - - /** - * 分段查询停留点(支持超过24小时的查询) - * 按照24小时为间隔分段查询,然后拼接结果 - * - * @param entityName 终端名称 - * @param startTime 开始时间(秒级时间戳) - * @param endTime 结束时间(秒级时间戳,不能超过当前时间) - * @param stayTimeSeconds 停留时间阈值(秒) - * @return 拼接后的停留点列表,按开始时间排序 - */ - public List queryStayPointsSegmented(String entityName, long startTime, long endTime, int stayTimeSeconds) { - if (StringUtils.isBlank(entityName)) { - return Collections.emptyList(); - } - - // ✅ 首先确保终端存在 - if (!ensureEntity(entityName)) { - logger.warn("查询停留点前终端不存在或创建失败,无法查询 - entity={}", entityName); - return Collections.emptyList(); - } - - // 验证时间范围 - long currentTime = System.currentTimeMillis() / 1000; - if (endTime > currentTime) { - logger.warn("结束时间不能超过当前时间,自动调整为当前时间。entity={}, endTime={}, currentTime={}", - entityName, endTime, currentTime); - endTime = currentTime; - } - - if (startTime >= endTime) { - logger.warn("开始时间不能大于等于结束时间。entity={}, startTime={}, endTime={}", - entityName, startTime, endTime); - return Collections.emptyList(); - } - - // 计算时间差(秒) - long timeDiff = endTime - startTime; - // 24小时 = 86400秒 - final long SEGMENT_INTERVAL = 86400L; - - // 如果时间差小于等于24小时,直接查询 - if (timeDiff <= SEGMENT_INTERVAL) { - logger.debug("时间范围在24小时内,直接查询停留点。entity={}, startTime={}, endTime={}, diff={}秒", - entityName, startTime, endTime, timeDiff); - return queryStayPoints(entityName, startTime, endTime, stayTimeSeconds); - } - - // 需要分段查询 - logger.info("开始分段查询停留点 - entity={}, startTime={}, endTime={}, 总时长={}小时", - entityName, startTime, endTime, timeDiff / 3600); - - List allStayPoints = new ArrayList<>(); - long segmentStart = startTime; - int segmentIndex = 1; - - while (segmentStart < endTime) { - // 计算当前段的结束时间(不超过endTime,且不超过24小时) - long segmentEnd = Math.min(segmentStart + SEGMENT_INTERVAL, endTime); - - logger.debug("查询第{}段停留点 - entity={}, segmentStart={}, segmentEnd={}, 时长={}小时", - segmentIndex, entityName, segmentStart, segmentEnd, (segmentEnd - segmentStart) / 3600); - - try { - // ✅ 在查询前确保终端存在(每段都检查,因为可能在某些时间段终端还未创建) - if (!ensureEntity(entityName)) { - logger.warn("第{}段查询前终端不存在或创建失败,跳过该段 - entity={}", segmentIndex, entityName); - segmentStart = segmentEnd; - segmentIndex++; - continue; - } - - List segmentStayPoints = queryStayPoints(entityName, segmentStart, segmentEnd, stayTimeSeconds); - if (!segmentStayPoints.isEmpty()) { - allStayPoints.addAll(segmentStayPoints); - logger.debug("第{}段查询成功,获得{}个停留点", segmentIndex, segmentStayPoints.size()); - } else { - logger.debug("第{}段无停留点", segmentIndex); - } - } catch (Exception e) { - logger.error("第{}段停留点查询异常 - entity={}, segmentStart={}, segmentEnd={}", - segmentIndex, entityName, segmentStart, segmentEnd, e); - // 继续查询下一段,不中断 - } - - // 移动到下一段(下一段的开始时间 = 当前段的结束时间) - segmentStart = segmentEnd; - segmentIndex++; - - // 避免无限循环 - if (segmentIndex > 100) { - logger.error("分段查询超过100段,可能存在逻辑错误,停止查询。entity={}", entityName); - break; - } - } - - // 按开始时间排序并去重(基于startTime) - if (!allStayPoints.isEmpty()) { - // 按开始时间排序 - allStayPoints.sort(Comparator.comparingLong(YingyanStayPoint::getStartTime)); - - // 去重:相同开始时间的停留点只保留一个 - List uniqueStayPoints = new ArrayList<>(); - long lastStartTime = -1; - for (YingyanStayPoint stayPoint : allStayPoints) { - if (stayPoint.getStartTime() != lastStartTime) { - uniqueStayPoints.add(stayPoint); - lastStartTime = stayPoint.getStartTime(); - } - } - - logger.info("分段查询停留点完成 - entity={}, 总段数={}, 原始停留点数={}, 去重后停留点数={}", - entityName, segmentIndex - 1, allStayPoints.size(), uniqueStayPoints.size()); - - return uniqueStayPoints; - } - - logger.warn("分段查询未获得任何停留点 - entity={}, startTime={}, endTime={}", - entityName, startTime, endTime); - return Collections.emptyList(); - } - - private JsonNode postForm(String path, MultiValueMap form) throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - - HttpEntity> httpEntity = new HttpEntity<>(form, headers); - ResponseEntity response = restTemplate.postForEntity(buildUrl(path), httpEntity, String.class); - return parseBody(response.getBody()); - } - - private JsonNode get(String path, Map params) throws Exception { - // ✅ 手动构建 URL,所有参数都不进行 URL 编码(与用户测试成功的 URL 格式一致) - StringBuilder urlBuilder = new StringBuilder(buildUrl(path)); - urlBuilder.append("?ak=").append(BaiduYingyanConstants.AK); - urlBuilder.append("&service_id=").append(BaiduYingyanConstants.SERVICE_ID); - - if (params != null) { - for (Map.Entry entry : params.entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - if (value != null) { - // ✅ 验证参数名和值,避免参数传递错误 - if ("entity_name".equals(key) && ("entity".equals(value) || "entity_name".equals(value))) { - logger.error("参数传递错误:entity_name 的值不能是 '{}',这可能是参数名和值混淆了", value); - throw new IllegalArgumentException("entity_name 参数值错误: " + value); - } - - // ✅ 所有参数都不进行 URL 编码,直接拼接(与用户测试成功的 URL 格式一致) - urlBuilder.append("&").append(key).append("=").append(value.toString()); - } - } - } - - String requestUrl = urlBuilder.toString(); - String maskedUrl = requestUrl.replaceAll("ak=[^&]+", "ak=***").replaceAll("service_id=[^&]+", "service_id=***"); - Object entityNameParam = params != null ? params.get("entity_name") : "N/A"; - logger.info("百度鹰眼API请求 - path={}, entity_name={}, service_id={}, url={}", - path, entityNameParam, BaiduYingyanConstants.SERVICE_ID, maskedUrl); - - // ✅ 记录实际请求 URL(用于调试,生产环境可关闭) - logger.debug("百度鹰眼API实际请求URL - {}", requestUrl.replaceAll("ak=[^&]+", "ak=***")); - - String response = restTemplate.getForObject(requestUrl, String.class); - return parseBody(response); - } - - private JsonNode parseBody(String body) throws Exception { - if (StringUtils.isBlank(body)) { - return objectMapper.createObjectNode(); - } - return objectMapper.readTree(body); - } - - private boolean isSuccess(JsonNode node) { - if (node == null) { - return false; - } - int status = node.path("status").asInt(-1); - return status == 0; - } - - private MultiValueMap baseForm() { - MultiValueMap form = new LinkedMultiValueMap<>(); - form.add("ak", BaiduYingyanConstants.AK); - form.add("service_id", String.valueOf(BaiduYingyanConstants.SERVICE_ID)); - return form; - } - - private RestTemplate buildRestTemplate() { - org.springframework.http.client.SimpleClientHttpRequestFactory factory = - new org.springframework.http.client.SimpleClientHttpRequestFactory(); - factory.setConnectTimeout((int) Duration.ofSeconds(10).toMillis()); - factory.setReadTimeout((int) Duration.ofSeconds(30).toMillis()); - return new RestTemplate(factory); - } - - private String buildUrl(String path) { - if (StringUtils.isBlank(path)) { - return BaiduYingyanConstants.BASE_URL; - } - if (path.startsWith("http")) { - return path; - } - if (!path.startsWith("/")) { - path = "/" + path; - } - return BaiduYingyanConstants.BASE_URL + path; - } -} - diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/DeliveryYingyanSyncService.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/DeliveryYingyanSyncService.java deleted file mode 100644 index 0c13242..0000000 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/DeliveryYingyanSyncService.java +++ /dev/null @@ -1,418 +0,0 @@ -package com.aiotagro.cattletrade.business.service; - -import com.aiotagro.cattletrade.business.dto.yingyan.YingyanTrackPoint; -import com.aiotagro.cattletrade.business.entity.Delivery; -import com.aiotagro.cattletrade.business.entity.IotDeviceData; -import com.aiotagro.cattletrade.business.entity.JbqClientLog; -import com.aiotagro.cattletrade.business.entity.JbqServerLog; -import com.aiotagro.cattletrade.business.entity.XqClientLog; -import com.aiotagro.cattletrade.business.mapper.IotDeviceDataMapper; -import com.aiotagro.cattletrade.business.mapper.JbqClientLogMapper; -import com.aiotagro.cattletrade.business.mapper.JbqServerLogMapper; -import com.aiotagro.cattletrade.business.mapper.XqClientLogMapper; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * 运送清单鹰眼同步服务 - */ -@Service -public class DeliveryYingyanSyncService { - - private static final Logger logger = LoggerFactory.getLogger(DeliveryYingyanSyncService.class); - - /** - * 每次最多同步 120 个轨迹点,避免请求过多 - */ - private static final int MAX_SYNC_POINTS = 120; - - private static final double ARRIVAL_RADIUS_METERS = 500D; - - @Autowired - private IDeliveryService deliveryService; - - @Autowired - private IotDeviceDataMapper iotDeviceDataMapper; - - @Autowired - private JbqClientLogMapper jbqClientLogMapper; - - @Autowired - private JbqServerLogMapper jbqServerLogMapper; - - @Autowired - private XqClientLogMapper xqClientLogMapper; - - @Autowired - private BaiduYingyanService baiduYingyanService; - - /** - * 同步所有运输中的运送清单 - */ - public void syncActiveDeliveries() { - logger.info("========== 开始执行百度鹰眼轨迹同步任务 =========="); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Delivery::getStatus, 2); - wrapper.isNotNull(Delivery::getLicensePlate); - List deliveries = deliveryService.list(wrapper); - if (CollectionUtils.isEmpty(deliveries)) { - logger.info("未找到运输中的运单(status=2),跳过同步"); - return; - } - - logger.info("找到 {} 个运输中的运单,开始同步轨迹", deliveries.size()); - int successCount = 0; - int failCount = 0; - int noPointsCount = 0; - - for (Delivery delivery : deliveries) { - try { - int result = syncDelivery(delivery); - if (result > 0) { - successCount++; - logger.info("运单 {} 同步成功,上传了 {} 个轨迹点", delivery.getDeliveryNumber(), result); - } else if (result == 0) { - noPointsCount++; - logger.debug("运单 {} 无有效轨迹点可同步", delivery.getDeliveryNumber()); - } else { - failCount++; - } - } catch (Exception e) { - failCount++; - logger.error("运单 {} 同步百度鹰眼失败", delivery.getDeliveryNumber(), e); - } - } - - logger.info("百度鹰眼轨迹同步完成 - 成功: {}, 无轨迹点: {}, 失败: {}", successCount, noPointsCount, failCount); - logger.info("========== 百度鹰眼轨迹同步任务执行完成 =========="); - } - - private int syncDelivery(Delivery delivery) { - String entityName = resolveEntityName(delivery); - if (StringUtils.isBlank(entityName)) { - logger.warn("运单 {} 无法同步,车牌为空", delivery.getDeliveryNumber()); - return -1; - } - - logger.info("开始同步运单 {} 的轨迹,终端名称: {}", delivery.getDeliveryNumber(), entityName); - - // ✅ 确保终端存在(如果已存在则继续,如果不存在则创建) - boolean entityExists = baiduYingyanService.ensureEntity(entityName); - if (!entityExists) { - logger.warn("运单 {} 创建/确保终端失败,终端名称: {},但继续尝试上传轨迹点(终端可能已存在)", - delivery.getDeliveryNumber(), entityName); - // 注意:即使 ensureEntity 返回 false,也继续尝试上传轨迹点 - // 因为终端可能已经存在,只是创建时返回了错误状态码 - } else { - logger.debug("运单 {} 终端已存在或创建成功,终端名称: {}", delivery.getDeliveryNumber(), entityName); - } - - Date startTime = determineSyncStart(delivery); - Date endTime = new Date(); - logger.info("运单 {} 查询轨迹点时间范围: {} 至 {}", - delivery.getDeliveryNumber(), startTime, endTime); - - List points = collectTrackPoints(delivery, startTime, endTime); - if (CollectionUtils.isEmpty(points)) { - logger.warn("运单 {} 未找到有效轨迹点,终端名称: {}, 时间范围: {} 至 {}", - delivery.getDeliveryNumber(), entityName, startTime, endTime); - - // ✅ 详细排查原因:从 iot_device_data 表查询设备 - QueryWrapper deviceQueryWrapper = new QueryWrapper<>(); - deviceQueryWrapper.eq("delivery_id", delivery.getId()); - deviceQueryWrapper.and(wrapper -> wrapper.eq("is_delet", 0).or().isNull("is_delet")); - List devices = iotDeviceDataMapper.selectList(deviceQueryWrapper); - - logger.warn("运单 {} 在 iot_device_data 表中的设备数量: {}", - delivery.getDeliveryNumber(), devices != null ? devices.size() : 0); - - if (devices != null && !devices.isEmpty()) { - devices.forEach(device -> { - logger.warn("运单 {} 设备信息 - deviceId: {}, deviceType: {}, latitude: {}, longitude: {}", - delivery.getDeliveryNumber(), device.getDeviceId(), device.getDeviceType(), - device.getLatitude(), device.getLongitude()); - }); - } else { - logger.warn("运单 {} 在 iot_device_data 表中没有找到设备,deliveryId: {}", - delivery.getDeliveryNumber(), delivery.getId()); - } - - return 0; - } - - logger.info("运单 {} 找到 {} 个有效轨迹点,开始批量上传到百度鹰眼", - delivery.getDeliveryNumber(), points.size()); - - // ✅ 检查运单状态,如果已结束则停止同步 - if (delivery.getStatus() != null && delivery.getStatus() == 3) { - logger.info("运单 {} 状态已变为已结束,停止同步", delivery.getDeliveryNumber()); - return 0; - } - - // ✅ 使用批量上传接口 - int successCount = baiduYingyanService.pushTrackPoints(entityName, points); - int failCount = points.size() - successCount; - - if (successCount > 0) { - // ✅ 批量上传成功后,使用最后一个轨迹点的时间更新同步时间 - YingyanTrackPoint lastPoint = points.get(points.size() - 1); - Date locDate = new Date(lastPoint.getLocTime() * 1000L); - updateLastSync(delivery, locDate); - - // ✅ 检查是否到达目的地(使用最后一个轨迹点) - handleArrivalIfNeeded(delivery, lastPoint); - - logger.info("运单 {} 批量上传轨迹点成功 - 成功: {}, 失败: {}, 总计: {}, 最后时间: {}", - delivery.getDeliveryNumber(), successCount, failCount, points.size(), locDate); - } else { - logger.warn("运单 {} 批量上传轨迹点全部失败 - 总计: {}", - delivery.getDeliveryNumber(), points.size()); - } - - return successCount; - } - - private List collectTrackPoints(Delivery delivery, Date startTime, Date endTime) { - // ✅ 修改:从 iot_device_data 表查询设备,而不是 delivery_device 表 - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("delivery_id", delivery.getId()); - queryWrapper.and(wrapper -> wrapper.eq("is_delet", 0).or().isNull("is_delet")); // 只查询未删除的设备 - List devices = iotDeviceDataMapper.selectList(queryWrapper); - - if (CollectionUtils.isEmpty(devices)) { - logger.warn("运单 {} 在 iot_device_data 表中未找到设备,deliveryId: {}", - delivery.getDeliveryNumber(), delivery.getId()); - return Collections.emptyList(); - } - - logger.info("运单 {} 从 iot_device_data 表查询到 {} 个设备", - delivery.getDeliveryNumber(), devices.size()); - - // ✅ 按设备类型分组:1=主机,2=耳标,4=项圈 - Map> deviceMap = devices.stream() - .filter(item -> StringUtils.isNotBlank(item.getDeviceId())) - .collect(Collectors.groupingBy(device -> { - Integer deviceType = device.getDeviceType(); - // 设备类型:1=主机,2=耳标,4=项圈 - return deviceType == null ? 0 : deviceType; - }, Collectors.mapping(IotDeviceData::getDeviceId, Collectors.toList()))); - - // ✅ 记录设备分组情况 - logger.debug("运单 {} 设备分组 - 主机(1): {}, 耳标(2): {}, 项圈(4): {}", - delivery.getDeliveryNumber(), - deviceMap.getOrDefault(1, Collections.emptyList()).size(), - deviceMap.getOrDefault(2, Collections.emptyList()).size(), - deviceMap.getOrDefault(4, Collections.emptyList()).size()); - - List result = new ArrayList<>(); - int remaining = MAX_SYNC_POINTS; - - // 1=主机设备,查询 jbq_server_log 表 - remaining = appendServerLogs(deviceMap.get(1), startTime, endTime, remaining, result); - if (remaining <= 0) { - return sortPoints(result); - } - - // 2=耳标设备,查询 jbq_client_log 表 - remaining = appendEarTagLogs(deviceMap.get(2), startTime, endTime, remaining, result); - if (remaining <= 0) { - return sortPoints(result); - } - - // 4=项圈设备,查询 xq_client_log 表 - appendCollarLogs(deviceMap.get(4), startTime, endTime, remaining, result); - return sortPoints(result); - } - - private int appendServerLogs(List deviceIds, Date startTime, Date endTime, int remaining, List result) { - if (CollectionUtils.isEmpty(deviceIds) || remaining <= 0) { - return remaining; - } - List logs = jbqServerLogMapper.listLogsForYingyan(deviceIds, startTime, endTime, remaining); - if (CollectionUtils.isEmpty(logs)) { - return remaining; - } - logs.forEach(log -> { - YingyanTrackPoint point = convertToPoint(log.getLatitude(), log.getLongitude(), log.getUpdateTime()); - if (point != null) { - result.add(point); - } - }); - return Math.max(0, remaining - logs.size()); - } - - private int appendEarTagLogs(List deviceIds, Date startTime, Date endTime, int remaining, List result) { - if (CollectionUtils.isEmpty(deviceIds) || remaining <= 0) { - return remaining; - } - List logs = jbqClientLogMapper.listLogsForYingyan(deviceIds, startTime, endTime, remaining); - if (CollectionUtils.isEmpty(logs)) { - return remaining; - } - logs.forEach(log -> { - YingyanTrackPoint point = convertToPoint(log.getLatitude(), log.getLongitude(), log.getUpdateTime()); - if (point != null) { - result.add(point); - } - }); - return Math.max(0, remaining - logs.size()); - } - - private void appendCollarLogs(List deviceIds, Date startTime, Date endTime, int remaining, List result) { - if (CollectionUtils.isEmpty(deviceIds) || remaining <= 0) { - return; - } - List logs = xqClientLogMapper.listLogsForYingyan(deviceIds, startTime, endTime, remaining); - if (CollectionUtils.isEmpty(logs)) { - return; - } - logs.forEach(log -> { - YingyanTrackPoint point = convertToPoint(log.getLatitude(), log.getLongitude(), log.getUpdateTime()); - if (point != null) { - result.add(point); - } - }); - } - - private List sortPoints(List points) { - if (CollectionUtils.isEmpty(points)) { - return Collections.emptyList(); - } - return points.stream() - .sorted(Comparator.comparingLong(YingyanTrackPoint::getLocTime)) - .collect(Collectors.toList()); - } - - private YingyanTrackPoint convertToPoint(String latStr, String lonStr, Date time) { - if (StringUtils.isAnyBlank(latStr, lonStr) || time == null) { - logger.debug("转换轨迹点失败:经纬度或时间为空 - lat={}, lon={}, time={}", latStr, lonStr, time); - return null; - } - try { - double lat = Double.parseDouble(latStr); - double lon = Double.parseDouble(lonStr); - - // ✅ 详细验证坐标有效性 - if (lat == 0 && lon == 0) { - logger.debug("转换轨迹点失败:经纬度为 (0,0) - lat={}, lon={}", latStr, lonStr); - return null; - } - - if (!isValidCoordinate(lat, lon)) { - logger.debug("转换轨迹点失败:经纬度超出有效范围 - lat={}, lon={}", lat, lon); - return null; - } - - YingyanTrackPoint point = new YingyanTrackPoint(); - point.setLatitude(lat); - point.setLongitude(lon); - point.setLocTime(time.getTime() / 1000); - - logger.debug("转换轨迹点成功 - lat={}, lon={}, time={}", lat, lon, time); - return point; - } catch (NumberFormatException ex) { - logger.warn("解析经纬度失败: lat={}, lon={}, error={}", latStr, lonStr, ex.getMessage()); - return null; - } - } - - private boolean isValidCoordinate(double lat, double lon) { - return lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180; - } - - private String resolveEntityName(Delivery delivery) { - if (StringUtils.isNotBlank(delivery.getYingyanEntityName())) { - return delivery.getYingyanEntityName().trim(); - } - if (StringUtils.isNotBlank(delivery.getLicensePlate())) { - return delivery.getLicensePlate().trim(); - } - return null; - } - - private Date determineSyncStart(Delivery delivery) { - if (delivery.getYingyanLastSyncTime() != null) { - return delivery.getYingyanLastSyncTime(); - } - if (delivery.getEstimatedDeliveryTime() != null) { - return delivery.getEstimatedDeliveryTime(); - } - if (delivery.getEstimatedDepartureTime() != null) { - return delivery.getEstimatedDepartureTime(); - } - if (delivery.getCreateTime() != null) { - return delivery.getCreateTime(); - } - return new Date(System.currentTimeMillis() - Duration.ofHours(12).toMillis()); - } - - private void updateLastSync(Delivery delivery, Date newTime) { - if (newTime == null) { - return; - } - Delivery update = new Delivery(); - update.setId(delivery.getId()); - update.setYingyanLastSyncTime(newTime); - deliveryService.updateById(update); - delivery.setYingyanLastSyncTime(newTime); - } - - private void handleArrivalIfNeeded(Delivery delivery, YingyanTrackPoint point) { - if (delivery.getStatus() == null || delivery.getStatus() != 2) { - return; - } - if (StringUtils.isAnyBlank(delivery.getEndLat(), delivery.getEndLon())) { - return; - } - try { - double targetLat = Double.parseDouble(delivery.getEndLat()); - double targetLon = Double.parseDouble(delivery.getEndLon()); - double distance = calculateDistance(targetLat, targetLon, point.getLatitude(), point.getLongitude()); - if (distance <= ARRIVAL_RADIUS_METERS) { - Delivery update = new Delivery(); - update.setId(delivery.getId()); - update.setStatus(3); - Date arrivalTime = new Date(point.getLocTime() * 1000L); - update.setArrivalTime(arrivalTime); - update.setYingyanLastSyncTime(arrivalTime); - deliveryService.updateById(update); - - delivery.setStatus(3); - delivery.setArrivalTime(arrivalTime); - delivery.setYingyanLastSyncTime(arrivalTime); - - logger.info("运单 {} 已到达终点,自动更新为已结束,距离 {} 米", delivery.getDeliveryNumber(), Math.round(distance)); - } - } catch (NumberFormatException ex) { - logger.warn("运单 {} 终点经纬度格式错误: lat={}, lon={}", - delivery.getDeliveryNumber(), delivery.getEndLat(), delivery.getEndLon()); - } - } - - private double calculateDistance(double lat1, double lon1, double lat2, double lon2) { - final double R = 6378137D; - double dLat = Math.toRadians(lat2 - lat1); - double dLon = Math.toRadians(lon2 - lon1); - double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * - Math.sin(dLon / 2) * Math.sin(dLon / 2); - double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - return R * c; - } -} - diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IDeliveryService.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IDeliveryService.java index cc8c36c..58cd16a 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IDeliveryService.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IDeliveryService.java @@ -50,7 +50,10 @@ public interface IDeliveryService extends IService { PageResultResponse pageQueryListLog(DeliverListDto dto); /** - * 查询百度鹰眼轨迹与停留点 + * 查询运单轨迹(基于车牌号,使用 OpenAPI) + * + * @param deliveryId 运单ID + * @return AjaxResult */ - AjaxResult queryYingyanTrack(Integer deliveryId); + AjaxResult queryTrack(Integer deliveryId); } diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IotDeviceLogSyncService.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IotDeviceLogSyncService.java index bea7a8d..debc9cd 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IotDeviceLogSyncService.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IotDeviceLogSyncService.java @@ -74,8 +74,7 @@ public class IotDeviceLogSyncService { }) .count(); logger.info("其中 {} 个设备包含有效的经纬度坐标数据", devicesWithCoordinates); - logger.info("注意:设备日志同步任务仅将数据同步到日志表,不直接上传到百度鹰眼服务"); - logger.info("百度鹰眼轨迹上传由 DeliveryYingyanSyncService 定时任务负责"); + logger.info("注意:设备日志同步任务仅将数据同步到日志表"); int hostCount = 0; int earTagCount = 0; @@ -202,7 +201,7 @@ public class IotDeviceLogSyncService { logger.info("设备日志同步完成 - 主机: {}, 耳标: {}, 项圈: {}", hostCount, earTagCount, collarCount); logger.info("包含有效经纬度坐标的设备数量: {}", devicesWithCoordinatesCount); logger.info("========== 设备日志同步任务执行完成 =========="); - logger.info("提示:同步到日志表的经纬度数据将由 DeliveryYingyanSyncService 定时任务上传到百度鹰眼服务"); + logger.info("提示:同步到日志表的经纬度数据可用于轨迹服务"); } catch (Exception e) { logger.error("设备日志同步任务执行失败", e); diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/OpenApiTrackService.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/OpenApiTrackService.java new file mode 100644 index 0000000..2287c41 --- /dev/null +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/OpenApiTrackService.java @@ -0,0 +1,333 @@ +package com.aiotagro.cattletrade.business.service; + +import com.aiotagro.cattletrade.business.dto.openapi.OpenApiTrackPoint; +import com.aiotagro.cattletrade.properties.OpenApiProperties; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.openapi.sdk.exception.RequestLimitExceededException; +import com.openapi.sdk.exception.UnauthorizedException; +import com.openapi.sdk.service.DataExchangeService; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * OpenAPI 轨迹服务 + * 封装 OpenAPI SDK 调用,提供轨迹查询功能 + * + * @author System + * @date 2025-01-20 + */ +@Service +public class OpenApiTrackService { + + private static final Logger logger = LoggerFactory.getLogger(OpenApiTrackService.class); + + @Autowired + private OpenApiProperties openApiProperties; + + @Autowired + private ObjectMapper objectMapper; + + private DataExchangeService dataExchangeService; + + /** + * 初始化 DataExchangeService + */ + @PostConstruct + public void init() { + try { + int connectTimeout = openApiProperties.getConnectTimeout() != null + ? openApiProperties.getConnectTimeout() : 5000; + int readTimeout = openApiProperties.getReadTimeout() != null + ? openApiProperties.getReadTimeout() : 8000; + + dataExchangeService = new DataExchangeService(connectTimeout, readTimeout); + logger.info("OpenAPI DataExchangeService 初始化成功 - connectTimeout: {}, readTimeout: {}", + connectTimeout, readTimeout); + } catch (Exception e) { + logger.error("OpenAPI DataExchangeService 初始化失败", e); + throw new RuntimeException("OpenAPI SDK 初始化失败", e); + } + } + + /** + * 路径回放(状态=3 时使用 routerPath) + */ + public String queryRoutePath(String licensePlate, + Date startTime, + Date endTime, + Double startLon, + Double startLat, + Double endLon, + Double endLat) { + // vclN 去空格,不追加后缀;vco 固定 2(黄色) + String vclN = licensePlate.trim().replaceAll("\\s+", ""); + Map params = new HashMap<>(); + params.put("cid", openApiProperties.getClientId()); + params.put("srt", openApiProperties.getPrivateKey()); + params.put("vclN", vclN); + params.put("vco", "2"); + params.put("qryBtm", formatDateTime(startTime)); + params.put("qryEtm", formatDateTime(endTime)); + params.put("parkMins", "15"); + params.put("startLonlat", formatLonLat(startLon, startLat)); + params.put("endLonlat", formatLonLat(endLon, endLat)); + + String apiUrl = buildApiUrl("/save/apis/routerPath"); + logger.info("调用 OpenAPI 路径回放 - vclN: {}, vco: {}, qryBtm: {}, qryEtm: {}, startLonlat: {}, endLonlat: {}, URL: {}", + vclN, "2", params.get("qryBtm"), params.get("qryEtm"), params.get("startLonlat"), params.get("endLonlat"), apiUrl); + // 记录请求参数(不含私钥) + Map logParams = new HashMap<>(); + logParams.put("cid", openApiProperties.getClientId()); + logParams.put("vclN", vclN); + logParams.put("vco", "2"); + logParams.put("qryBtm", params.get("qryBtm")); + logParams.put("qryEtm", params.get("qryEtm")); + logParams.put("startLonlat", params.get("startLonlat")); + logParams.put("endLonlat", params.get("endLonlat")); + logParams.put("parkMins", "15"); + logger.info("OpenAPI 路径回放参数(不含srt): {}", logParams); + try { + String resp = dataExchangeService.postHttps(apiUrl, params); + logger.info("OpenAPI 路径回放响应 raw: {}", resp); + return resp; + } catch (Exception e) { + logger.error("调用路径回放接口失败 - 车牌号: {}", vclN, e); + return ""; + } + } + + /** + * 构建完整的 API URL + */ + private String buildApiUrl(String path) { + String baseUrl = openApiProperties.getUrl(); + if (StringUtils.isBlank(baseUrl)) { + throw new IllegalArgumentException("OpenAPI URL 配置为空"); + } + + // 确保 baseUrl 不以 / 结尾 + if (baseUrl.endsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + } + + // 确保 path 以 / 开头 + if (!path.startsWith("/")) { + path = "/" + path; + } + + return baseUrl + path; + } + + /** + * 解析 API 响应 + */ + private List parseResponse(String response) { + List trackPoints = new ArrayList<>(); + + if (StringUtils.isBlank(response)) { + logger.warn("OpenAPI 响应为空"); + return trackPoints; + } + + try { + JsonNode rootNode = objectMapper.readTree(response); + + // 检查响应状态 + if (rootNode.has("status")) { + int status = rootNode.path("status").asInt(-1); + // 平台文档:1001 代表服务执行成功 + if (status != 0 && status != 1001) { + String message = rootNode.path("message").asText("未知错误"); + logger.warn("OpenAPI 返回错误状态 - status: {}, message: {}", status, message); + return trackPoints; + } + } + + // 解析轨迹点数据 + // 注意:这里需要根据实际 API 返回格式调整解析逻辑 + JsonNode dataNode = rootNode.path("data"); + if (dataNode.isArray()) { + for (JsonNode pointNode : dataNode) { + OpenApiTrackPoint point = parseTrackPoint(pointNode); + if (point != null) { + trackPoints.add(point); + } + } + } else if (dataNode.isObject()) { + // 如果 data 是对象,可能包含 points 或其他字段 + JsonNode pointsNode = dataNode.path("points"); + if (pointsNode.isArray()) { + for (JsonNode pointNode : pointsNode) { + OpenApiTrackPoint point = parseTrackPoint(pointNode); + if (point != null) { + trackPoints.add(point); + } + } + } + } + + // 若未解析到轨迹点,尝试从 result.firstVcl 取最新定位(状态=1001 也视为成功) + if (trackPoints.isEmpty()) { + JsonNode resultNode = rootNode.path("result"); + JsonNode firstVcl = resultNode.path("firstVcl"); + OpenApiTrackPoint latest = parseFirstVcl(firstVcl); + if (latest != null) { + trackPoints.add(latest); + logger.info("解析 OpenAPI firstVcl 成功,返回最新定位 1 条"); + } + } + + logger.info("解析 OpenAPI 响应成功,获得 {} 个轨迹点", trackPoints.size()); + return trackPoints; + + } catch (Exception e) { + logger.error("解析 OpenAPI 响应失败 - response: {}", response, e); + return trackPoints; + } + } + + /** + * 解析 firstVcl 为最新点(lat/lon 为 1/600000 WGS84) + */ + private OpenApiTrackPoint parseFirstVcl(JsonNode firstVcl) { + if (firstVcl == null || firstVcl.isMissingNode() || firstVcl.isNull()) { + return null; + } + try { + String vno = firstVcl.path("vno").asText(""); + String latStr = firstVcl.path("lat").asText(""); + String lonStr = firstVcl.path("lon").asText(""); + if (StringUtils.isBlank(latStr) || StringUtils.isBlank(lonStr)) { + return null; + } + double lat = Double.parseDouble(latStr) / 600000d; + double lon = Double.parseDouble(lonStr) / 600000d; + + OpenApiTrackPoint point = new OpenApiTrackPoint(); + point.setLatitude(lat); + point.setLongitude(lon); + + // 时间戳 utc + long utc = firstVcl.path("utc").asLong(0); + if (utc > 0) { + point.setLocTime(utc); + } + // 速度 km/h + if (firstVcl.has("spd")) { + point.setSpeed(firstVcl.path("spd").asDouble()); + } + // 方向 + if (firstVcl.has("drc")) { + point.setDirection(firstVcl.path("drc").asDouble()); + } + logger.info("firstVcl 最新定位 - vno:{}, lat:{}, lon:{}, utc:{}, spd:{}, drc:{}", + vno, lat, lon, utc, point.getSpeed(), point.getDirection()); + return point; + } catch (Exception ex) { + logger.warn("解析 firstVcl 失败", ex); + return null; + } + } + + /** + * 解析单个轨迹点 + */ + private OpenApiTrackPoint parseTrackPoint(JsonNode pointNode) { + try { + OpenApiTrackPoint point = new OpenApiTrackPoint(); + + // 解析经纬度(根据实际 API 返回字段名调整) + if (pointNode.has("latitude") || pointNode.has("lat")) { + double lat = pointNode.path("latitude").asDouble( + pointNode.path("lat").asDouble(0)); + point.setLatitude(lat); + } + + if (pointNode.has("longitude") || pointNode.has("lng") || pointNode.has("lon")) { + double lng = pointNode.path("longitude").asDouble( + pointNode.path("lng").asDouble( + pointNode.path("lon").asDouble(0))); + point.setLongitude(lng); + } + + // 验证经纬度有效性 + if (point.getLatitude() == null || point.getLongitude() == null || + point.getLatitude() == 0 && point.getLongitude() == 0 || + point.getLatitude() < -90 || point.getLatitude() > 90 || + point.getLongitude() < -180 || point.getLongitude() > 180) { + logger.debug("跳过无效轨迹点 - lat: {}, lng: {}", + point.getLatitude(), point.getLongitude()); + return null; + } + + // 解析时间(根据实际 API 返回格式调整) + if (pointNode.has("locTime") || pointNode.has("time") || pointNode.has("timestamp")) { + long locTime = pointNode.path("locTime").asLong( + pointNode.path("time").asLong( + pointNode.path("timestamp").asLong(0))); + point.setLocTime(locTime); + } + + // 解析速度 + if (pointNode.has("speed")) { + point.setSpeed(pointNode.path("speed").asDouble()); + } + + // 解析方向 + if (pointNode.has("direction") || pointNode.has("bearing")) { + point.setDirection(pointNode.path("direction").asDouble( + pointNode.path("bearing").asDouble(0))); + } + + // 解析海拔 + if (pointNode.has("altitude") || pointNode.has("alt")) { + point.setAltitude(pointNode.path("altitude").asDouble( + pointNode.path("alt").asDouble(0))); + } + + return point; + + } catch (Exception e) { + logger.warn("解析轨迹点失败", e); + return null; + } + } + + /** + * 车牌号清洗并追加颜色后缀 + */ + private String buildVnos(String licensePlate, String colorCode) { + String clean = licensePlate.trim().replaceAll("\\s+", ""); + if (StringUtils.isNotBlank(colorCode) && !clean.endsWith("_" + colorCode)) { + clean = clean + "_" + colorCode; + } + return clean; + } + + private String formatDateTime(Date date) { + if (date == null) { + return ""; + } + // 平台未给出格式,这里使用常见格式 yyyy-MM-dd HH:mm:ss + return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); + } + + private String formatLonLat(Double lon, Double lat) { + if (lon == null || lat == null) { + return ""; + } + return String.format(java.util.Locale.US, "%.7f,%.7f", lon, lat); + } +} + diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/DeliveryServiceImpl.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/DeliveryServiceImpl.java index f026a4f..ed7d052 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/DeliveryServiceImpl.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/DeliveryServiceImpl.java @@ -5,11 +5,8 @@ import com.aiotagro.cattletrade.business.dto.DeliveryAddDto; import com.aiotagro.cattletrade.business.dto.DeliveryCreateDto; import com.aiotagro.cattletrade.business.dto.DeliveryEditDto; import com.aiotagro.cattletrade.business.dto.DeliveryQueryDto; -import com.aiotagro.cattletrade.business.dto.yingyan.YingyanStayPoint; -import com.aiotagro.cattletrade.business.dto.yingyan.YingyanTrackPoint; import com.aiotagro.cattletrade.business.entity.*; import com.aiotagro.cattletrade.business.mapper.*; -import com.aiotagro.cattletrade.business.service.BaiduYingyanService; import com.aiotagro.cattletrade.business.service.IDeliveryService; import com.aiotagro.cattletrade.business.service.IDeliveryDeviceService; import com.aiotagro.cattletrade.business.service.IXqClientService; @@ -69,14 +66,14 @@ public class DeliveryServiceImpl extends ServiceImpl i @Autowired private IDeliveryDeviceService deliveryDeviceService; - @Autowired - private BaiduYingyanService baiduYingyanService; @Autowired private IXqClientService xqClientService; @Autowired private com.aiotagro.cattletrade.business.mapper.OrderMapper orderMapper; @Autowired private com.aiotagro.cattletrade.business.mapper.VehicleMapper vehicleMapper; + @Autowired + private com.aiotagro.cattletrade.business.service.OpenApiTrackService openApiTrackService; /** * 列表查询 @@ -571,38 +568,6 @@ public class DeliveryServiceImpl extends ServiceImpl i String newLicensePlate = StringUtils.isNotEmpty(dto.getLicensePlate()) ? dto.getLicensePlate().trim() : null; delivery.setLicensePlate(newLicensePlate); logger.info("更新车牌号: oldLicensePlate={}, newLicensePlate={}", oldLicensePlate, newLicensePlate); - - // 如果车牌号发生变化,且运单状态为运输中(2),需要同步更新鹰眼终端名称 - if (StringUtils.isNotEmpty(newLicensePlate) && - delivery.getStatus() != null && - delivery.getStatus() == 2) { - - // 检查车牌号是否真的发生了变化 - boolean licensePlateChanged = oldLicensePlate == null || !newLicensePlate.equals(oldLicensePlate); - - if (licensePlateChanged) { - String newEntityName = newLicensePlate; - String oldEntityName = delivery.getYingyanEntityName(); - - // 更新鹰眼终端名称 - delivery.setYingyanEntityName(newEntityName); - - // 如果旧的终端名称存在且与新名称不同,需要重新创建终端 - if (StringUtils.isNotEmpty(oldEntityName) && !oldEntityName.equals(newEntityName)) { - logger.info("车牌号变更,重新创建鹰眼终端: oldEntityName={}, newEntityName={}", oldEntityName, newEntityName); - boolean ensureResult = baiduYingyanService.ensureEntity(newEntityName); - if (!ensureResult) { - logger.warn("运单 {} 重新创建百度鹰眼终端失败,将在后台重试", delivery.getDeliveryNumber()); - } - } else { - // 如果之前没有终端名称,直接创建 - boolean ensureResult = baiduYingyanService.ensureEntity(newEntityName); - if (!ensureResult) { - logger.warn("运单 {} 创建百度鹰眼终端失败,将在后台重试", delivery.getDeliveryNumber()); - } - } - } - } } if (dto.getBuyerId() != null) { delivery.setBuyerId(dto.getBuyerId()); @@ -1663,156 +1628,116 @@ public class DeliveryServiceImpl extends ServiceImpl i } @Override - public AjaxResult queryYingyanTrack(Integer deliveryId) { + public AjaxResult queryTrack(Integer deliveryId) { if (deliveryId == null) { return AjaxResult.error("运单ID不能为空"); } + Delivery delivery = this.getById(deliveryId); if (delivery == null) { return AjaxResult.error("运单不存在"); } + if (delivery.getStatus() == null || delivery.getStatus() == 1) { return AjaxResult.error("该运单尚未开始运输"); } - String entityName = StringUtils.defaultIfBlank(delivery.getYingyanEntityName(), delivery.getLicensePlate()); - if (StringUtils.isBlank(entityName)) { - return AjaxResult.error("缺少车牌信息,无法查询轨迹"); - } - // 去除空格,确保格式一致 - entityName = entityName.trim(); - baiduYingyanService.ensureEntity(entityName); + // 获取车牌号(供 try/catch 内外使用) + String licensePlate = delivery.getLicensePlate(); + try { + if (StringUtils.isBlank(licensePlate)) { + return AjaxResult.error("运单缺少车牌信息,无法查询轨迹"); + } - // ✅ 按照用户要求:使用预计开始时间(estimatedDeliveryTime)作为轨迹查询的起始时间 - // 如果没有 estimatedDeliveryTime,则依次尝试 estimatedDepartureTime、createTime - long startTime = dateToSeconds(delivery.getEstimatedDeliveryTime()); - if (startTime <= 0) { - startTime = dateToSeconds(delivery.getEstimatedDepartureTime()); - } - if (startTime <= 0) { - startTime = dateToSeconds(delivery.getCreateTime()); - } - if (startTime <= 0) { - startTime = System.currentTimeMillis() / 1000 - 12 * 3600; - } - - logger.info("运单 {} 轨迹查询开始时间确定 - estimatedDeliveryTime: {}, estimatedDepartureTime: {}, createTime: {}, 最终使用: {}", - delivery.getDeliveryNumber(), - delivery.getEstimatedDeliveryTime(), - delivery.getEstimatedDepartureTime(), - delivery.getCreateTime(), - new Date(startTime * 1000)); + Integer status = delivery.getStatus(); - long endTime = determineTrackEndTime(delivery); - if (endTime <= startTime) { - endTime = startTime + 3600; - } + // 状态 2 或 3:统一调用 routerPath + Date start = safeStartTime(delivery); + Date end = safeEndTime(delivery); - // ✅ 使用分段查询方法,支持超过24小时的轨迹查询 - long durationHours = Math.max(1, (endTime - startTime) / 3600); - logger.info("查询运单 {} 的百度鹰眼轨迹 - entity={}, startTime={}, endTime={}, 时间跨度={}小时", - delivery.getDeliveryNumber(), entityName, startTime, endTime, durationHours); - - // ✅ 确保终端存在后再查询(重要:如果终端不存在,查询会失败) - boolean entityExists = baiduYingyanService.ensureEntity(entityName); - if (!entityExists) { - logger.error("运单 {} 终端不存在且创建失败,无法查询轨迹 - entity={}, deliveryNumber={}, deliveryId={}", - delivery.getDeliveryNumber(), entityName, delivery.getDeliveryNumber(), delivery.getId()); - // 返回空结果,而不是继续查询(因为查询肯定会失败) + // 生成坐标 + Double startLon = parseDoubleSafe(delivery.getStartLon()); + Double startLat = parseDoubleSafe(delivery.getStartLat()); + Double endLon = parseDoubleSafe(delivery.getEndLon()); + Double endLat = parseDoubleSafe(delivery.getEndLat()); + + logger.info("查询运单 {} 的路径回放 - 车牌号: {}, qryBtm: {}, qryEtm: {}", + delivery.getDeliveryNumber(), licensePlate, start, end); + logger.debug("路径回放参数 - deliveryId:{}, status:{}, start:{}, end:{}, startLonlat:{}, endLonlat:{}", + deliveryId, status, start, end, formatLonLat(startLon, startLat), formatLonLat(endLon, endLat)); + + String response = openApiTrackService.queryRoutePath(licensePlate, start, end, startLon, startLat, endLon, endLat); Map data = new HashMap<>(); - data.put("trackPoints", Collections.emptyList()); - data.put("stayPoints", Collections.emptyList()); - data.put("entityName", entityName); - data.put("startTime", startTime * 1000); - data.put("endTime", endTime * 1000); - data.put("status", delivery.getStatus()); - data.put("error", "终端不存在且创建失败,请检查百度鹰眼服务配置或终端名称是否正确"); + data.put("rawResponse", response); + data.put("licensePlate", licensePlate); + data.put("status", status); return AjaxResult.success(data); + + } catch (Exception e) { + logger.error("查询运单 {} 轨迹异常 - 车牌号: {}", + delivery.getDeliveryNumber(), licensePlate, e); + return AjaxResult.error("查询轨迹失败:" + e.getMessage()); } - - logger.info("运单 {} 终端已确保存在,开始查询轨迹 - entity={}", - delivery.getDeliveryNumber(), entityName); - - List segmentRanges = buildTrackSegments(startTime, endTime); - logger.info("运单 {} 将分 {} 段调用百度鹰眼接口(每段≤24小时)", delivery.getDeliveryNumber(), segmentRanges.size()); - - List mergedTrackPoints = new ArrayList<>(); - List mergedStayPoints = new ArrayList<>(); - List> segmentStats = new ArrayList<>(); - - for (int i = 0; i < segmentRanges.size(); i++) { - long[] range = segmentRanges.get(i); - long segStart = range[0]; - long segEnd = range[1]; - logger.info("运单 {} 调用轨迹接口-第{}段: {} -> {}", - delivery.getDeliveryNumber(), i + 1, new Date(segStart * 1000), new Date(segEnd * 1000)); - - List segmentTracks = baiduYingyanService.queryTrack(entityName, segStart, segEnd); - logger.info("运单 {} 第{}段轨迹点数量:{}", delivery.getDeliveryNumber(), i + 1, segmentTracks.size()); - mergedTrackPoints.addAll(segmentTracks); - - List segmentStayPoints = baiduYingyanService.queryStayPoints(entityName, segStart, segEnd, 900); - logger.info("运单 {} 第{}段停留点数量:{}", delivery.getDeliveryNumber(), i + 1, segmentStayPoints.size()); - mergedStayPoints.addAll(segmentStayPoints); - - Map segmentInfo = new HashMap<>(); - segmentInfo.put("index", i + 1); - segmentInfo.put("startTime", segStart * 1000); - segmentInfo.put("endTime", segEnd * 1000); - segmentInfo.put("trackCount", segmentTracks.size()); - segmentInfo.put("stayCount", segmentStayPoints.size()); - segmentStats.add(segmentInfo); - } - - List trackPoints = dedupTrackPoints(mergedTrackPoints); - List stayPoints = dedupStayPoints(mergedStayPoints); - - logger.info("运单 {} 轨迹查询完成 - 分段:{}段, 合并后轨迹点数:{}, 停留点数:{}", - delivery.getDeliveryNumber(), segmentRanges.size(), trackPoints.size(), stayPoints.size()); - - YingyanTrackPoint latestPoint = null; - if (delivery.getStatus() != null && delivery.getStatus() == 2) { - latestPoint = baiduYingyanService.queryLatestPoint(entityName); - if (latestPoint != null) { - logger.info("运单 {} 最新轨迹点:lat={}, lon={}, locTime={}", - delivery.getDeliveryNumber(), latestPoint.getLatitude(), latestPoint.getLongitude(), latestPoint.getLocTime()); - } else { - logger.warn("运单 {} 查询最新轨迹点返回为空", delivery.getDeliveryNumber()); - } - } else { - logger.info("运单 {} 状态为 {},跳过实时定位查询", delivery.getDeliveryNumber(), delivery.getStatus()); - } - - Map data = new HashMap<>(); - data.put("trackPoints", trackPoints); - data.put("stayPoints", stayPoints); - data.put("entityName", entityName); - data.put("startTime", startTime * 1000); - data.put("endTime", endTime * 1000); - data.put("status", delivery.getStatus()); - data.put("segmentStats", segmentStats); - if (latestPoint != null) { - data.put("latestPoint", latestPoint); - } - - return AjaxResult.success(data); } - private long determineTrackEndTime(Delivery delivery) { - if (delivery.getStatus() != null && delivery.getStatus() == 3) { - long arrivalTime = dateToSeconds(delivery.getArrivalTime()); - if (arrivalTime > 0) { - return arrivalTime; - } + /** + * 状态=2:计算 timeNearby(分钟) + * 按 estimatedDeliveryTime - estimatedDepartureTime,若结束时间在未来则用当前时间 + */ + private String calculateNearbyByEstimate(Delivery delivery) { + Date start = safeStartTime(delivery); + Date end = safeEndTime(delivery); + if (start == null || end == null) { + return "30"; // 无时间信息时给默认 30 分钟 } - return System.currentTimeMillis() / 1000; + long diffMillis = end.getTime() - start.getTime(); + long diffMinutes = diffMillis / (1000 * 60); + if (diffMinutes < 1) { + diffMinutes = 1; + } + // 平台常见上限 1440/720,防止过大:这里上限 1440 + if (diffMinutes > 1440) { + diffMinutes = 1440; + } + return String.valueOf(diffMinutes); } - private long dateToSeconds(Date date) { - if (date == null) { - return 0; + private Date safeStartTime(Delivery delivery) { + if (delivery.getEstimatedDepartureTime() != null) { + return delivery.getEstimatedDepartureTime(); + } + if (delivery.getCreateTime() != null) { + return delivery.getCreateTime(); + } + return new Date(); + } + + private Date safeEndTime(Delivery delivery) { + Date end = delivery.getEstimatedDeliveryTime() != null + ? delivery.getEstimatedDeliveryTime() : new Date(); + Date now = new Date(); + if (end.after(now)) { + end = now; + } + return end; + } + + private String formatLonLat(Double lon, Double lat) { + if (lon == null || lat == null) { + return ""; + } + return String.format(java.util.Locale.US, "%.7f,%.7f", lon, lat); + } + + private Double parseDoubleSafe(String val) { + if (StringUtils.isBlank(val)) { + return null; + } + try { + return Double.parseDouble(val); + } catch (NumberFormatException e) { + return null; } - return date.getTime() / 1000; } /** @@ -1845,53 +1770,6 @@ public class DeliveryServiceImpl extends ServiceImpl i } } - private static final long MAX_SEGMENT_SECONDS = 24 * 60 * 60; - - private List buildTrackSegments(long startTime, long endTime) { - List segments = new ArrayList<>(); - long cursor = startTime; - while (cursor < endTime) { - long next = Math.min(cursor + MAX_SEGMENT_SECONDS, endTime); - segments.add(new long[]{cursor, next}); - cursor = next; - } - return segments; - } - - private List dedupTrackPoints(List points) { - if (CollectionUtils.isEmpty(points)) { - return Collections.emptyList(); - } - points.sort(Comparator.comparingLong(YingyanTrackPoint::getLocTime) - .thenComparingDouble(YingyanTrackPoint::getLatitude) - .thenComparingDouble(YingyanTrackPoint::getLongitude)); - Set seen = new HashSet<>(); - List unique = new ArrayList<>(); - for (YingyanTrackPoint point : points) { - String key = point.getLocTime() + "_" + point.getLatitude() + "_" + point.getLongitude(); - if (seen.add(key)) { - unique.add(point); - } - } - return unique; - } - - private List dedupStayPoints(List points) { - if (CollectionUtils.isEmpty(points)) { - return Collections.emptyList(); - } - points.sort(Comparator.comparingLong(YingyanStayPoint::getStartTime) - .thenComparingLong(YingyanStayPoint::getEndTime)); - Set seen = new HashSet<>(); - List unique = new ArrayList<>(); - for (YingyanStayPoint point : points) { - String key = point.getStartTime() + "_" + point.getEndTime() + "_" + point.getLatitude() + "_" + point.getLongitude(); - if (seen.add(key)) { - unique.add(point); - } - } - return unique; - } /** * 批量填充 Delivery 列表的关联信息(优化性能,避免 N+1 查询) diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/job/BaiduYingyanSyncJob.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/job/BaiduYingyanSyncJob.java deleted file mode 100644 index 6a74dbf..0000000 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/job/BaiduYingyanSyncJob.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.aiotagro.cattletrade.job; - -import com.aiotagro.cattletrade.business.service.DeliveryYingyanSyncService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -/** - * 百度鹰眼轨迹同步任务 - */ -@Component -public class BaiduYingyanSyncJob { - - private static final Logger logger = LoggerFactory.getLogger(BaiduYingyanSyncJob.class); - - @Autowired - private DeliveryYingyanSyncService deliveryYingyanSyncService; - - /** - * 每两分钟同步一次轨迹 - */ - @Scheduled(initialDelay = 30_000, fixedDelay = 120_000) - public void syncDeliveryTrack() { - try { - logger.debug("开始执行运单鹰眼轨迹同步任务"); - deliveryYingyanSyncService.syncActiveDeliveries(); - } catch (Exception e) { - logger.error("运单鹰眼轨迹同步任务执行失败", e); - } - } -} - diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/properties/OpenApiProperties.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/properties/OpenApiProperties.java new file mode 100644 index 0000000..5a2aabb --- /dev/null +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/properties/OpenApiProperties.java @@ -0,0 +1,54 @@ +package com.aiotagro.cattletrade.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * OpenAPI 配置属性类 + * 用于读取 OpenAPI SDK 的配置信息 + * + * @author System + * @date 2025-01-20 + */ +@Data +@Component +@ConfigurationProperties(prefix = "openapi") +public class OpenApiProperties { + + /** + * API 地址(测试环境:https://openapi-test.sinoiov.cn) + */ + private String url; + + /** + * 客户端ID + */ + private String clientId; + + /** + * 私钥(用于 SDK 内部生成签名) + */ + private String privateKey; + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 连接超时时间(毫秒),默认 5000 + */ + private Integer connectTimeout = 5000; + + /** + * 读取超时时间(毫秒),默认 8000 + */ + private Integer readTimeout = 8000; +} + diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/common/core/constant/BaiduYingyanConstants.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/common/core/constant/BaiduYingyanConstants.java deleted file mode 100644 index ac48505..0000000 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/common/core/constant/BaiduYingyanConstants.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.aiotagro.common.core.constant; - -/** - * 百度鹰眼常量配置 - * - *

注意:AK 与 ServiceId 根据业务要求写死在后端,禁止透出给前端。

- */ -public final class BaiduYingyanConstants { - - private BaiduYingyanConstants() { - } - - /** - * 百度鹰眼控制台申请的 AK - */ - public static final String AK = "3AN3VahoqaXUs32U8luXD2Dwn86KK5B7"; - - /** - * 百度鹰眼服务 ID - */ - public static final long SERVICE_ID = 242517L; - - /** - * 百度鹰眼 API 基础路径 - */ - public static final String BASE_URL = "https://yingyan.baidu.com/api/v3"; -} - diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/.classpath b/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/.classpath new file mode 100644 index 0000000..ea14e61 --- /dev/null +++ b/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/.project b/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/.project new file mode 100644 index 0000000..fb88512 --- /dev/null +++ b/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/.project @@ -0,0 +1,23 @@ + + + openapi-sdk + + + + + + org.eclipse.jdt.core.javabuilder + + + + + edu.umd.cs.findbugs.plugin.eclipse.findbugsBuilder + + + + + + org.eclipse.jdt.core.javanature + edu.umd.cs.findbugs.plugin.eclipse.findbugsNature + + diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/META-INF/MANIFEST.MF b/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/META-INF/MANIFEST.MF new file mode 100644 index 0000000..59499bc --- /dev/null +++ b/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/build.xml b/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/build.xml new file mode 100644 index 0000000..d27f319 --- /dev/null +++ b/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/build.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + === COMPILE === + Compiling ${src.dir} files ... + + + + + + === PACKAGE === + + + + + \ No newline at end of file diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/openapi-sdk.iml b/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/openapi-sdk.iml new file mode 100644 index 0000000..ef61f98 --- /dev/null +++ b/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/openapi-sdk.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/version.txt b/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/version.txt new file mode 100644 index 0000000..957657c --- /dev/null +++ b/tradeCattle/aiotagro-cattle-trade/src/main/resources/api/version.txt @@ -0,0 +1,11 @@ +2018年11月22日 +去掉Apache第三方相关的包 + +2018年2月24日 sdk去des解密 +2017年9月29日 +去除白名单异常类。 + +2017年5月10日 +开放平台SDK 包路径更换。 +-------------------- +数据共享平台-调用HTTPS请求客户端API \ No newline at end of file diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/resources/application.yml b/tradeCattle/aiotagro-cattle-trade/src/main/resources/application.yml index 6bba8a6..f1f1760 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/resources/application.yml +++ b/tradeCattle/aiotagro-cattle-trade/src/main/resources/application.yml @@ -91,7 +91,13 @@ sms: template-id: 2175348 openapi: - url: http://api.aiotagro.com/api/business/xq/locationLog + url: https://openapi-test.sinoiov.cn # 测试环境 + client-id: 262e6525-a1c8-43a1-aa35-b7d66f587a19 + private-key: 6b4d3006-19ce-4910-92e0-3ad4849f7af3 + username: 32d4175c-1c30-4350-83cc-dffdbde3b004 + password: HI57V6dZg26e92298539m971oLBHo7 + connect-timeout: 5000 + read-timeout: 8000 iot: url: http://aiot.aiotagro.com/iotPlateform/iotBusiness/sendCmd