集成百度鹰眼服务AK
This commit is contained in:
@@ -7,11 +7,13 @@ 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.DeliveryTrackQueryDto;
|
||||
import com.aiotagro.cattletrade.business.dto.DeviceAssignDto;
|
||||
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;
|
||||
@@ -85,6 +87,9 @@ public class DeliveryController {
|
||||
@Autowired
|
||||
private OrderMapper orderMapper;
|
||||
|
||||
@Autowired
|
||||
private BaiduYingyanService baiduYingyanService;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -594,8 +599,45 @@ public class DeliveryController {
|
||||
if (existDelivery == null) {
|
||||
return AjaxResult.error("运单不存在");
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (status == 2) {
|
||||
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);
|
||||
arrivalUpdate.setArrivalTime(new Date());
|
||||
deliveryService.updateById(arrivalUpdate);
|
||||
}
|
||||
|
||||
Delivery delivery = new Delivery();
|
||||
delivery.setId(id);
|
||||
delivery.setStatus(status);
|
||||
@@ -614,6 +656,15 @@ public class DeliveryController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询运送清单百度鹰眼轨迹/停留点
|
||||
*/
|
||||
@SaCheckPermission("delivery:view")
|
||||
@PostMapping("/yingyan/track")
|
||||
public AjaxResult getYingyanTrack(@Validated @RequestBody DeliveryTrackQueryDto dto) {
|
||||
return deliveryService.queryYingyanTrack(dto.getDeliveryId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除装车订单(物理删除)
|
||||
* 删除订单时同时清空关联设备的delivery_id和weight
|
||||
@@ -671,7 +722,7 @@ public class DeliveryController {
|
||||
|
||||
/**
|
||||
* 逻辑删除运送清单
|
||||
* 只标记为已删除,不清空设备绑定关系,保留历史记录
|
||||
* 逻辑删除时同时清空关联设备的delivery_id和car_number
|
||||
*/
|
||||
@SaCheckPermission("delivery:delete")
|
||||
@PostMapping("/deleteLogic")
|
||||
@@ -694,12 +745,36 @@ public class DeliveryController {
|
||||
return AjaxResult.error("无权限删除此运单");
|
||||
}
|
||||
|
||||
// 1. 先清空关联设备的delivery_id和car_number
|
||||
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<com.aiotagro.cattletrade.business.entity.IotDeviceData> queryWrapper =
|
||||
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
|
||||
queryWrapper.eq("delivery_id", id);
|
||||
List<com.aiotagro.cattletrade.business.entity.IotDeviceData> devices = iotDeviceDataMapper.selectList(queryWrapper);
|
||||
|
||||
// 使用 MyBatis-Plus 的逻辑删除功能
|
||||
int updatedCount = 0;
|
||||
for (com.aiotagro.cattletrade.business.entity.IotDeviceData device : devices) {
|
||||
// 使用LambdaUpdateWrapper确保null值也能被更新
|
||||
com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<com.aiotagro.cattletrade.business.entity.IotDeviceData> updateWrapper =
|
||||
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(com.aiotagro.cattletrade.business.entity.IotDeviceData::getDeviceId, device.getDeviceId())
|
||||
.set(com.aiotagro.cattletrade.business.entity.IotDeviceData::getDeliveryId, null)
|
||||
.set(com.aiotagro.cattletrade.business.entity.IotDeviceData::getCarNumber, null)
|
||||
.set(com.aiotagro.cattletrade.business.entity.IotDeviceData::getUpdateTime, new Date());
|
||||
|
||||
int result = iotDeviceDataMapper.update(null, updateWrapper);
|
||||
if (result > 0) {
|
||||
updatedCount++;
|
||||
logger.debug("清空设备绑定信息: deviceId={}, delivery_id=null, car_number=null", device.getDeviceId());
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("逻辑删除运送清单,已清空 {} 个设备的绑定信息,运单ID: {}", updatedCount, id);
|
||||
|
||||
// 2. 使用 MyBatis-Plus 的逻辑删除功能
|
||||
boolean deleted = deliveryService.removeById(id);
|
||||
|
||||
if (deleted) {
|
||||
return AjaxResult.success("运单删除成功");
|
||||
return AjaxResult.success("运单删除成功,已清空 " + updatedCount + " 个设备的绑定信息");
|
||||
} else {
|
||||
return AjaxResult.error("运单删除失败");
|
||||
}
|
||||
|
||||
@@ -53,13 +53,19 @@ public class IotDeviceProxyController {
|
||||
// 只查询正常状态的设备(is_delet=0)
|
||||
queryWrapper.and(wrapper -> wrapper.eq("is_delet", 0).or().isNull("is_delet"));
|
||||
|
||||
// 根据设备类型查询(用于创建运送清单时过滤设备)
|
||||
// 根据设备类型查询
|
||||
if (params.containsKey("type") && params.get("type") != null) {
|
||||
Integer deviceType = (Integer) params.get("type");
|
||||
queryWrapper.eq("device_type", deviceType);
|
||||
// 创建运送清单时,只显示未绑定的设备(delivery_id为空)
|
||||
queryWrapper.isNull("delivery_id");
|
||||
logger.info("查询未绑定的设备,类型: {}", deviceType);
|
||||
// 只有当 allotType 参数存在且为 "unassigned" 时,才限制 delivery_id 为空
|
||||
// 这是为了在创建运送清单时只显示未绑定的设备
|
||||
// 硬件管理页面(eartag.vue, host.vue, collar.vue)不传递 allotType,会显示所有设备
|
||||
if (params.containsKey("allotType") && "unassigned".equals(params.get("allotType"))) {
|
||||
queryWrapper.isNull("delivery_id");
|
||||
logger.info("查询未绑定的设备,类型: {}", deviceType);
|
||||
} else {
|
||||
logger.info("查询所有设备(包括已分配的),类型: {}", deviceType);
|
||||
}
|
||||
}
|
||||
|
||||
// 根据设备ID查询
|
||||
|
||||
@@ -237,6 +237,7 @@ public class MemberController {
|
||||
*/
|
||||
@SaCheckPermission("member:edit")
|
||||
@PostMapping("/updateDriver")
|
||||
@Transactional
|
||||
public AjaxResult updateDriver(@RequestBody Map<String, Object> params) {
|
||||
try {
|
||||
Integer id = (Integer) params.get("id");
|
||||
@@ -246,6 +247,7 @@ public class MemberController {
|
||||
|
||||
// 获取参数值
|
||||
String username = (String) params.get("username");
|
||||
String mobile = (String) params.get("mobile");
|
||||
String driverLicense = (String) params.get("driverLicense");
|
||||
String drivingLicense = (String) params.get("drivingLicense");
|
||||
String carImg = (String) params.get("carImg");
|
||||
@@ -253,7 +255,48 @@ public class MemberController {
|
||||
String idCard = (String) params.get("idCard");
|
||||
String remark = (String) params.get("remark");
|
||||
|
||||
// 执行更新(不再包含carNumber参数)
|
||||
// 获取司机的member_id
|
||||
Map<String, Object> driverInfo = memberDriverMapper.selectDriverById(id);
|
||||
if (driverInfo == null) {
|
||||
return AjaxResult.error("司机信息不存在");
|
||||
}
|
||||
|
||||
// member_id 从数据库返回时可能是Long类型,需要转换为Integer
|
||||
Object memberIdObj = driverInfo.get("member_id");
|
||||
Integer memberId = null;
|
||||
if (memberIdObj != null) {
|
||||
if (memberIdObj instanceof Long) {
|
||||
memberId = ((Long) memberIdObj).intValue();
|
||||
} else if (memberIdObj instanceof Integer) {
|
||||
memberId = (Integer) memberIdObj;
|
||||
} else if (memberIdObj instanceof Number) {
|
||||
memberId = ((Number) memberIdObj).intValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (memberId == null) {
|
||||
return AjaxResult.error("司机关联的会员ID不存在");
|
||||
}
|
||||
|
||||
// 如果提供了新手机号,检查是否与其他用户重复
|
||||
if (mobile != null && !mobile.trim().isEmpty()) {
|
||||
String currentMobile = (String) driverInfo.get("mobile");
|
||||
// 如果手机号有变化,检查新手机号是否已存在
|
||||
if (currentMobile == null || !currentMobile.equals(mobile.trim())) {
|
||||
Integer existingMemberId = memberMapper.selectMemberIdByMobile(mobile.trim());
|
||||
if (existingMemberId != null && !existingMemberId.equals(memberId)) {
|
||||
return AjaxResult.error("该手机号已被其他用户使用");
|
||||
}
|
||||
}
|
||||
|
||||
// 更新member表的手机号
|
||||
int mobileUpdateResult = memberMapper.updateMemberMobile(memberId, mobile.trim());
|
||||
if (mobileUpdateResult <= 0) {
|
||||
return AjaxResult.error("更新手机号失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 执行更新member_driver表
|
||||
int result = memberDriverMapper.updateDriver(id, username, driverLicense,
|
||||
drivingLicense, carImg, recordCode, idCard, remark);
|
||||
|
||||
@@ -288,6 +331,14 @@ public class MemberController {
|
||||
String cbkAccount = (String) params.get("cbkAccount");
|
||||
String remark = (String) params.get("remark");
|
||||
|
||||
// 处理mobile和cbkAccount:如果为空字符串,设置为null
|
||||
if (mobile != null && mobile.trim().isEmpty()) {
|
||||
mobile = null;
|
||||
}
|
||||
if (cbkAccount != null && cbkAccount.trim().isEmpty()) {
|
||||
cbkAccount = null;
|
||||
}
|
||||
|
||||
// 执行更新
|
||||
int result = memberMapper.updateUserInfo(id, mobile, type, status, username, cbkAccount, remark);
|
||||
|
||||
@@ -318,9 +369,6 @@ public class MemberController {
|
||||
String remark = (String) params.get("remark");
|
||||
|
||||
// 参数验证
|
||||
if (mobile == null || mobile.trim().isEmpty()) {
|
||||
return AjaxResult.error("手机号不能为空");
|
||||
}
|
||||
if (username == null || username.trim().isEmpty()) {
|
||||
return AjaxResult.error("用户姓名不能为空");
|
||||
}
|
||||
@@ -328,10 +376,17 @@ public class MemberController {
|
||||
return AjaxResult.error("用户类型不能为空");
|
||||
}
|
||||
|
||||
// 检查手机号是否已存在(直接查询member表)
|
||||
Integer existingMemberId = memberMapper.selectMemberIdByMobile(mobile);
|
||||
if (existingMemberId != null) {
|
||||
return AjaxResult.error("该手机号已存在");
|
||||
// 处理mobile:如果为空或空字符串,设置为null
|
||||
if (mobile != null && mobile.trim().isEmpty()) {
|
||||
mobile = null;
|
||||
}
|
||||
|
||||
// 检查手机号是否已存在(仅在mobile不为空时检查)
|
||||
if (mobile != null && !mobile.trim().isEmpty()) {
|
||||
Integer existingMemberId = memberMapper.selectMemberIdByMobile(mobile);
|
||||
if (existingMemberId != null) {
|
||||
return AjaxResult.error("该手机号已存在");
|
||||
}
|
||||
}
|
||||
|
||||
// 先插入member表
|
||||
@@ -340,8 +395,15 @@ public class MemberController {
|
||||
return AjaxResult.error("用户基础信息插入失败");
|
||||
}
|
||||
|
||||
// 获取刚插入的member记录的ID(通过查询最新记录)
|
||||
Integer memberId = memberMapper.selectMemberIdByMobile(mobile);
|
||||
// 获取刚插入的member记录的ID
|
||||
Integer memberId = null;
|
||||
if (mobile != null && !mobile.trim().isEmpty()) {
|
||||
// 如果mobile不为空,通过mobile查询
|
||||
memberId = memberMapper.selectMemberIdByMobile(mobile);
|
||||
} else {
|
||||
// 如果mobile为空,查询最新插入的记录(通过create_time和id)
|
||||
memberId = memberMapper.selectLatestMemberId();
|
||||
}
|
||||
if (memberId == null) {
|
||||
return AjaxResult.error("获取新用户ID失败");
|
||||
}
|
||||
|
||||
@@ -131,5 +131,29 @@ public class OrderController {
|
||||
return AjaxResult.error("查询订单详情失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导入订单
|
||||
*/
|
||||
@SaCheckPermission("order:import")
|
||||
@PostMapping("/batchImport")
|
||||
public AjaxResult batchImport(@RequestBody Map<String, Object> params) {
|
||||
try {
|
||||
logger.info("批量导入订单,参数:{}", params);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> orders = (List<Map<String, Object>>) params.get("orders");
|
||||
|
||||
if (orders == null || orders.isEmpty()) {
|
||||
logger.error("批量导入失败:订单列表不能为空");
|
||||
return AjaxResult.error("订单列表不能为空");
|
||||
}
|
||||
|
||||
return orderService.batchImportOrders(orders);
|
||||
} catch (Exception e) {
|
||||
logger.error("批量导入订单失败:{}", e.getMessage(), e);
|
||||
return AjaxResult.error("批量导入订单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ public class DeliveryEditDto {
|
||||
|
||||
private String driverMobile;
|
||||
|
||||
/** 车牌号 */
|
||||
private String licensePlate;
|
||||
|
||||
private Integer buyerId;
|
||||
|
||||
private Double buyerPrice;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.aiotagro.cattletrade.business.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 运单轨迹查询参数
|
||||
*/
|
||||
@Data
|
||||
public class DeliveryTrackQueryDto {
|
||||
|
||||
@NotNull(message = "运单ID不能为空")
|
||||
private Integer deliveryId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public class Delivery implements Serializable {
|
||||
private Double firmPrice;
|
||||
|
||||
/**
|
||||
* 状态,1-待装车,2-已装车/预付款已支付,3-已装车/尾款待支付,4-已核验/待买家付款,5-尾款已付款,6-发票待开/进项票,7-发票待开/销项
|
||||
* 状态,1-准备中,2-运输中,3-已结束
|
||||
*/
|
||||
@TableField("status")
|
||||
private Integer status;
|
||||
@@ -179,6 +179,26 @@ public class Delivery implements Serializable {
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
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")
|
||||
private Date arrivalTime;
|
||||
|
||||
/**
|
||||
* 登记智能耳标数
|
||||
*/
|
||||
|
||||
@@ -25,4 +25,12 @@ public interface JbqClientLogMapper extends BaseMapper<JbqClientLog> {
|
||||
* 批量插入耳标日志数据
|
||||
*/
|
||||
int batchInsert(@Param("list") List<JbqClientLog> logList);
|
||||
|
||||
/**
|
||||
* 供百度鹰眼同步使用的增量查询
|
||||
*/
|
||||
List<JbqClientLog> listLogsForYingyan(@Param("deviceIds") List<String> deviceIds,
|
||||
@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime,
|
||||
@Param("limit") Integer limit);
|
||||
}
|
||||
|
||||
@@ -26,4 +26,12 @@ public interface JbqServerLogMapper extends BaseMapper<JbqServerLog> {
|
||||
* 批量插入主机日志数据
|
||||
*/
|
||||
int batchInsert(@Param("list") List<JbqServerLog> logList);
|
||||
|
||||
/**
|
||||
* 查询用于百度鹰眼推送的主机轨迹
|
||||
*/
|
||||
List<JbqServerLog> listLogsForYingyan(@Param("deviceIds") List<String> deviceIds,
|
||||
@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime,
|
||||
@Param("limit") Integer limit);
|
||||
}
|
||||
|
||||
@@ -188,6 +188,12 @@ public interface MemberMapper extends BaseMapper<Member> {
|
||||
@Select("SELECT id FROM member WHERE mobile = #{mobile} ORDER BY id DESC LIMIT 1")
|
||||
Integer selectMemberIdByMobile(@Param("mobile") String mobile);
|
||||
|
||||
/**
|
||||
* 查询最新插入的member记录ID(用于mobile为null时获取新插入的ID)
|
||||
*/
|
||||
@Select("SELECT id FROM member ORDER BY id DESC LIMIT 1")
|
||||
Integer selectLatestMemberId();
|
||||
|
||||
/**
|
||||
* 新增用户基础信息(插入member表)
|
||||
*/
|
||||
@@ -215,4 +221,24 @@ public interface MemberMapper extends BaseMapper<Member> {
|
||||
"LEFT JOIN member_user mu ON m.id = mu.member_id " +
|
||||
"WHERE m.id = #{memberId}")
|
||||
Map<String, Object> selectMemberUserById(@Param("memberId") Integer memberId);
|
||||
|
||||
/**
|
||||
* 批量根据member ID查询member和member_user关联数据
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT m.id, m.mobile, mu.username " +
|
||||
"FROM member m " +
|
||||
"LEFT JOIN member_user mu ON m.id = mu.member_id " +
|
||||
"WHERE m.id IN " +
|
||||
"<foreach collection='memberIds' item='id' open='(' separator=',' close=')'>" +
|
||||
" #{id}" +
|
||||
"</foreach>" +
|
||||
"</script>")
|
||||
List<Map<String, Object>> selectMemberUserByIds(@Param("memberIds") List<Integer> memberIds);
|
||||
|
||||
/**
|
||||
* 更新member表的手机号
|
||||
*/
|
||||
@org.apache.ibatis.annotations.Update("UPDATE member SET mobile = #{mobile}, update_time = NOW() WHERE id = #{memberId}")
|
||||
int updateMemberMobile(@Param("memberId") Integer memberId, @Param("mobile") String mobile);
|
||||
}
|
||||
|
||||
@@ -25,4 +25,12 @@ public interface XqClientLogMapper extends BaseMapper<XqClientLog> {
|
||||
* 批量插入项圈日志数据
|
||||
*/
|
||||
int batchInsert(@Param("list") List<XqClientLog> logList);
|
||||
|
||||
/**
|
||||
* 查询用于百度鹰眼推送的项圈轨迹
|
||||
*/
|
||||
List<XqClientLog> listLogsForYingyan(@Param("deviceIds") List<String> deviceIds,
|
||||
@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime,
|
||||
@Param("limit") Integer limit);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,611 @@
|
||||
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 org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ✅ 验证 entityName 不是 "entity" 字符串
|
||||
if ("entity".equals(entityName) || "entity_name".equals(entityName)) {
|
||||
logger.error("ensureEntity 失败:entityName 参数错误,值为 '{}',这可能是参数传递错误", entityName);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
MultiValueMap<String, String> form = baseForm();
|
||||
form.add("entity_name", entityName);
|
||||
// entity_desc 为非必填项,且命名规则限制:只支持中文、英文字母、下划线、连字符、数字
|
||||
// 为避免参数错误,不传递 entity_desc 参数
|
||||
|
||||
logger.debug("确保终端存在 - entity={}", entityName);
|
||||
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);
|
||||
return true;
|
||||
} else if (status == 3005) {
|
||||
// ✅ 终端已存在,这是正常情况,视为成功
|
||||
logger.info("✅ 鹰眼终端已存在, entityName={}, message={}", entityName, message);
|
||||
return true;
|
||||
} else if (status == 3006) {
|
||||
logger.info("✅ 鹰眼终端操作成功, entityName={}, status={}", entityName, status);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 其他状态码视为失败
|
||||
logger.warn("❌ 鹰眼创建终端失败, entityName={}, status={}, message={}",
|
||||
entityName, status, message);
|
||||
} catch (Exception e) {
|
||||
logger.error("❌ 鹰眼创建终端异常, entityName={}", entityName, 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<String, String> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询轨迹(单次查询,限制24小时内)
|
||||
*/
|
||||
public List<YingyanTrackPoint> queryTrack(String entityName, long startTime, long endTime) {
|
||||
if (StringUtils.isBlank(entityName)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
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("coord_type_output", "bd09ll");
|
||||
|
||||
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();
|
||||
|
||||
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 pointsNode = result.path("track").path("points");
|
||||
if (pointsNode == null || !pointsNode.isArray() || pointsNode.size() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<YingyanTrackPoint> 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分段查询轨迹(支持超过24小时的查询)
|
||||
* 按照24小时为间隔分段查询,然后拼接结果
|
||||
*
|
||||
* @param entityName 终端名称
|
||||
* @param startTime 开始时间(秒级时间戳)
|
||||
* @param endTime 结束时间(秒级时间戳,不能超过当前时间)
|
||||
* @return 拼接后的轨迹点列表,按时间排序
|
||||
*/
|
||||
public List<YingyanTrackPoint> 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<YingyanTrackPoint> 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<YingyanTrackPoint> 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<YingyanTrackPoint> 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<YingyanStayPoint> 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<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);
|
||||
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<YingyanStayPoint> 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<YingyanStayPoint> 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<YingyanStayPoint> 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<YingyanStayPoint> 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<YingyanStayPoint> 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<String, String> form) throws Exception {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
|
||||
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(form, headers);
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(buildUrl(path), httpEntity, String.class);
|
||||
return parseBody(response.getBody());
|
||||
}
|
||||
|
||||
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);
|
||||
if (params != null) {
|
||||
params.forEach((key, value) -> {
|
||||
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(调试用,生产环境可关闭)
|
||||
String requestUrl = builder.toUriString();
|
||||
logger.debug("百度鹰眼API请求 - path={}, url={}", path, 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<String, String> baseForm() {
|
||||
MultiValueMap<String, String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
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<Delivery> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Delivery::getStatus, 2);
|
||||
wrapper.isNotNull(Delivery::getLicensePlate);
|
||||
List<Delivery> 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<YingyanTrackPoint> points = collectTrackPoints(delivery, startTime, endTime);
|
||||
if (CollectionUtils.isEmpty(points)) {
|
||||
logger.warn("运单 {} 未找到有效轨迹点,终端名称: {}, 时间范围: {} 至 {}",
|
||||
delivery.getDeliveryNumber(), entityName, startTime, endTime);
|
||||
|
||||
// ✅ 详细排查原因:从 iot_device_data 表查询设备
|
||||
QueryWrapper<IotDeviceData> deviceQueryWrapper = new QueryWrapper<>();
|
||||
deviceQueryWrapper.eq("delivery_id", delivery.getId());
|
||||
deviceQueryWrapper.and(wrapper -> wrapper.eq("is_delet", 0).or().isNull("is_delet"));
|
||||
List<IotDeviceData> 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());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("运单 {} 轨迹上传完成 - 成功: {}, 失败: {}, 总计: {}",
|
||||
delivery.getDeliveryNumber(), successCount, failCount, points.size());
|
||||
return successCount;
|
||||
}
|
||||
|
||||
private List<YingyanTrackPoint> collectTrackPoints(Delivery delivery, Date startTime, Date endTime) {
|
||||
// ✅ 修改:从 iot_device_data 表查询设备,而不是 delivery_device 表
|
||||
QueryWrapper<IotDeviceData> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("delivery_id", delivery.getId());
|
||||
queryWrapper.and(wrapper -> wrapper.eq("is_delet", 0).or().isNull("is_delet")); // 只查询未删除的设备
|
||||
List<IotDeviceData> 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<Integer, List<String>> 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<YingyanTrackPoint> 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<String> deviceIds, Date startTime, Date endTime, int remaining, List<YingyanTrackPoint> result) {
|
||||
if (CollectionUtils.isEmpty(deviceIds) || remaining <= 0) {
|
||||
return remaining;
|
||||
}
|
||||
List<JbqServerLog> 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<String> deviceIds, Date startTime, Date endTime, int remaining, List<YingyanTrackPoint> result) {
|
||||
if (CollectionUtils.isEmpty(deviceIds) || remaining <= 0) {
|
||||
return remaining;
|
||||
}
|
||||
List<JbqClientLog> 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<String> deviceIds, Date startTime, Date endTime, int remaining, List<YingyanTrackPoint> result) {
|
||||
if (CollectionUtils.isEmpty(deviceIds) || remaining <= 0) {
|
||||
return;
|
||||
}
|
||||
List<XqClientLog> 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<YingyanTrackPoint> sortPoints(List<YingyanTrackPoint> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,4 +48,9 @@ public interface IDeliveryService extends IService<Delivery> {
|
||||
AjaxResult detail(Integer id);
|
||||
|
||||
PageResultResponse<Delivery> pageQueryListLog(DeliverListDto dto);
|
||||
|
||||
/**
|
||||
* 查询百度鹰眼轨迹与停留点
|
||||
*/
|
||||
AjaxResult queryYingyanTrack(Integer deliveryId);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.aiotagro.common.core.web.domain.AjaxResult;
|
||||
import com.aiotagro.common.core.web.domain.PageResultResponse;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -54,5 +55,13 @@ public interface IOrderService extends IService<Order> {
|
||||
* @return AjaxResult
|
||||
*/
|
||||
AjaxResult getOrderDetail(Integer id);
|
||||
|
||||
/**
|
||||
* 批量导入订单
|
||||
*
|
||||
* @param orders 订单列表
|
||||
* @return AjaxResult
|
||||
*/
|
||||
AjaxResult batchImportOrders(List<Map<String, Object>> orders);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,17 +50,32 @@ public class IotDeviceLogSyncService {
|
||||
@Transactional
|
||||
public void syncDeviceDataToLogs() {
|
||||
try {
|
||||
logger.info("开始执行设备日志同步任务");
|
||||
logger.info("========== 开始执行设备日志同步任务 ==========");
|
||||
|
||||
// 查询所有设备数据
|
||||
List<IotDeviceData> allDevices = iotDeviceDataMapper.selectList(null);
|
||||
// 只查询绑定了运送清单的设备数据(delivery_id 不为空)
|
||||
QueryWrapper<IotDeviceData> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.isNotNull("delivery_id");
|
||||
List<IotDeviceData> allDevices = iotDeviceDataMapper.selectList(queryWrapper);
|
||||
|
||||
if (allDevices.isEmpty()) {
|
||||
logger.warn("未找到任何设备数据");
|
||||
logger.warn("未找到任何绑定了运送清单的设备数据");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("找到 {} 个设备,开始同步到日志表", allDevices.size());
|
||||
logger.info("找到 {} 个绑定了运送清单的设备,开始同步到日志表", allDevices.size());
|
||||
|
||||
// ✅ 统计有经纬度数据的设备数量
|
||||
long devicesWithCoordinates = allDevices.stream()
|
||||
.filter(device -> device.getLatitude() != null && device.getLongitude() != null)
|
||||
.filter(device -> {
|
||||
String lat = sanitizeCoordinate(device.getLatitude());
|
||||
String lng = sanitizeCoordinate(device.getLongitude());
|
||||
return lat != null && lng != null && !lat.equals("0") && !lng.equals("0");
|
||||
})
|
||||
.count();
|
||||
logger.info("其中 {} 个设备包含有效的经纬度坐标数据", devicesWithCoordinates);
|
||||
logger.info("注意:设备日志同步任务仅将数据同步到日志表,不直接上传到百度鹰眼服务");
|
||||
logger.info("百度鹰眼轨迹上传由 DeliveryYingyanSyncService 定时任务负责");
|
||||
|
||||
int hostCount = 0;
|
||||
int earTagCount = 0;
|
||||
@@ -72,6 +87,7 @@ public class IotDeviceLogSyncService {
|
||||
List<XqClientLog> collarLogs = new ArrayList<>();
|
||||
|
||||
// 遍历所有设备,根据设备类型分组
|
||||
int devicesWithCoordinatesCount = 0;
|
||||
for (IotDeviceData device : allDevices) {
|
||||
try {
|
||||
Integer deviceType = device.getDeviceType();
|
||||
@@ -80,6 +96,23 @@ public class IotDeviceLogSyncService {
|
||||
logger.warn("设备 {} 的设备类型为空,跳过", device.getDeviceId());
|
||||
continue;
|
||||
}
|
||||
|
||||
// ✅ 记录设备经纬度信息
|
||||
boolean hasValidCoordinates = false;
|
||||
if (device.getLatitude() != null && device.getLongitude() != null) {
|
||||
String lat = sanitizeCoordinate(device.getLatitude());
|
||||
String lng = sanitizeCoordinate(device.getLongitude());
|
||||
if (lat != null && lng != null && !lat.equals("0") && !lng.equals("0")) {
|
||||
hasValidCoordinates = true;
|
||||
devicesWithCoordinatesCount++;
|
||||
logger.debug("设备 {} (类型: {}, 运单ID: {}) 包含经纬度坐标 - 纬度: {}, 经度: {}",
|
||||
device.getDeviceId(), deviceType, device.getDeliveryId(), lat, lng);
|
||||
}
|
||||
}
|
||||
if (!hasValidCoordinates) {
|
||||
logger.debug("设备 {} (类型: {}, 运单ID: {}) 无有效经纬度坐标",
|
||||
device.getDeviceId(), deviceType, device.getDeliveryId());
|
||||
}
|
||||
|
||||
switch (deviceType) {
|
||||
case 1: // 主机设备
|
||||
@@ -167,6 +200,9 @@ public class IotDeviceLogSyncService {
|
||||
}
|
||||
|
||||
logger.info("设备日志同步完成 - 主机: {}, 耳标: {}, 项圈: {}", hostCount, earTagCount, collarCount);
|
||||
logger.info("包含有效经纬度坐标的设备数量: {}", devicesWithCoordinatesCount);
|
||||
logger.info("========== 设备日志同步任务执行完成 ==========");
|
||||
logger.info("提示:同步到日志表的经纬度数据将由 DeliveryYingyanSyncService 定时任务上传到百度鹰眼服务");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("设备日志同步任务执行失败", e);
|
||||
|
||||
@@ -5,8 +5,11 @@ 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;
|
||||
@@ -19,6 +22,7 @@ import com.aiotagro.common.core.utils.StringUtils;
|
||||
import com.aiotagro.common.core.web.domain.AjaxResult;
|
||||
import com.aiotagro.common.core.web.domain.PageResultResponse;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
@@ -64,6 +68,9 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
private MemberDriverMapper memberDriverMapper;
|
||||
@Autowired
|
||||
private IDeliveryDeviceService deliveryDeviceService;
|
||||
|
||||
@Autowired
|
||||
private BaiduYingyanService baiduYingyanService;
|
||||
@Autowired
|
||||
private IXqClientService xqClientService;
|
||||
@Autowired
|
||||
@@ -84,7 +91,6 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
|
||||
// 调试:打印接收到的所有参数
|
||||
|
||||
Page<Delivery> result = PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
|
||||
LambdaQueryWrapper<Delivery> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 运输单号模糊查询
|
||||
@@ -119,12 +125,20 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
}
|
||||
|
||||
wrapper.orderByDesc(Delivery::getId);
|
||||
List<Delivery> list = this.list(wrapper);
|
||||
if(CollectionUtils.isNotEmpty(list)){
|
||||
}
|
||||
|
||||
if(CollectionUtils.isNotEmpty(list)){
|
||||
list.forEach(delivery -> {
|
||||
// 判断是否需要数据权限过滤
|
||||
boolean needPermissionFilter = !SecurityUtil.isSuperAdmin() && StringUtils.isNotEmpty(currentUserMobile);
|
||||
|
||||
List<Delivery> list;
|
||||
long total;
|
||||
|
||||
if (needPermissionFilter) {
|
||||
// 需要权限过滤:先查询所有数据,填充信息,过滤后再分页
|
||||
list = this.list(wrapper);
|
||||
|
||||
// 填充关联信息(供应商、资金方、采购商、司机等)
|
||||
if(CollectionUtils.isNotEmpty(list)){
|
||||
list.forEach(delivery -> {
|
||||
if(userId.equals(delivery.getCheckBy())){
|
||||
//判断是否需要核验,1:需要核验,2:不需要核验
|
||||
delivery.setIfCheck(1);
|
||||
@@ -344,58 +358,283 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据权限过滤:非超级管理员只能看到与自己手机号相关的订单
|
||||
|
||||
if (!SecurityUtil.isSuperAdmin() && StringUtils.isNotEmpty(currentUserMobile)) {
|
||||
list = list.stream().filter(delivery -> {
|
||||
boolean hasPermission = false;
|
||||
|
||||
list = list.stream().filter(delivery -> {
|
||||
boolean hasPermission = false;
|
||||
|
||||
|
||||
// 检查是否是司机
|
||||
if (StringUtils.isNotEmpty(delivery.getDriverMobile()) &&
|
||||
currentUserMobile.equals(delivery.getDriverMobile())) {
|
||||
hasPermission = true;
|
||||
}
|
||||
|
||||
// 检查是否是供应商(可能有多个供应商)
|
||||
if (!hasPermission && StringUtils.isNotEmpty(delivery.getSupplierMobile())) {
|
||||
String[] supplierMobiles = delivery.getSupplierMobile().split(",");
|
||||
for (String mobile : supplierMobiles) {
|
||||
if (currentUserMobile.equals(mobile.trim())) {
|
||||
hasPermission = true;
|
||||
break;
|
||||
}
|
||||
// 检查是否是司机
|
||||
if (StringUtils.isNotEmpty(delivery.getDriverMobile()) &&
|
||||
currentUserMobile.equals(delivery.getDriverMobile())) {
|
||||
hasPermission = true;
|
||||
}
|
||||
|
||||
// 检查是否是供应商(可能有多个供应商)
|
||||
if (!hasPermission && StringUtils.isNotEmpty(delivery.getSupplierMobile())) {
|
||||
String[] supplierMobiles = delivery.getSupplierMobile().split(",");
|
||||
for (String mobile : supplierMobiles) {
|
||||
if (currentUserMobile.equals(mobile.trim())) {
|
||||
hasPermission = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否是资金方
|
||||
if (!hasPermission && StringUtils.isNotEmpty(delivery.getFundMobile()) &&
|
||||
currentUserMobile.equals(delivery.getFundMobile())) {
|
||||
hasPermission = true;
|
||||
}
|
||||
|
||||
// 检查是否是采购商
|
||||
if (!hasPermission && StringUtils.isNotEmpty(delivery.getBuyerMobile()) &&
|
||||
currentUserMobile.equals(delivery.getBuyerMobile())) {
|
||||
hasPermission = true;
|
||||
}
|
||||
|
||||
if (!hasPermission) {
|
||||
}
|
||||
|
||||
return hasPermission;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
} else if (SecurityUtil.isSuperAdmin()) {
|
||||
// 检查是否是资金方
|
||||
if (!hasPermission && StringUtils.isNotEmpty(delivery.getFundMobile()) &&
|
||||
currentUserMobile.equals(delivery.getFundMobile())) {
|
||||
hasPermission = true;
|
||||
}
|
||||
|
||||
// 检查是否是采购商
|
||||
if (!hasPermission && StringUtils.isNotEmpty(delivery.getBuyerMobile()) &&
|
||||
currentUserMobile.equals(delivery.getBuyerMobile())) {
|
||||
hasPermission = true;
|
||||
}
|
||||
|
||||
return hasPermission;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
// 获取过滤后的总数
|
||||
total = list.size();
|
||||
|
||||
// 手动分页
|
||||
int startIndex = (dto.getPageNum() - 1) * dto.getPageSize();
|
||||
int endIndex = Math.min(startIndex + dto.getPageSize(), list.size());
|
||||
if (startIndex < list.size()) {
|
||||
list = list.subList(startIndex, endIndex);
|
||||
} else {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
} else {
|
||||
// 不需要权限过滤:直接使用PageHelper分页
|
||||
Page<Delivery> result = PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
|
||||
list = this.list(wrapper);
|
||||
total = result.getTotal();
|
||||
|
||||
// 填充关联信息(供应商、资金方、采购商、司机等)
|
||||
if(CollectionUtils.isNotEmpty(list)){
|
||||
list.forEach(delivery -> {
|
||||
if(userId.equals(delivery.getCheckBy())){
|
||||
//判断是否需要核验,1:需要核验,2:不需要核验
|
||||
delivery.setIfCheck(1);
|
||||
}
|
||||
|
||||
// 查询四个角色的手机号(供应商、资金方、采购商、司机)
|
||||
try {
|
||||
// 1. 查询供应商信息(supplierId是逗号分隔的字符串)
|
||||
if (StringUtils.isNotEmpty(delivery.getSupplierId())) {
|
||||
String[] supplierIds = delivery.getSupplierId().split(",");
|
||||
List<String> supplierNames = new ArrayList<>();
|
||||
List<String> supplierMobiles = new ArrayList<>();
|
||||
for (String supplierId : supplierIds) {
|
||||
if (StringUtils.isNotEmpty(supplierId.trim())) {
|
||||
try {
|
||||
Integer sid = Integer.parseInt(supplierId.trim());
|
||||
// 查询member和member_user表关联数据
|
||||
Map<String, Object> supplierInfo = memberMapper.selectMemberUserById(sid);
|
||||
if (supplierInfo != null) {
|
||||
String username = (String) supplierInfo.get("username");
|
||||
String mobile = (String) supplierInfo.get("mobile");
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
supplierNames.add(username);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(mobile)) {
|
||||
supplierMobiles.add(mobile);
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!supplierNames.isEmpty()) {
|
||||
delivery.setSupplierName(String.join(",", supplierNames));
|
||||
} else if (!supplierMobiles.isEmpty()) {
|
||||
// 如果用户名为空,使用手机号作为备选
|
||||
delivery.setSupplierName(String.join(",", supplierMobiles));
|
||||
}
|
||||
if (!supplierMobiles.isEmpty()) {
|
||||
delivery.setSupplierMobile(String.join(",", supplierMobiles));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 查询资金方信息
|
||||
if (delivery.getFundId() != null) {
|
||||
Map<String, Object> fundInfo = memberMapper.selectMemberUserById(delivery.getFundId());
|
||||
if (fundInfo != null) {
|
||||
String username = (String) fundInfo.get("username");
|
||||
String mobile = (String) fundInfo.get("mobile");
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
delivery.setFundName(username);
|
||||
} else if (StringUtils.isNotEmpty(mobile)) {
|
||||
// 如果用户名为空,使用手机号作为备选
|
||||
delivery.setFundName(mobile);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(mobile)) {
|
||||
delivery.setFundMobile(mobile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 查询采购商信息
|
||||
if (delivery.getBuyerId() != null) {
|
||||
Map<String, Object> buyerInfo = memberMapper.selectMemberUserById(delivery.getBuyerId());
|
||||
if (buyerInfo != null) {
|
||||
String username = (String) buyerInfo.get("username");
|
||||
String mobile = (String) buyerInfo.get("mobile");
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
delivery.setBuyerName(username);
|
||||
} else if (StringUtils.isNotEmpty(mobile)) {
|
||||
// 如果用户名为空,使用手机号作为备选
|
||||
delivery.setBuyerName(mobile);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(mobile)) {
|
||||
delivery.setBuyerMobile(mobile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 查询司机手机号(如果有司机ID)
|
||||
if (delivery.getDriverId() != null) {
|
||||
try {
|
||||
Map<String, Object> driverInfo = memberDriverMapper.selectDriverById(delivery.getDriverId());
|
||||
if (driverInfo != null) {
|
||||
String driverName = (String) driverInfo.get("username");
|
||||
String driverMobile = (String) driverInfo.get("mobile");
|
||||
String carImg = (String) driverInfo.get("car_img");
|
||||
|
||||
if (StringUtils.isNotEmpty(driverMobile)) {
|
||||
delivery.setDriverMobile(driverMobile);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(driverName)) {
|
||||
delivery.setDriverName(driverName);
|
||||
}
|
||||
|
||||
// 优先从车辆表获取车身照片(根据车牌号)
|
||||
// 如果车辆表中没有,再从司机信息中获取作为后备
|
||||
boolean vehiclePhotoSet = false;
|
||||
if (delivery.getLicensePlate() != null && StringUtils.isNotEmpty(delivery.getLicensePlate())) {
|
||||
try {
|
||||
Vehicle vehicle = vehicleMapper.selectByLicensePlate(delivery.getLicensePlate());
|
||||
if (vehicle != null) {
|
||||
String carFrontPhoto = vehicle.getCarFrontPhoto();
|
||||
String carRearPhoto = vehicle.getCarRearPhoto();
|
||||
if (StringUtils.isNotEmpty(carFrontPhoto) || StringUtils.isNotEmpty(carRearPhoto)) {
|
||||
delivery.setCarFrontPhoto(StringUtils.isNotEmpty(carFrontPhoto) ? carFrontPhoto : null);
|
||||
delivery.setCarBehindPhoto(StringUtils.isNotEmpty(carRearPhoto) ? carRearPhoto : null);
|
||||
vehiclePhotoSet = true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("从车辆表获取照片失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果车辆表中没有照片,从司机信息中获取作为后备
|
||||
if (!vehiclePhotoSet && carImg != null && !carImg.isEmpty()) {
|
||||
// 按逗号分割car_img字段,分别映射到车头和车尾照片
|
||||
String[] carImgUrls = carImg.split(",");
|
||||
|
||||
if (carImgUrls.length >= 2) {
|
||||
// 逗号前面的URL作为车尾照片
|
||||
String carBehindPhoto = carImgUrls[0].trim();
|
||||
// 逗号后面的URL作为车头照片
|
||||
String carFrontPhoto = carImgUrls[1].trim();
|
||||
|
||||
delivery.setCarBehindPhoto(carBehindPhoto);
|
||||
delivery.setCarFrontPhoto(carFrontPhoto);
|
||||
} else if (carImgUrls.length == 1) {
|
||||
// 只有一个URL时,同时设置为车头和车尾照片
|
||||
String singlePhoto = carImgUrls[0].trim();
|
||||
delivery.setCarFrontPhoto(singlePhoto);
|
||||
delivery.setCarBehindPhoto(singlePhoto);
|
||||
} else {
|
||||
// 没有有效URL,设置为null
|
||||
delivery.setCarFrontPhoto(null);
|
||||
delivery.setCarBehindPhoto(null);
|
||||
}
|
||||
} else if (!vehiclePhotoSet) {
|
||||
// 如果车辆表和司机信息中都没有照片,设置为null
|
||||
delivery.setCarFrontPhoto(null);
|
||||
delivery.setCarBehindPhoto(null);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// 统计登记设备数量(耳标+项圈)
|
||||
Integer currentDeliveryId = delivery.getId();
|
||||
if (currentDeliveryId != null) {
|
||||
try {
|
||||
// 统计耳标设备数量
|
||||
LambdaQueryWrapper<DeliveryDevice> earTagWrapper = new LambdaQueryWrapper<>();
|
||||
earTagWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 2);
|
||||
long earTagCount = deliveryDeviceService.count(earTagWrapper);
|
||||
|
||||
// 统计项圈设备数量
|
||||
LambdaQueryWrapper<DeliveryDevice> collarWrapper = new LambdaQueryWrapper<>();
|
||||
collarWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 3);
|
||||
long collarCount = deliveryDeviceService.count(collarWrapper);
|
||||
|
||||
// 设置总设备数量和耳标数量
|
||||
int totalDeviceCount = (int) (earTagCount + collarCount);
|
||||
delivery.setRegisteredJbqCount(totalDeviceCount);
|
||||
delivery.setEarTagCount((int) earTagCount);
|
||||
|
||||
// 设置已分配设备数量,与登记设备数量保持一致
|
||||
delivery.setBindJbqCount(totalDeviceCount);
|
||||
|
||||
// 统计已佩戴设备数量(bandge_status = 1)
|
||||
int wornDeviceCount = 0;
|
||||
try {
|
||||
// 统计已佩戴的耳标设备数量(通过delivery_device表关联)
|
||||
LambdaQueryWrapper<DeliveryDevice> wornEarTagWrapper = new LambdaQueryWrapper<>();
|
||||
wornEarTagWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 2)
|
||||
.eq(DeliveryDevice::getIsWare, 1); // 1表示已佩戴
|
||||
long wornEarTagCount = deliveryDeviceService.count(wornEarTagWrapper);
|
||||
|
||||
// 统计已佩戴的项圈设备数量(通过delivery_device表关联xq_client表)
|
||||
LambdaQueryWrapper<DeliveryDevice> wornCollarWrapper = new LambdaQueryWrapper<>();
|
||||
wornCollarWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 3);
|
||||
List<DeliveryDevice> collarDevices = deliveryDeviceService.list(wornCollarWrapper);
|
||||
|
||||
int wornCollarCount = 0;
|
||||
for (DeliveryDevice device : collarDevices) {
|
||||
// 查询xq_client表中的bandge_status
|
||||
LambdaQueryWrapper<XqClient> xqWrapper = new LambdaQueryWrapper<>();
|
||||
xqWrapper.eq(XqClient::getSn, device.getDeviceId());
|
||||
XqClient xqClient = xqClientService.getOne(xqWrapper);
|
||||
if (xqClient != null && xqClient.getBandgeStatus() != null && xqClient.getBandgeStatus() == 1) {
|
||||
wornCollarCount++;
|
||||
}
|
||||
}
|
||||
|
||||
wornDeviceCount = (int) (wornEarTagCount + wornCollarCount);
|
||||
delivery.setWareCount(wornDeviceCount);
|
||||
} catch (Exception e) {
|
||||
delivery.setWareCount(0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
delivery.setRegisteredJbqCount(0);
|
||||
delivery.setBindJbqCount(0);
|
||||
delivery.setWareCount(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新分页信息
|
||||
long filteredTotal = list.size();
|
||||
return new PageResultResponse(filteredTotal, list);
|
||||
// 返回分页结果
|
||||
return new PageResultResponse<>(total, list);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -754,6 +993,44 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
if (dto.getDriverMobile() != null) {
|
||||
delivery.setDriverMobile(dto.getDriverMobile());
|
||||
}
|
||||
if (dto.getLicensePlate() != null) {
|
||||
String oldLicensePlate = delivery.getLicensePlate();
|
||||
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());
|
||||
}
|
||||
@@ -1277,6 +1554,11 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
public PageResultResponse<DeliveryLogVo> pageQueryList(DeliverListDto dto) {
|
||||
//获取当前登录人的信息
|
||||
String currentUserMobile = SecurityUtil.getUserMobile();
|
||||
boolean isSuperAdmin = SecurityUtil.isSuperAdmin();
|
||||
|
||||
logger.info("[预警列表查询] 开始查询,参数: pageNum={}, pageSize={}, deliveryNumber={}, licensePlate={}, warningType={}, startTime={}, endTime={}, isSuperAdmin={}",
|
||||
dto.getPageNum(), dto.getPageSize(), dto.getDeliveryNumber(), dto.getLicensePlate(),
|
||||
dto.getWarningType(), dto.getStartTime(), dto.getEndTime(), isSuperAdmin);
|
||||
|
||||
Page<Delivery> result = PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
|
||||
if(StringUtils.isNotEmpty(dto.getStartTime())){
|
||||
@@ -1287,16 +1569,74 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
String endTime = dto.getEndTime() + " 23:59:59";
|
||||
dto.setEndTime(endTime);
|
||||
}
|
||||
|
||||
List<Delivery> resList = this.baseMapper.getPageWarningLog(dto);
|
||||
long totalBeforeFilter = result.getTotal(); // 保存过滤前的总数
|
||||
|
||||
logger.info("[预警列表查询] SQL查询结果: 总数={}, 当前页数据量={}", totalBeforeFilter, resList.size());
|
||||
|
||||
// ✅ 调试:如果查询结果为空,检查可能的原因
|
||||
if (totalBeforeFilter == 0 && resList.isEmpty()) {
|
||||
logger.warn("[预警列表查询] ⚠️ 查询结果为空,可能原因:");
|
||||
logger.warn("[预警列表查询] 1. delivery 表和 warning_log 表的 delivery_id 不匹配");
|
||||
logger.warn("[预警列表查询] 2. warning_log 表中的 delivery_id 在 delivery 表中不存在");
|
||||
logger.warn("[预警列表查询] 3. SQL 查询逻辑有问题");
|
||||
|
||||
// 检查 warning_log 表中是否有数据
|
||||
try {
|
||||
long warningLogCount = warningLogMapper.selectCount(null);
|
||||
logger.info("[预警列表查询] warning_log 表总记录数: {}", warningLogCount);
|
||||
|
||||
// 检查有 delivery_id 的记录数
|
||||
QueryWrapper<WarningLog> countWrapper = new QueryWrapper<>();
|
||||
countWrapper.in("warning_type", 2,3,4,5,6,7,8,9);
|
||||
long validWarningCount = warningLogMapper.selectCount(countWrapper);
|
||||
logger.info("[预警列表查询] warning_log 表中预警类型在(2-9)范围内的记录数: {}", validWarningCount);
|
||||
|
||||
// 检查 delivery 表中有多少运单
|
||||
long deliveryCount = this.count();
|
||||
logger.info("[预警列表查询] delivery 表总记录数: {}", deliveryCount);
|
||||
|
||||
// ✅ 关键调试:检查 warning_log 表中的 delivery_id 是否在 delivery 表中存在
|
||||
QueryWrapper<WarningLog> deliveryIdWrapper = new QueryWrapper<>();
|
||||
deliveryIdWrapper.in("warning_type", 2,3,4,5,6,7,8,9);
|
||||
deliveryIdWrapper.select("DISTINCT delivery_id");
|
||||
List<WarningLog> warningLogsWithDeliveryId = warningLogMapper.selectList(deliveryIdWrapper);
|
||||
Set<Integer> warningLogDeliveryIds = warningLogsWithDeliveryId.stream()
|
||||
.map(WarningLog::getDeliveryId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
logger.info("[预警列表查询] warning_log 表中不重复的 delivery_id 数量: {}", warningLogDeliveryIds.size());
|
||||
|
||||
// 检查这些 delivery_id 在 delivery 表中是否存在
|
||||
if (!warningLogDeliveryIds.isEmpty()) {
|
||||
List<Integer> deliveryIds = this.listByIds(new ArrayList<>(warningLogDeliveryIds))
|
||||
.stream()
|
||||
.map(Delivery::getId)
|
||||
.collect(Collectors.toList());
|
||||
logger.info("[预警列表查询] 在 delivery 表中存在的 delivery_id 数量: {}", deliveryIds.size());
|
||||
logger.info("[预警列表查询] 在 delivery 表中存在的 delivery_id: {}", deliveryIds);
|
||||
|
||||
// 找出不匹配的 delivery_id
|
||||
Set<Integer> missingDeliveryIds = new HashSet<>(warningLogDeliveryIds);
|
||||
missingDeliveryIds.removeAll(deliveryIds);
|
||||
if (!missingDeliveryIds.isEmpty()) {
|
||||
logger.warn("[预警列表查询] ⚠️ warning_log 表中有 {} 个 delivery_id 在 delivery 表中不存在", missingDeliveryIds.size());
|
||||
logger.warn("[预警列表查询] 不匹配的 delivery_id 示例(前10个): {}",
|
||||
missingDeliveryIds.stream().limit(10).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("[预警列表查询] 调试查询失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 数据权限过滤:非超级管理员只能看到与自己手机号相关的订单
|
||||
|
||||
if (!SecurityUtil.isSuperAdmin() && StringUtils.isNotEmpty(currentUserMobile)) {
|
||||
|
||||
if (!isSuperAdmin && StringUtils.isNotEmpty(currentUserMobile)) {
|
||||
int beforeFilterSize = resList.size();
|
||||
resList = resList.stream().filter(delivery -> {
|
||||
boolean hasPermission = false;
|
||||
|
||||
|
||||
// 检查是否是司机
|
||||
if (StringUtils.isNotEmpty(delivery.getDriverMobile()) &&
|
||||
currentUserMobile.equals(delivery.getDriverMobile())) {
|
||||
@@ -1326,26 +1666,61 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
hasPermission = true;
|
||||
}
|
||||
|
||||
if (!hasPermission) {
|
||||
}
|
||||
|
||||
return hasPermission;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
} else if (SecurityUtil.isSuperAdmin()) {
|
||||
logger.info("[预警列表查询] 权限过滤后: 过滤前={}, 过滤后={}", beforeFilterSize, resList.size());
|
||||
} else if (isSuperAdmin) {
|
||||
logger.info("[预警列表查询] 超级管理员,跳过权限过滤");
|
||||
} else {
|
||||
logger.warn("[预警列表查询] 未获取到用户手机号,跳过权限过滤");
|
||||
}
|
||||
|
||||
resList.forEach(deliveryLogVo -> {
|
||||
String warningType = deliveryLogVo.getWarningType();
|
||||
// ✅ 修复:将 Delivery 实体转换为 DeliveryLogVo
|
||||
List<DeliveryLogVo> voList = new ArrayList<>();
|
||||
for (Delivery delivery : resList) {
|
||||
DeliveryLogVo vo = new DeliveryLogVo();
|
||||
vo.setId(delivery.getId());
|
||||
vo.setDeliveryId(delivery.getId());
|
||||
vo.setDeliveryNumber(delivery.getDeliveryNumber());
|
||||
vo.setLicensePlate(delivery.getLicensePlate());
|
||||
vo.setStatus(delivery.getStatus());
|
||||
vo.setCarFrontPhoto(delivery.getCarFrontPhoto());
|
||||
vo.setRegisteredJbqCount(delivery.getRegisteredJbqCount());
|
||||
vo.setInventoryJbqCount(delivery.getInventoryJbqCount());
|
||||
vo.setWarningType(delivery.getWarningType());
|
||||
vo.setWarningTime(delivery.getWarningTime());
|
||||
vo.setCreateByDesc(delivery.getCreateByName());
|
||||
|
||||
// ✅ 新增:填充起始地、目的地、创建时间、预计送达时间、司机姓名、创建人
|
||||
vo.setStartLocation(delivery.getStartLocation());
|
||||
vo.setEndLocation(delivery.getEndLocation());
|
||||
vo.setCreateTime(delivery.getCreateTime());
|
||||
vo.setEstimatedDeliveryTime(delivery.getEstimatedDeliveryTime());
|
||||
vo.setDriverName(delivery.getDriverName());
|
||||
vo.setCreateByName(delivery.getCreateByName());
|
||||
|
||||
// 设置预警类型描述
|
||||
String warningType = delivery.getWarningType();
|
||||
if(StringUtils.isNotEmpty(warningType)){
|
||||
deliveryLogVo.setWarningTypeDesc(EnumUtil.getEnumConstant(WarningStatusAdminEnum.class , Integer.parseInt(warningType)).getDescription());
|
||||
try {
|
||||
vo.setWarningTypeDesc(EnumUtil.getEnumConstant(WarningStatusAdminEnum.class , Integer.parseInt(warningType)).getDescription());
|
||||
} catch (Exception e) {
|
||||
logger.warn("[预警列表查询] 预警类型解析失败: {}", warningType);
|
||||
vo.setWarningTypeDesc("未知类型");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
voList.add(vo);
|
||||
}
|
||||
|
||||
// 更新分页信息
|
||||
long filteredTotal = resList.size();
|
||||
return new PageResultResponse(filteredTotal, resList);
|
||||
// ✅ 修复:使用分页查询的总数,而不是过滤后的数量
|
||||
// 如果是超级管理员,使用 SQL 查询的总数;否则使用过滤后的数量
|
||||
long finalTotal = isSuperAdmin ? totalBeforeFilter : voList.size();
|
||||
|
||||
logger.info("[预警列表查询] 最终返回: 总数={}, 数据量={}", finalTotal, voList.size());
|
||||
|
||||
return new PageResultResponse<DeliveryLogVo>(finalTotal, voList);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1654,6 +2029,107 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
return AjaxResult.success(resMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AjaxResult queryYingyanTrack(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);
|
||||
|
||||
// ✅ 按照用户要求:使用预计开始时间(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));
|
||||
|
||||
long endTime = determineTrackEndTime(delivery);
|
||||
|
||||
// ✅ 使用分段查询方法,支持超过24小时的轨迹查询
|
||||
logger.info("查询运单 {} 的百度鹰眼轨迹 - entity={}, startTime={}, endTime={}, 时间跨度={}小时",
|
||||
delivery.getDeliveryNumber(), entityName, startTime, endTime, (endTime - startTime) / 3600);
|
||||
|
||||
// ✅ 确保终端存在后再查询(重要:如果终端不存在,查询会失败)
|
||||
boolean entityExists = baiduYingyanService.ensureEntity(entityName);
|
||||
if (!entityExists) {
|
||||
logger.error("运单 {} 终端不存在且创建失败,无法查询轨迹 - entity={}, deliveryNumber={}, deliveryId={}",
|
||||
delivery.getDeliveryNumber(), entityName, delivery.getDeliveryNumber(), delivery.getId());
|
||||
// 返回空结果,而不是继续查询(因为查询肯定会失败)
|
||||
Map<String, Object> 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", "终端不存在且创建失败,请检查百度鹰眼服务配置或终端名称是否正确");
|
||||
return AjaxResult.success(data);
|
||||
}
|
||||
|
||||
logger.debug("运单 {} 终端已确保存在,开始查询轨迹 - 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());
|
||||
|
||||
Map<String, Object> 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());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
return System.currentTimeMillis() / 1000;
|
||||
}
|
||||
|
||||
private long dateToSeconds(Date date) {
|
||||
if (date == null) {
|
||||
return 0;
|
||||
}
|
||||
return date.getTime() / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取核验状态的中文描述
|
||||
* @param status 状态码
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.aiotagro.cattletrade.business.service.impl;
|
||||
|
||||
import com.aiotagro.cattletrade.business.entity.Order;
|
||||
import com.aiotagro.cattletrade.business.entity.Member;
|
||||
import com.aiotagro.cattletrade.business.entity.SysUser;
|
||||
import com.aiotagro.cattletrade.business.mapper.OrderMapper;
|
||||
import com.aiotagro.cattletrade.business.mapper.MemberMapper;
|
||||
@@ -20,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -62,9 +62,6 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
logger.info("分页查询订单列表,页码:{},每页数量:{},买方:{},卖方:{},结算方式:{}",
|
||||
pageNum, pageSize, buyerName, sellerName, settlementType);
|
||||
|
||||
// 使用PageHelper进行分页
|
||||
Page<Order> page = PageHelper.startPage(pageNum, pageSize);
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(settlementType != null, Order::getSettlementType, settlementType);
|
||||
@@ -89,28 +86,65 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
|
||||
queryWrapper.orderByDesc(Order::getCreateTime);
|
||||
|
||||
// 执行查询
|
||||
List<Order> list = orderMapper.selectList(queryWrapper);
|
||||
|
||||
// 填充关联信息
|
||||
list.forEach(this::fillOrderInfo);
|
||||
|
||||
// 如果提供了买方或卖方名称搜索,进行过滤
|
||||
List<Order> filteredList = list;
|
||||
if (buyerName != null && !buyerName.trim().isEmpty()) {
|
||||
filteredList = filteredList.stream()
|
||||
.filter(order -> order.getBuyerName() != null && order.getBuyerName().contains(buyerName.trim()))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
if (sellerName != null && !sellerName.trim().isEmpty()) {
|
||||
filteredList = filteredList.stream()
|
||||
.filter(order -> order.getSellerName() != null && order.getSellerName().contains(sellerName.trim()))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
// 判断是否需要先过滤再分页(如果提供了买方或卖方名称搜索)
|
||||
boolean needFilter = (buyerName != null && !buyerName.trim().isEmpty()) ||
|
||||
(sellerName != null && !sellerName.trim().isEmpty());
|
||||
|
||||
List<Order> filteredList;
|
||||
long total;
|
||||
|
||||
if (needFilter) {
|
||||
// 需要过滤的情况:先查询所有数据,填充信息,过滤,然后手动分页
|
||||
List<Order> allList = orderMapper.selectList(queryWrapper);
|
||||
|
||||
// 批量填充关联信息(优化性能)
|
||||
fillOrderInfoBatch(allList);
|
||||
|
||||
// 进行过滤
|
||||
filteredList = allList;
|
||||
if (buyerName != null && !buyerName.trim().isEmpty()) {
|
||||
filteredList = filteredList.stream()
|
||||
.filter(order -> order.getBuyerName() != null && order.getBuyerName().contains(buyerName.trim()))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
if (sellerName != null && !sellerName.trim().isEmpty()) {
|
||||
filteredList = filteredList.stream()
|
||||
.filter(order -> order.getSellerName() != null && order.getSellerName().contains(sellerName.trim()))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
total = filteredList.size();
|
||||
|
||||
// 手动分页
|
||||
int startIndex = (pageNum - 1) * pageSize;
|
||||
int endIndex = Math.min(startIndex + pageSize, filteredList.size());
|
||||
if (startIndex < filteredList.size()) {
|
||||
filteredList = filteredList.subList(startIndex, endIndex);
|
||||
} else {
|
||||
filteredList = new ArrayList<>();
|
||||
}
|
||||
|
||||
logger.info("查询到{}条订单记录,过滤后{}条,分页后{}条", allList.size(), total, filteredList.size());
|
||||
} else {
|
||||
// 不需要过滤的情况:直接使用PageHelper分页
|
||||
Page<Order> page = PageHelper.startPage(pageNum, pageSize);
|
||||
|
||||
// 执行查询
|
||||
List<Order> list = orderMapper.selectList(queryWrapper);
|
||||
|
||||
// 批量填充关联信息(优化性能)
|
||||
fillOrderInfoBatch(list);
|
||||
|
||||
// 获取总数和分页数据
|
||||
total = page.getTotal();
|
||||
filteredList = list;
|
||||
|
||||
logger.info("查询到{}条订单记录,分页后{}条", total, filteredList.size());
|
||||
}
|
||||
|
||||
// 构建分页结果
|
||||
logger.info("查询到{}条订单记录,过滤后{}条", list.size(), filteredList.size());
|
||||
return new PageResultResponse<>(filteredList.size(), filteredList);
|
||||
return new PageResultResponse<>(total, filteredList);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,8 +324,178 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
return AjaxResult.success(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量填充订单关联信息(优化性能,减少数据库查询次数)
|
||||
*/
|
||||
private void fillOrderInfoBatch(List<Order> orders) {
|
||||
if (orders == null || orders.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 收集所有需要查询的ID
|
||||
java.util.Set<Integer> buyerIds = new java.util.HashSet<>();
|
||||
java.util.Set<Integer> sellerIds = new java.util.HashSet<>();
|
||||
java.util.Set<Integer> creatorIds = new java.util.HashSet<>();
|
||||
|
||||
for (Order order : orders) {
|
||||
// 收集买方ID
|
||||
if (order.getBuyerId() != null && !order.getBuyerId().isEmpty()) {
|
||||
Arrays.stream(order.getBuyerId().split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.forEach(id -> {
|
||||
try {
|
||||
buyerIds.add(Integer.parseInt(id));
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("无效的买方ID:{}", id);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 收集卖方ID
|
||||
if (order.getSellerId() != null && !order.getSellerId().isEmpty()) {
|
||||
Arrays.stream(order.getSellerId().split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.forEach(id -> {
|
||||
try {
|
||||
sellerIds.add(Integer.parseInt(id));
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("无效的卖方ID:{}", id);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 收集创建人ID
|
||||
if (order.getCreatedBy() != null) {
|
||||
creatorIds.add(order.getCreatedBy());
|
||||
}
|
||||
}
|
||||
|
||||
// 批量查询买方信息
|
||||
java.util.Map<Integer, String> buyerNameMap = new java.util.HashMap<>();
|
||||
if (!buyerIds.isEmpty()) {
|
||||
List<Map<String, Object>> buyerList = memberMapper.selectMemberUserByIds(new ArrayList<>(buyerIds));
|
||||
for (Map<String, Object> buyer : buyerList) {
|
||||
Object idObj = buyer.get("id");
|
||||
Integer id = null;
|
||||
if (idObj != null) {
|
||||
if (idObj instanceof Integer) {
|
||||
id = (Integer) idObj;
|
||||
} else if (idObj instanceof Long) {
|
||||
id = ((Long) idObj).intValue();
|
||||
} else if (idObj instanceof Number) {
|
||||
id = ((Number) idObj).intValue();
|
||||
}
|
||||
}
|
||||
String username = (String) buyer.get("username");
|
||||
if (id != null && username != null) {
|
||||
buyerNameMap.put(id, username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量查询卖方信息
|
||||
java.util.Map<Integer, String> sellerNameMap = new java.util.HashMap<>();
|
||||
if (!sellerIds.isEmpty()) {
|
||||
List<Map<String, Object>> sellerList = memberMapper.selectMemberUserByIds(new ArrayList<>(sellerIds));
|
||||
for (Map<String, Object> seller : sellerList) {
|
||||
Object idObj = seller.get("id");
|
||||
Integer id = null;
|
||||
if (idObj != null) {
|
||||
if (idObj instanceof Integer) {
|
||||
id = (Integer) idObj;
|
||||
} else if (idObj instanceof Long) {
|
||||
id = ((Long) idObj).intValue();
|
||||
} else if (idObj instanceof Number) {
|
||||
id = ((Number) idObj).intValue();
|
||||
}
|
||||
}
|
||||
String username = (String) seller.get("username");
|
||||
if (id != null && username != null) {
|
||||
sellerNameMap.put(id, username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量查询创建人信息
|
||||
java.util.Map<Integer, String> creatorNameMap = new java.util.HashMap<>();
|
||||
if (!creatorIds.isEmpty()) {
|
||||
List<SysUser> creatorList = sysUserMapper.selectBatchIds(creatorIds);
|
||||
for (SysUser user : creatorList) {
|
||||
if (user != null && user.getId() != null && user.getName() != null) {
|
||||
creatorNameMap.put(user.getId(), user.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量填充订单信息
|
||||
for (Order order : orders) {
|
||||
// 填充买方名称
|
||||
if (order.getBuyerId() != null && !order.getBuyerId().isEmpty()) {
|
||||
String buyerNames = Arrays.stream(order.getBuyerId().split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.map(id -> {
|
||||
try {
|
||||
Integer buyerId = Integer.parseInt(id);
|
||||
return buyerNameMap.getOrDefault(buyerId, "");
|
||||
} catch (NumberFormatException e) {
|
||||
return "";
|
||||
}
|
||||
})
|
||||
.filter(name -> !name.isEmpty())
|
||||
.collect(Collectors.joining(", "));
|
||||
order.setBuyerName(buyerNames);
|
||||
}
|
||||
|
||||
// 填充卖方名称
|
||||
if (order.getSellerId() != null && !order.getSellerId().isEmpty()) {
|
||||
String sellerNames = Arrays.stream(order.getSellerId().split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.map(id -> {
|
||||
try {
|
||||
Integer sellerId = Integer.parseInt(id);
|
||||
return sellerNameMap.getOrDefault(sellerId, "");
|
||||
} catch (NumberFormatException e) {
|
||||
return "";
|
||||
}
|
||||
})
|
||||
.filter(name -> !name.isEmpty())
|
||||
.collect(Collectors.joining(", "));
|
||||
order.setSellerName(sellerNames);
|
||||
}
|
||||
|
||||
// 填充创建人名称
|
||||
if (order.getCreatedBy() != null) {
|
||||
String creatorName = creatorNameMap.get(order.getCreatedBy());
|
||||
if (creatorName != null) {
|
||||
order.setCreatedByName(creatorName);
|
||||
}
|
||||
}
|
||||
|
||||
// 填充结算方式描述
|
||||
if (order.getSettlementType() != null) {
|
||||
switch (order.getSettlementType()) {
|
||||
case 1:
|
||||
order.setSettlementTypeDesc("上车重量");
|
||||
break;
|
||||
case 2:
|
||||
order.setSettlementTypeDesc("下车重量");
|
||||
break;
|
||||
case 3:
|
||||
order.setSettlementTypeDesc("按肉价结算");
|
||||
break;
|
||||
default:
|
||||
order.setSettlementTypeDesc("未知");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充订单关联信息(买方名称、卖方名称、创建人名称、结算方式描述)
|
||||
* 用于单个订单详情查询
|
||||
*/
|
||||
private void fillOrderInfo(Order order) {
|
||||
// 填充买方名称
|
||||
@@ -358,5 +562,115 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导入订单
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult batchImportOrders(List<Map<String, Object>> orders) {
|
||||
logger.info("开始批量导入订单,共{}条", orders.size());
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
List<String> failMessages = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < orders.size(); i++) {
|
||||
Map<String, Object> orderMap = orders.get(i);
|
||||
try {
|
||||
// 构建Order对象
|
||||
Order order = new Order();
|
||||
|
||||
// 设置买方ID
|
||||
Object buyerIdObj = orderMap.get("buyerId");
|
||||
if (buyerIdObj != null) {
|
||||
order.setBuyerId(String.valueOf(buyerIdObj));
|
||||
} else {
|
||||
throw new RuntimeException("买方ID不能为空");
|
||||
}
|
||||
|
||||
// 设置卖方ID
|
||||
Object sellerIdObj = orderMap.get("sellerId");
|
||||
if (sellerIdObj != null) {
|
||||
order.setSellerId(String.valueOf(sellerIdObj));
|
||||
} else {
|
||||
throw new RuntimeException("卖方ID不能为空");
|
||||
}
|
||||
|
||||
// 设置结算方式(默认为1-上车重量)
|
||||
Object settlementTypeObj = orderMap.get("settlementType");
|
||||
if (settlementTypeObj != null) {
|
||||
Integer settlementType = null;
|
||||
if (settlementTypeObj instanceof Integer) {
|
||||
settlementType = (Integer) settlementTypeObj;
|
||||
} else if (settlementTypeObj instanceof Number) {
|
||||
settlementType = ((Number) settlementTypeObj).intValue();
|
||||
} else {
|
||||
settlementType = Integer.parseInt(String.valueOf(settlementTypeObj));
|
||||
}
|
||||
if (settlementType < 1 || settlementType > 3) {
|
||||
throw new RuntimeException("结算方式无效,必须为1-3");
|
||||
}
|
||||
order.setSettlementType(settlementType);
|
||||
} else {
|
||||
order.setSettlementType(1); // 默认上车重量
|
||||
}
|
||||
|
||||
// 设置约定价格
|
||||
Object firmPriceObj = orderMap.get("firmPrice");
|
||||
if (firmPriceObj != null) {
|
||||
java.math.BigDecimal firmPrice = null;
|
||||
if (firmPriceObj instanceof java.math.BigDecimal) {
|
||||
firmPrice = (java.math.BigDecimal) firmPriceObj;
|
||||
} else if (firmPriceObj instanceof Number) {
|
||||
firmPrice = new java.math.BigDecimal(String.valueOf(firmPriceObj));
|
||||
} else {
|
||||
firmPrice = new java.math.BigDecimal(String.valueOf(firmPriceObj));
|
||||
}
|
||||
if (firmPrice.compareTo(java.math.BigDecimal.ZERO) < 0) {
|
||||
throw new RuntimeException("约定价格不能小于0");
|
||||
}
|
||||
order.setFirmPrice(firmPrice);
|
||||
} else {
|
||||
throw new RuntimeException("约定价格不能为空");
|
||||
}
|
||||
|
||||
// 设置创建人和创建时间
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
order.setCreatedBy(userId);
|
||||
order.setCreateTime(new Date());
|
||||
|
||||
// 插入数据库
|
||||
int result = orderMapper.insert(order);
|
||||
if (result > 0) {
|
||||
successCount++;
|
||||
logger.info("第{}条订单导入成功,订单ID:{}", i + 1, order.getId());
|
||||
} else {
|
||||
failCount++;
|
||||
failMessages.add(String.format("第%d条:插入数据库失败", i + 1));
|
||||
logger.error("第{}条订单导入失败:插入数据库失败", i + 1);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failCount++;
|
||||
String errorMsg = String.format("第%d条:%s", i + 1, e.getMessage());
|
||||
failMessages.add(errorMsg);
|
||||
logger.error("第{}条订单导入失败:{}", i + 1, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("批量导入完成:成功{}条,失败{}条", successCount, failCount);
|
||||
|
||||
// 构建返回结果
|
||||
Map<String, Object> result = new java.util.HashMap<>();
|
||||
result.put("successCount", successCount);
|
||||
result.put("failCount", failCount);
|
||||
result.put("failMessages", failMessages);
|
||||
|
||||
if (failCount == 0) {
|
||||
return AjaxResult.success("批量导入成功,共导入" + successCount + "条订单", result);
|
||||
} else {
|
||||
return AjaxResult.success("批量导入完成:成功" + successCount + "条,失败" + failCount + "条", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +153,24 @@ public class WarningLogServiceImpl extends ServiceImpl<WarningLogMapper, Warning
|
||||
}
|
||||
|
||||
//获取当前记录的运单信息
|
||||
Delivery delivery = deliveryMapper.selectById(warningLog.getDeliveryId());
|
||||
Integer originalDeliveryId = warningLog.getDeliveryId();
|
||||
Delivery delivery = deliveryMapper.selectById(originalDeliveryId);
|
||||
|
||||
// ✅ 容错处理:如果通过 delivery_id 查不到运单,尝试用预警记录的 id 作为 delivery_id 查询
|
||||
// 这种情况可能是因为数据不一致,预警记录的 id 可能就是运单的 id
|
||||
Integer correctDeliveryId = originalDeliveryId;
|
||||
if (delivery == null && originalDeliveryId != null && !originalDeliveryId.equals(warningLog.getId())) {
|
||||
log.warn("[预警详情] 通过 delivery_id={} 查询不到运单,尝试使用预警记录 id={} 作为 delivery_id 查询",
|
||||
originalDeliveryId, warningLog.getId());
|
||||
delivery = deliveryMapper.selectById(warningLog.getId());
|
||||
if (delivery != null) {
|
||||
log.info("[预警详情] ✅ 容错成功:使用预警记录 id={} 找到了运单,运单号: {}",
|
||||
warningLog.getId(), delivery.getDeliveryNumber());
|
||||
// 使用预警记录的 id 作为正确的 deliveryId
|
||||
correctDeliveryId = warningLog.getId();
|
||||
}
|
||||
}
|
||||
|
||||
if (delivery != null) {
|
||||
BeanUtils.copyProperties(delivery, warningDetailDto);
|
||||
}
|
||||
@@ -162,13 +179,19 @@ public class WarningLogServiceImpl extends ServiceImpl<WarningLogMapper, Warning
|
||||
warningDetailDto.setInventoryJbqCount(warningLog.getInventoryJbqCount());
|
||||
|
||||
// ✅ 设置运单ID(用于前端查询设备列表和日志)
|
||||
warningDetailDto.setDeliveryId(warningLog.getDeliveryId());
|
||||
log.info("[预警详情] 设置运单ID: {}", warningLog.getDeliveryId());
|
||||
// 使用修正后的 deliveryId(如果容错成功,这里会是预警记录的 id)
|
||||
warningDetailDto.setDeliveryId(correctDeliveryId);
|
||||
log.info("[预警详情] 设置运单ID: {} (原始 delivery_id: {})", correctDeliveryId, originalDeliveryId);
|
||||
|
||||
//获取当前运单关联的设备信息
|
||||
List<DeliveryDevice> deliveryDevices = deliveryDeviceMapper.selectList(
|
||||
new LambdaQueryWrapper<DeliveryDevice>().eq(DeliveryDevice::getDeliveryId, delivery.getId())
|
||||
);
|
||||
List<DeliveryDevice> deliveryDevices = new ArrayList<>();
|
||||
if (delivery != null) {
|
||||
deliveryDevices = deliveryDeviceMapper.selectList(
|
||||
new LambdaQueryWrapper<DeliveryDevice>().eq(DeliveryDevice::getDeliveryId, delivery.getId())
|
||||
);
|
||||
} else {
|
||||
log.warn("[预警详情] 运单不存在,deliveryId: {} (原始: {})", correctDeliveryId, originalDeliveryId);
|
||||
}
|
||||
|
||||
String mainDeviceId = null;
|
||||
if (CollectionUtils.isNotEmpty(deliveryDevices)) {
|
||||
@@ -187,34 +210,38 @@ public class WarningLogServiceImpl extends ServiceImpl<WarningLogMapper, Warning
|
||||
warningDetailDto.setJbqDeviceSn(jbqDeviceCollect);
|
||||
} else {
|
||||
// ✅ 如果 delivery_device 表为空,尝试从 iot_device_data 表查询(兼容旧数据)
|
||||
log.info("[预警详情] delivery_device 表无数据,尝试从 iot_device_data 表查询设备");
|
||||
List<IotDeviceData> iotDevices = iotDeviceDataMapper.selectList(
|
||||
new LambdaQueryWrapper<IotDeviceData>().eq(IotDeviceData::getDeliveryId, delivery.getId())
|
||||
);
|
||||
|
||||
if (CollectionUtils.isNotEmpty(iotDevices)) {
|
||||
log.info("[预警详情] 从 iot_device_data 查询到 {} 个设备", iotDevices.size());
|
||||
if (delivery != null) {
|
||||
log.info("[预警详情] delivery_device 表无数据,尝试从 iot_device_data 表查询设备");
|
||||
List<IotDeviceData> iotDevices = iotDeviceDataMapper.selectList(
|
||||
new LambdaQueryWrapper<IotDeviceData>().eq(IotDeviceData::getDeliveryId, delivery.getId())
|
||||
);
|
||||
|
||||
// 查找主机设备(类型1或4)
|
||||
List<String> serverCollect = iotDevices.stream()
|
||||
.filter(device -> device.getDeviceType() != null &&
|
||||
(device.getDeviceType() == 1 || device.getDeviceType() == 4))
|
||||
.map(IotDeviceData::getDeviceId)
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtils.isNotEmpty(serverCollect)) {
|
||||
mainDeviceId = serverCollect.get(0);
|
||||
warningDetailDto.setServerDeviceSn(mainDeviceId);
|
||||
log.info("[预警详情] 找到主机设备: {}", mainDeviceId);
|
||||
if (CollectionUtils.isNotEmpty(iotDevices)) {
|
||||
log.info("[预警详情] 从 iot_device_data 查询到 {} 个设备", iotDevices.size());
|
||||
|
||||
// 查找主机设备(类型1或4)
|
||||
List<String> serverCollect = iotDevices.stream()
|
||||
.filter(device -> device.getDeviceType() != null &&
|
||||
(device.getDeviceType() == 1 || device.getDeviceType() == 4))
|
||||
.map(IotDeviceData::getDeviceId)
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtils.isNotEmpty(serverCollect)) {
|
||||
mainDeviceId = serverCollect.get(0);
|
||||
warningDetailDto.setServerDeviceSn(mainDeviceId);
|
||||
log.info("[预警详情] 找到主机设备: {}", mainDeviceId);
|
||||
}
|
||||
|
||||
// 查找耳标设备(类型2)
|
||||
List<String> jbqDeviceCollect = iotDevices.stream()
|
||||
.filter(device -> device.getDeviceType() != null && device.getDeviceType() == 2)
|
||||
.map(IotDeviceData::getDeviceId)
|
||||
.collect(Collectors.toList());
|
||||
warningDetailDto.setJbqDeviceSn(jbqDeviceCollect);
|
||||
} else {
|
||||
log.warn("[预警详情] iot_device_data 表也无设备数据");
|
||||
}
|
||||
|
||||
// 查找耳标设备(类型2)
|
||||
List<String> jbqDeviceCollect = iotDevices.stream()
|
||||
.filter(device -> device.getDeviceType() != null && device.getDeviceType() == 2)
|
||||
.map(IotDeviceData::getDeviceId)
|
||||
.collect(Collectors.toList());
|
||||
warningDetailDto.setJbqDeviceSn(jbqDeviceCollect);
|
||||
} else {
|
||||
log.warn("[预警详情] iot_device_data 表也无设备数据");
|
||||
log.warn("[预警详情] 运单不存在,无法查询设备信息");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,5 +82,37 @@ public class DeliveryLogVo {
|
||||
*/
|
||||
private String createByDesc;
|
||||
|
||||
/**
|
||||
* 起始地
|
||||
*/
|
||||
private String startLocation;
|
||||
|
||||
/**
|
||||
* 送达目的地
|
||||
*/
|
||||
private String endLocation;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 预计送达时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date estimatedDeliveryTime;
|
||||
|
||||
/**
|
||||
* 司机姓名
|
||||
*/
|
||||
private String driverName;
|
||||
|
||||
/**
|
||||
* 创建人(从 sys_user 表关联查询)
|
||||
*/
|
||||
private String createByName;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -40,10 +40,25 @@ public class XxlJobConfig {
|
||||
@Value("${xxl.job.executor.logretentiondays}")
|
||||
private int logRetentionDays;
|
||||
|
||||
@Value("${xxl.job.executor.connect-timeout:30000}")
|
||||
private int connectTimeout;
|
||||
|
||||
@Value("${xxl.job.executor.read-timeout:30000}")
|
||||
private int readTimeout;
|
||||
|
||||
|
||||
@Bean
|
||||
public XxlJobSpringExecutor xxlJobExecutor() {
|
||||
logger.info(">>>>>>>>>>> xxl-job config init.");
|
||||
|
||||
// 设置 HTTP 连接超时和读取超时时间(通过系统属性)
|
||||
// XXL-Job 内部使用 HttpsURLConnection,通过系统属性可以设置默认超时时间
|
||||
System.setProperty("sun.net.client.defaultConnectTimeout", String.valueOf(connectTimeout));
|
||||
System.setProperty("sun.net.client.defaultReadTimeout", String.valueOf(readTimeout));
|
||||
|
||||
logger.info(">>>>>>>>>>> xxl-job timeout config: connectTimeout={}ms, readTimeout={}ms",
|
||||
connectTimeout, readTimeout);
|
||||
|
||||
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
|
||||
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
|
||||
xxlJobSpringExecutor.setAppname(appname);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.aiotagro.common.core.constant;
|
||||
|
||||
/**
|
||||
* 百度鹰眼常量配置
|
||||
*
|
||||
* <p>注意:AK 与 ServiceId 根据业务要求写死在后端,禁止透出给前端。</p>
|
||||
*/
|
||||
public final class BaiduYingyanConstants {
|
||||
|
||||
private BaiduYingyanConstants() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 百度鹰眼控制台申请的 AK
|
||||
*/
|
||||
public static final String AK = "xITbC7jegaAAuu4m9jC2Zx6eFbQJ29Rj";
|
||||
|
||||
/**
|
||||
* 百度鹰眼服务 ID
|
||||
*/
|
||||
public static final long SERVICE_ID = 242517L;
|
||||
|
||||
/**
|
||||
* 百度鹰眼 API 基础路径
|
||||
*/
|
||||
public static final String BASE_URL = "https://yingyan.baidu.com/api/v3";
|
||||
}
|
||||
|
||||
@@ -99,6 +99,10 @@ xxl:
|
||||
logpath: /data/applogs/xxl-job/jobhandler
|
||||
# 日志保存时间
|
||||
logretentiondays: 30
|
||||
# 连接超时时间(毫秒),默认30秒
|
||||
connect-timeout: 30000
|
||||
# 读取超时时间(毫秒),默认30秒
|
||||
read-timeout: 30000
|
||||
address:
|
||||
ip:
|
||||
# 日志配置
|
||||
|
||||
@@ -108,6 +108,10 @@ xxl:
|
||||
logpath: /data/applogs/xxl-job/jobhandler
|
||||
# 日志保存时间
|
||||
logretentiondays: 30
|
||||
# 连接超时时间(毫秒),默认30秒
|
||||
connect-timeout: 30000
|
||||
# 读取超时时间(毫秒),默认30秒
|
||||
read-timeout: 30000
|
||||
address:
|
||||
ip:
|
||||
|
||||
|
||||
@@ -101,6 +101,10 @@ xxl:
|
||||
logpath: /data/applogs/xxl-job/jobhandler
|
||||
# 日志保存时间
|
||||
logretentiondays: 30
|
||||
# 连接超时时间(毫秒),默认30秒
|
||||
connect-timeout: 30000
|
||||
# 读取超时时间(毫秒),默认30秒
|
||||
read-timeout: 30000
|
||||
address:
|
||||
ip:
|
||||
# 日志配置 - 生产环境应使用更高的日志级别以提升性能
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
USE cattletrade;
|
||||
|
||||
-- 为运送清单表新增百度鹰眼同步字段
|
||||
ALTER TABLE `delivery`
|
||||
ADD COLUMN `yingyan_entity_name` VARCHAR(64) NULL DEFAULT NULL COMMENT '百度鹰眼终端名称';
|
||||
|
||||
ALTER TABLE `delivery`
|
||||
ADD COLUMN `yingyan_last_sync_time` DATETIME NULL DEFAULT NULL COMMENT '百度鹰眼最后同步时间';
|
||||
|
||||
ALTER TABLE `delivery`
|
||||
ADD COLUMN `arrival_time` DATETIME NULL DEFAULT NULL COMMENT '自动判定的到达时间';
|
||||
|
||||
@@ -64,31 +64,38 @@
|
||||
wl.warning_time,
|
||||
wl.inventory_jbq_count,
|
||||
su.name as createByName
|
||||
FROM delivery d inner join warning_log wl on d.id = wl.delivery_id
|
||||
left join sys_user su on d.created_by = su.id
|
||||
FROM delivery d
|
||||
INNER JOIN warning_log wl ON d.id = wl.delivery_id
|
||||
LEFT JOIN sys_user su ON d.created_by = su.id
|
||||
<where>
|
||||
wl.warning_type in (2,3,4,5,6,7,8,9)
|
||||
and wl.id in (select max(id) from warning_log where delivery_id = d.id group by warning_type)
|
||||
wl.warning_type IN (2,3,4,5,6,7,8,9)
|
||||
AND wl.id IN (
|
||||
SELECT MAX(id)
|
||||
FROM warning_log
|
||||
WHERE delivery_id = d.id
|
||||
AND warning_type IN (2,3,4,5,6,7,8,9)
|
||||
GROUP BY warning_type
|
||||
)
|
||||
<if test="dto.deliveryNumber != null and '' != dto.deliveryNumber">
|
||||
and d.delivery_number like concat('%', #{dto.deliveryNumber}, '%')
|
||||
AND d.delivery_number LIKE CONCAT('%', #{dto.deliveryNumber}, '%')
|
||||
</if>
|
||||
<if test="dto.licensePlate != null and '' != dto.licensePlate">
|
||||
and d.license_plate like concat('%', #{dto.licensePlate}, '%')
|
||||
AND d.license_plate LIKE CONCAT('%', #{dto.licensePlate}, '%')
|
||||
</if>
|
||||
<if test="dto.startTime != null and '' != dto.startTime">
|
||||
and d.create_time <![CDATA[ >= ]]> #{dto.startTime}
|
||||
AND d.create_time <![CDATA[ >= ]]> #{dto.startTime}
|
||||
</if>
|
||||
<if test="dto.endTime != null and '' != dto.endTime">
|
||||
and d.create_time <![CDATA[ <= ]]> #{dto.endTime}
|
||||
AND d.create_time <![CDATA[ <= ]]> #{dto.endTime}
|
||||
</if>
|
||||
<if test="dto.warningType != null">
|
||||
and wl.warning_type = #{dto.warningType}
|
||||
AND wl.warning_type = #{dto.warningType}
|
||||
</if>
|
||||
<if test="dto.status != null">
|
||||
and d.status = #{dto.status}
|
||||
AND d.status = #{dto.status}
|
||||
</if>
|
||||
</where>
|
||||
order by wl.warning_time desc
|
||||
ORDER BY wl.warning_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -55,4 +55,33 @@
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<select id="listLogsForYingyan" resultType="com.aiotagro.cattletrade.business.entity.JbqClientLog">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM
|
||||
jbq_client_log
|
||||
<where>
|
||||
<if test="deviceIds != null and deviceIds.size > 0">
|
||||
device_id IN
|
||||
<foreach collection="deviceIds" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="startTime != null">
|
||||
AND update_time <![CDATA[>=]]> #{startTime}
|
||||
</if>
|
||||
<if test="endTime != null">
|
||||
AND update_time <![CDATA[<=]]> #{endTime}
|
||||
</if>
|
||||
AND latitude IS NOT NULL
|
||||
AND latitude != ''
|
||||
AND longitude IS NOT NULL
|
||||
AND longitude != ''
|
||||
</where>
|
||||
ORDER BY update_time ASC
|
||||
<if test="limit != null">
|
||||
LIMIT #{limit}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -46,4 +46,33 @@
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<select id="listLogsForYingyan" resultType="com.aiotagro.cattletrade.business.entity.JbqServerLog">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM
|
||||
jbq_server_log
|
||||
<where>
|
||||
<if test="deviceIds != null and deviceIds.size > 0">
|
||||
device_id IN
|
||||
<foreach collection="deviceIds" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="startTime != null">
|
||||
AND update_time <![CDATA[>=]]> #{startTime}
|
||||
</if>
|
||||
<if test="endTime != null">
|
||||
AND update_time <![CDATA[<=]]> #{endTime}
|
||||
</if>
|
||||
AND latitude IS NOT NULL
|
||||
AND latitude != ''
|
||||
AND longitude IS NOT NULL
|
||||
AND longitude != ''
|
||||
</where>
|
||||
ORDER BY update_time ASC
|
||||
<if test="limit != null">
|
||||
LIMIT #{limit}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -57,4 +57,33 @@
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<select id="listLogsForYingyan" resultType="com.aiotagro.cattletrade.business.entity.XqClientLog">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM
|
||||
xq_client_log
|
||||
<where>
|
||||
<if test="deviceIds != null and deviceIds.size > 0">
|
||||
device_id IN
|
||||
<foreach collection="deviceIds" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="startTime != null">
|
||||
AND update_time <![CDATA[>=]]> #{startTime}
|
||||
</if>
|
||||
<if test="endTime != null">
|
||||
AND update_time <![CDATA[<=]]> #{endTime}
|
||||
</if>
|
||||
AND latitude IS NOT NULL
|
||||
AND latitude != ''
|
||||
AND longitude IS NOT NULL
|
||||
AND longitude != ''
|
||||
</where>
|
||||
ORDER BY update_time ASC
|
||||
<if test="limit != null">
|
||||
LIMIT #{limit}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
Reference in New Issue
Block a user