已经成功集成百度鹰眼服务

This commit is contained in:
xuqiuyun
2025-11-24 16:10:39 +08:00
parent 0f963bf535
commit f45a57fad6
6 changed files with 1017 additions and 551 deletions

View File

@@ -16,7 +16,6 @@ import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.time.Duration;
import java.util.Date;
@@ -53,41 +52,46 @@ public class BaiduYingyanService {
return false;
}
// ✅ 去除空格,确保格式一致
String cleanEntityName = entityName.trim();
// ✅ 验证 entityName 不是 "entity" 字符串
if ("entity".equals(entityName) || "entity_name".equals(entityName)) {
logger.error("ensureEntity 失败entityName 参数错误,值为 '{}',这可能是参数传递错误", entityName);
if ("entity".equals(cleanEntityName) || "entity_name".equals(cleanEntityName)) {
logger.error("ensureEntity 失败entityName 参数错误,值为 '{}',这可能是参数传递错误", cleanEntityName);
return false;
}
try {
MultiValueMap<String, String> form = baseForm();
form.add("entity_name", entityName);
form.add("entity_name", cleanEntityName);
// entity_desc 为非必填项,且命名规则限制:只支持中文、英文字母、下划线、连字符、数字
// 为避免参数错误,不传递 entity_desc 参数
logger.debug("确保终端存在 - entity={}", entityName);
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={}", entityName);
logger.info("✅ 鹰眼终端创建成功, entityName={}, service_id={}", cleanEntityName, BaiduYingyanConstants.SERVICE_ID);
return true;
} else if (status == 3005) {
// ✅ 终端已存在,这是正常情况,视为成功
logger.info("✅ 鹰眼终端已存在, entityName={}, message={}", entityName, message);
logger.info("✅ 鹰眼终端已存在, entityName={}, service_id={}, message={}",
cleanEntityName, BaiduYingyanConstants.SERVICE_ID, message);
return true;
} else if (status == 3006) {
logger.info("✅ 鹰眼终端操作成功, entityName={}, status={}", entityName, status);
logger.info("✅ 鹰眼终端操作成功, entityName={}, service_id={}, status={}",
cleanEntityName, BaiduYingyanConstants.SERVICE_ID, status);
return true;
}
// 其他状态码视为失败
logger.warn("❌ 鹰眼创建终端失败, entityName={}, status={}, message={}",
entityName, status, message);
logger.warn("❌ 鹰眼创建终端失败, entityName={}, service_id={}, status={}, message={}",
cleanEntityName, BaiduYingyanConstants.SERVICE_ID, status, message);
} catch (Exception e) {
logger.error("❌ 鹰眼创建终端异常, entityName={}", entityName, e);
logger.error("❌ 鹰眼创建终端异常, entityName={}", cleanEntityName, e);
}
return false;
}
@@ -155,6 +159,105 @@ public class BaiduYingyanService {
return false;
}
/**
* 批量推送轨迹点(使用 /track/addpoints 接口)
* @param entityName 终端名称
* @param points 轨迹点列表
* @return 成功上传的轨迹点数量
*/
public int pushTrackPoints(String entityName, List<YingyanTrackPoint> 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<Map<String, Object>> 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<String, Object> 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<String, String> 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小时内
*/
@@ -162,15 +265,31 @@ public class BaiduYingyanService {
if (StringUtils.isBlank(entityName)) {
return Collections.emptyList();
}
// ✅ 查询前再次确保终端存在,避免查询时终端不存在
if (!ensureEntity(entityName)) {
logger.warn("查询轨迹前终端不存在或创建失败,无法查询 - entity={}", entityName);
return Collections.emptyList();
}
try {
// ✅ 去除空格,确保格式一致
String cleanEntityName = entityName.trim();
Map<String, Object> params = new HashMap<>();
params.put("entity_name", entityName);
params.put("start_time", startTime);
params.put("end_time", endTime);
params.put("is_processed", 1);
params.put("need_denoise", 1);
params.put("need_mapmatch", 0);
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));
@@ -179,24 +298,42 @@ public class BaiduYingyanService {
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.debug("鹰眼查询轨迹:指定时间范围内可能无轨迹数据, entity={}, startTime={}, endTime={}, message={}",
logger.info("鹰眼查询轨迹:指定时间范围内可能无轨迹数据, 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));
logger.warn("鹰眼查询轨迹失败, entity={}, status={}, message={}, startTime={}, endTime={}, raw={}",
entityName, status, message, new Date(startTime * 1000), new Date(endTime * 1000), result.toString());
}
return Collections.emptyList();
}
JsonNode pointsNode = result.path("track").path("points");
if (pointsNode == null || !pointsNode.isArray() || pointsNode.size() == 0) {
// 文档返回结构存在两种形式:直接 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<YingyanTrackPoint> points = new ArrayList<>(pointsNode.size());
pointsNode.forEach(node -> {
if (!node.hasNonNull("latitude") || !node.hasNonNull("longitude")) {
@@ -221,6 +358,75 @@ public class BaiduYingyanService {
}
}
/**
* 查询最新轨迹点(实时位置)
*/
public YingyanTrackPoint queryLatestPoint(String entityName) {
if (StringUtils.isBlank(entityName)) {
logger.warn("查询最新轨迹点失败:终端名称为空");
return null;
}
try {
// ✅ 查询前再次确保终端存在
if (!ensureEntity(entityName)) {
logger.warn("查询最新轨迹点前终端不存在或创建失败entity={}", entityName);
return null;
}
Map<String, Object> 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小时为间隔分段查询然后拼接结果
@@ -359,9 +565,10 @@ public class BaiduYingyanService {
try {
Map<String, Object> params = new HashMap<>();
params.put("entity_name", entityName);
params.put("start_time", startTime);
params.put("end_time", endTime);
params.put("stay_time", stayTimeSeconds);
// ✅ 所有参数统一为 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={}",
@@ -541,25 +748,36 @@ public class BaiduYingyanService {
}
private JsonNode get(String path, Map<String, Object> params) throws Exception {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(buildUrl(path))
.queryParam("ak", BaiduYingyanConstants.AK)
.queryParam("service_id", BaiduYingyanConstants.SERVICE_ID);
// ✅ 手动构建 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) {
params.forEach((key, value) -> {
for (Map.Entry<String, Object> 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);
}
builder.queryParam(key, value);
// ✅ 所有参数都不进行 URL 编码,直接拼接(与用户测试成功的 URL 格式一致)
urlBuilder.append("&").append(key).append("=").append(value.toString());
}
});
}
}
// ✅ 记录请求URL调试用生产环境可关闭
String requestUrl = builder.toUriString();
logger.debug("百度鹰眼API请求 - path={}, url={}", path, requestUrl.replaceAll("ak=[^&]+", "ak=***"));
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);

View File

@@ -155,43 +155,35 @@ public class DeliveryYingyanSyncService {
return 0;
}
logger.info("运单 {} 找到 {} 个有效轨迹点,开始上传到百度鹰眼",
logger.info("运单 {} 找到 {} 个有效轨迹点,开始批量上传到百度鹰眼",
delivery.getDeliveryNumber(), points.size());
int successCount = 0;
int failCount = 0;
for (YingyanTrackPoint point : points) {
boolean success = baiduYingyanService.pushTrackPoint(
entityName, point.getLatitude(), point.getLongitude(), point.getLocTime());
if (success) {
successCount++;
Date locDate = new Date(point.getLocTime() * 1000L);
updateLastSync(delivery, locDate);
handleArrivalIfNeeded(delivery, point);
if (successCount <= 3 || successCount % 20 == 0) {
logger.debug("运单 {} 上传轨迹点成功 [{}/{}] - 纬度: {}, 经度: {}, 时间: {}",
delivery.getDeliveryNumber(), successCount, points.size(),
point.getLatitude(), point.getLongitude(), locDate);
}
} else {
failCount++;
if (failCount <= 3) {
logger.warn("运单 {} 上传轨迹点失败 [{}/{}] - 纬度: {}, 经度: {}, 时间: {}",
delivery.getDeliveryNumber(), failCount, points.size(),
point.getLatitude(), point.getLongitude(),
new Date(point.getLocTime() * 1000L));
}
}
if (delivery.getStatus() != null && delivery.getStatus() == 3) {
logger.info("运单 {} 状态已变为已结束,停止同步", delivery.getDeliveryNumber());
break;
}
// ✅ 检查运单状态,如果已结束则停止同步
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());
}
logger.info("运单 {} 轨迹上传完成 - 成功: {}, 失败: {}, 总计: {}",
delivery.getDeliveryNumber(), successCount, failCount, points.size());
return successCount;
}

View File

@@ -2071,10 +2071,14 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
new Date(startTime * 1000));
long endTime = determineTrackEndTime(delivery);
if (endTime <= startTime) {
endTime = startTime + 3600;
}
// ✅ 使用分段查询方法支持超过24小时的轨迹查询
long durationHours = Math.max(1, (endTime - startTime) / 3600);
logger.info("查询运单 {} 的百度鹰眼轨迹 - entity={}, startTime={}, endTime={}, 时间跨度={}小时",
delivery.getDeliveryNumber(), entityName, startTime, endTime, (endTime - startTime) / 3600);
delivery.getDeliveryNumber(), entityName, startTime, endTime, durationHours);
// ✅ 确保终端存在后再查询(重要:如果终端不存在,查询会失败)
boolean entityExists = baiduYingyanService.ensureEntity(entityName);
@@ -2093,14 +2097,58 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
return AjaxResult.success(data);
}
logger.debug("运单 {} 终端已确保存在,开始查询轨迹 - entity={}",
logger.info("运单 {} 终端已确保存在,开始查询轨迹 - entity={}",
delivery.getDeliveryNumber(), entityName);
List<YingyanTrackPoint> trackPoints = baiduYingyanService.queryTrackSegmented(entityName, startTime, endTime);
List<YingyanStayPoint> stayPoints = baiduYingyanService.queryStayPointsSegmented(entityName, startTime, endTime, 900);
logger.info("运单 {} 轨迹查询完成 - 轨迹点数: {}, 停留点数: {}",
delivery.getDeliveryNumber(), trackPoints.size(), stayPoints.size());
List<long[]> segmentRanges = buildTrackSegments(startTime, endTime);
logger.info("运单 {} 将分 {} 段调用百度鹰眼接口每段≤24小时", delivery.getDeliveryNumber(), segmentRanges.size());
List<YingyanTrackPoint> mergedTrackPoints = new ArrayList<>();
List<YingyanStayPoint> mergedStayPoints = new ArrayList<>();
List<Map<String, Object>> 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<YingyanTrackPoint> segmentTracks = baiduYingyanService.queryTrack(entityName, segStart, segEnd);
logger.info("运单 {} 第{}段轨迹点数量:{}", delivery.getDeliveryNumber(), i + 1, segmentTracks.size());
mergedTrackPoints.addAll(segmentTracks);
List<YingyanStayPoint> segmentStayPoints = baiduYingyanService.queryStayPoints(entityName, segStart, segEnd, 900);
logger.info("运单 {} 第{}段停留点数量:{}", delivery.getDeliveryNumber(), i + 1, segmentStayPoints.size());
mergedStayPoints.addAll(segmentStayPoints);
Map<String, Object> 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<YingyanTrackPoint> trackPoints = dedupTrackPoints(mergedTrackPoints);
List<YingyanStayPoint> 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<String, Object> data = new HashMap<>();
data.put("trackPoints", trackPoints);
@@ -2109,6 +2157,10 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
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);
}
@@ -2160,5 +2212,53 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
}
}
private static final long MAX_SEGMENT_SECONDS = 24 * 60 * 60;
private List<long[]> buildTrackSegments(long startTime, long endTime) {
List<long[]> 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<YingyanTrackPoint> dedupTrackPoints(List<YingyanTrackPoint> points) {
if (CollectionUtils.isEmpty(points)) {
return Collections.emptyList();
}
points.sort(Comparator.comparingLong(YingyanTrackPoint::getLocTime)
.thenComparingDouble(YingyanTrackPoint::getLatitude)
.thenComparingDouble(YingyanTrackPoint::getLongitude));
Set<String> seen = new HashSet<>();
List<YingyanTrackPoint> 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<YingyanStayPoint> dedupStayPoints(List<YingyanStayPoint> points) {
if (CollectionUtils.isEmpty(points)) {
return Collections.emptyList();
}
points.sort(Comparator.comparingLong(YingyanStayPoint::getStartTime)
.thenComparingLong(YingyanStayPoint::getEndTime));
Set<String> seen = new HashSet<>();
List<YingyanStayPoint> 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;
}
}

View File

@@ -0,0 +1,3 @@
artifactId=aiotagro-redis
groupId=com.aiotagro
version=1.0.1