From e968fcf52ae3b7ab7b598ea07017edb44a3efb40 Mon Sep 17 00:00:00 2001 From: xuqiuyun <1113560936@qq.com> Date: Fri, 28 Nov 2025 17:12:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A1=B9=E7=9B=AE=E7=BB=86?= =?UTF-8?q?=E8=8A=82=E5=92=8Csql=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pc-cattle-transportation/src/api/shipping.js | 24 + pc-cattle-transportation/src/main.ts | 2 +- pc-cattle-transportation/src/router/index.ts | 25 +- .../src/utils/loadBmap.js | 2 +- .../earlywarning/warningDetailDialog.vue | 12 +- .../src/views/entry/attestation.vue | 102 ++- .../src/views/entry/details.vue | 149 +++- .../views/shipping/createDeliveryDialog.vue | 439 +++++++++- .../src/views/shipping/loadingOrder.vue | 87 +- .../src/views/shipping/orderDialog.vue | 65 +- .../business/controller/OrderController.java | 79 +- .../business/dto/DeliveryEditDto.java | 6 + .../cattletrade/business/entity/Delivery.java | 6 + .../business/mapper/MemberDriverMapper.java | 17 + .../business/mapper/VehicleMapper.java | 15 + .../business/service/IOrderService.java | 8 + .../service/impl/DeliveryServiceImpl.java | 771 ++++++++---------- .../service/impl/OrderServiceImpl.java | 586 ++++++++++--- .../config/DruidDataSourceConfig.java | 25 +- .../src/main/resources/application-demo.yml | 39 +- .../src/main/resources/application-dev.yml | 39 +- .../src/main/resources/application-prod.yml | 39 +- .../migration/alter_warning_log_add_index.sql | 63 ++ .../main/resources/mapper/DeliveryMapper.xml | 22 +- 24 files changed, 1928 insertions(+), 694 deletions(-) create mode 100644 tradeCattle/aiotagro-cattle-trade/src/main/resources/db/migration/alter_warning_log_add_index.sql diff --git a/pc-cattle-transportation/src/api/shipping.js b/pc-cattle-transportation/src/api/shipping.js index 4a82338..cbf738a 100644 --- a/pc-cattle-transportation/src/api/shipping.js +++ b/pc-cattle-transportation/src/api/shipping.js @@ -326,4 +326,28 @@ export function updateDeliveryInfo(data) { method: 'POST', data, }); +} + +// ==================== 订单关联管理 API ==================== + +// 解除订单与运送清单的关联 +export function unbindOrderFromDelivery(orderId) { + return request({ + url: '/order/unbindFromDelivery', + method: 'POST', + data: { orderId }, + }); +} + +// 查询运送清单关联的订单列表 +export function getOrdersByDeliveryId(deliveryId) { + return request({ + url: '/order/list', + method: 'POST', + data: { + deliveryId: deliveryId, + pageNum: 1, + pageSize: 1000, // 获取所有关联的订单 + }, + }); } \ No newline at end of file diff --git a/pc-cattle-transportation/src/main.ts b/pc-cattle-transportation/src/main.ts index 880d29a..0679c90 100644 --- a/pc-cattle-transportation/src/main.ts +++ b/pc-cattle-transportation/src/main.ts @@ -98,7 +98,7 @@ app.use(JsonViewer); app.use(BaiduMap, { // ak 是在百度地图开发者平台申请的密钥 详见 http://lbsyun.baidu.com/apiconsole/key */ - ak: 'fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC', // + ak: 'xITbC7jegaAAuu4m9jC2Zx6eFbQJ29Rj', // // v: '2.0', // 默认使用3.0 // type: 'WebGL' // ||API 默认API (使用此模式 BMap=BMapGL) // type: 'WebGL', // ||API 默认API (使用此模式 BMap=BMapGL) diff --git a/pc-cattle-transportation/src/router/index.ts b/pc-cattle-transportation/src/router/index.ts index 541bdb2..45552b4 100644 --- a/pc-cattle-transportation/src/router/index.ts +++ b/pc-cattle-transportation/src/router/index.ts @@ -237,18 +237,6 @@ export const constantRoutes: Array = [ requireAuth: true, }, children: [ - { - path: 'loadingOrder', // ✅ 修复:使用相对路径 - name: 'LoadingOrder', - meta: { - title: '装车订单', - keepAlive: true, - requireAuth: true, - }, - component: () => import('~/views/shipping/loadingOrder.vue'), - }, - - { path: 'shippinglist', // ✅ 修复:使用相对路径 name: 'ShippingList', @@ -259,6 +247,19 @@ export const constantRoutes: Array = [ }, component: () => import('~/views/entry/attestation.vue'), }, + { + path: 'loadingOrder', // ✅ 修复:使用相对路径 + name: 'LoadingOrder', + meta: { + title: '订单信息', + keepAlive: true, + requireAuth: true, + }, + component: () => import('~/views/shipping/loadingOrder.vue'), + }, + + + ], }, diff --git a/pc-cattle-transportation/src/utils/loadBmap.js b/pc-cattle-transportation/src/utils/loadBmap.js index 670f9e9..8dfdfe3 100644 --- a/pc-cattle-transportation/src/utils/loadBmap.js +++ b/pc-cattle-transportation/src/utils/loadBmap.js @@ -6,7 +6,7 @@ export function BMPGL(ak) { }; const script = document.createElement('script'); script.type = 'text/javascript'; - script.src = `http://api.map.baidu.com/api?v=3.0&type=webgl&ak=${ak}&callback=init`; + script.src = `https://api.map.baidu.com/api?v=3.0&type=webgl&ak=${ak}&callback=init`; script.onerror = reject; document.head.appendChild(script); }); diff --git a/pc-cattle-transportation/src/views/earlywarning/warningDetailDialog.vue b/pc-cattle-transportation/src/views/earlywarning/warningDetailDialog.vue index 1ba4bfc..a9780c0 100644 --- a/pc-cattle-transportation/src/views/earlywarning/warningDetailDialog.vue +++ b/pc-cattle-transportation/src/views/earlywarning/warningDetailDialog.vue @@ -749,7 +749,7 @@ const loadDeviceLogs = async (deviceId, deviceType, deliveryId) => { const initMap = async () => { try { // 使用百度地图 API Key - const BMapGL = await BMPGL('fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC'); + const BMapGL = await BMPGL('xITbC7jegaAAuu4m9jC2Zx6eFbQJ29Rj'); const lat = parseFloat(warningData.latitude); const lon = parseFloat(warningData.longitude); @@ -1054,7 +1054,7 @@ const initTrackMap = async () => { } try { - const BMapGL = await BMPGL('fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC'); + const BMapGL = await BMPGL('xITbC7jegaAAuu4m9jC2Zx6eFbQJ29Rj'); trackBMapGL.value = BMapGL; // 保存 BMapGL 实例 // 创建地图实例 @@ -1147,7 +1147,7 @@ const drawStaticTrack = (BMapGL) => { // 使用简单的圆形标记作为起点(绿色) const startMarker = new BMapGL.Marker(startPoint, { icon: new BMapGL.Icon( - 'http://api.map.baidu.com/images/marker_red.png', + 'https://api.map.baidu.com/images/marker_red.png', new BMapGL.Size(32, 32), { anchor: new BMapGL.Size(16, 32) } ) @@ -1174,7 +1174,7 @@ const drawStaticTrack = (BMapGL) => { // 使用红色标记作为终点 const endMarker = new BMapGL.Marker(endPoint, { icon: new BMapGL.Icon( - 'http://api.map.baidu.com/images/marker_red.png', + 'https://api.map.baidu.com/images/marker_red.png', new BMapGL.Size(32, 32), { anchor: new BMapGL.Size(16, 32) } ) @@ -1219,7 +1219,7 @@ const drawDynamicTrack = (BMapGL) => { const startPoint = new BMapGL.Point(trackPath.value[0].lng, trackPath.value[0].lat); const startMarker = new BMapGL.Marker(startPoint, { icon: new BMapGL.Icon( - 'http://api.map.baidu.com/images/marker_red.png', + 'https://api.map.baidu.com/images/marker_red.png', new BMapGL.Size(32, 32), { anchor: new BMapGL.Size(16, 32) } ) @@ -1232,7 +1232,7 @@ const drawDynamicTrack = (BMapGL) => { const currentPoint = new BMapGL.Point(trackPath.value[0].lng, trackPath.value[0].lat); const playMarker = new BMapGL.Marker(currentPoint, { icon: new BMapGL.Icon( - 'http://api.map.baidu.com/images/marker_red.png', + 'https://api.map.baidu.com/images/marker_red.png', new BMapGL.Size(20, 20), { anchor: new BMapGL.Size(10, 20) } ) diff --git a/pc-cattle-transportation/src/views/entry/attestation.vue b/pc-cattle-transportation/src/views/entry/attestation.vue index d7122b6..710e109 100644 --- a/pc-cattle-transportation/src/views/entry/attestation.vue +++ b/pc-cattle-transportation/src/views/entry/attestation.vue @@ -2,6 +2,21 @@
+ +
+
+ + + 新增运送清单 + +
+
+
@@ -94,11 +109,12 @@ - + @@ -132,6 +150,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'; import baseSearch from '@/components/common/searchCustom/index.vue'; import Pagination from '@/components/Pagination/index.vue'; import createDeliveryDialog from '@/views/shipping/createDeliveryDialog.vue'; +import OrderDialog from '@/views/shipping/orderDialog.vue'; import { inspectionList, downloadZip, pageDeviceList } from '@/api/abroad.js'; import { updateDeliveryStatus, deleteDeliveryLogic, downloadDeliveryPackage, getDeliveryDetail, downloadAcceptanceForm } from '@/api/shipping.js'; @@ -139,6 +158,7 @@ const router = useRouter(); const route = useRoute(); const baseSearchRef = ref(); const editDialogRef = ref(); +const OrderDialogRef = ref(); const dataListLoading = ref(false); const downLoading = reactive({}); const form = reactive({ @@ -837,9 +857,87 @@ const downloadAcceptanceFormHandler = async (row) => { } }; +// 显示创建订单对话框 +const showAddOrderDialog = (deliveryId) => { + if (OrderDialogRef.value) { + OrderDialogRef.value.onShowDialog(null, deliveryId); + } +}; + +// 显示新增运送清单对话框 +const showCreateDeliveryDialog = () => { + if (editDialogRef.value) { + editDialogRef.value.open(); + } +}; + +// 订单创建成功回调 +const handleOrderSuccess = () => { + getDataList(); +}; + onMounted(() => { getDataList(); }); - + diff --git a/pc-cattle-transportation/src/views/entry/details.vue b/pc-cattle-transportation/src/views/entry/details.vue index f5cbac9..de31040 100644 --- a/pc-cattle-transportation/src/views/entry/details.vue +++ b/pc-cattle-transportation/src/views/entry/details.vue @@ -33,8 +33,8 @@ {{ data.baseInfo.deliveryNumber || '-' }} - {{ data.baseInfo.supplierName || '-' }} - {{ data.baseInfo.buyerName || '-' }} + {{ getSupplierName() }} + {{ getBuyerName() }} {{ data.baseInfo.licensePlate || '-' }} {{ data.baseInfo.driverName || '-' }} {{ data.baseInfo.startLocation || '-' }} @@ -85,18 +85,41 @@
-
重量信息
- - - {{ data.baseInfo.emptyWeight }}kg - - - {{ data.baseInfo.entruckWeight }}kg - - - {{ data.baseInfo.landingEntruckWeight }}kg - - +
+ 重量信息 +
+
+ +
+ + + {{ data.baseInfo.emptyWeight }}kg + + + {{ data.baseInfo.entruckWeight }}kg + + + {{ data.baseInfo.landingEntruckWeight }}kg + + +
+ +
+ + + {{ departureCattleWeight }}kg + + + {{ landingCattleWeight }}kg + + + + {{ weightLoss > 0 ? '-' : weightLoss < 0 ? '+' : '' }}{{ Math.abs(weightLoss) }}kg + + + +
+
@@ -185,7 +208,7 @@ />
-
到地纸质磅单
+
落地纸质磅单
-
到地过重磅车头
+
落地过重磅车头
-
到地过磅视频
+
落地过磅视频
@@ -1119,6 +1142,40 @@ const getStatusType = (status) => { }; // 判断字段是否有有效值(用于隐藏空值字段) +// 计算发车牛只重量(装车过磅重量 - 空车过磅重量) +const departureCattleWeight = computed(() => { + const emptyWeight = parseFloat(data.baseInfo.emptyWeight); + const entruckWeight = parseFloat(data.baseInfo.entruckWeight); + if (!isNaN(emptyWeight) && !isNaN(entruckWeight) && emptyWeight > 0 && entruckWeight > 0) { + const result = entruckWeight - emptyWeight; + return result > 0 ? result.toFixed(2) : null; + } + return null; +}); + +// 计算落地牛只重量(落地过磅重量 - 空车过磅重量) +const landingCattleWeight = computed(() => { + const emptyWeight = parseFloat(data.baseInfo.emptyWeight); + const landingWeight = parseFloat(data.baseInfo.landingEntruckWeight); + if (!isNaN(emptyWeight) && !isNaN(landingWeight) && emptyWeight > 0 && landingWeight > 0) { + const result = landingWeight - emptyWeight; + return result > 0 ? result.toFixed(2) : null; + } + return null; +}); + +// 计算损耗(发车牛只重量 - 落地牛只重量) +// 正数表示损耗(减少),负数表示增重(增加) +const weightLoss = computed(() => { + const departure = departureCattleWeight.value; + const landing = landingCattleWeight.value; + if (departure !== null && landing !== null) { + const result = parseFloat(departure) - parseFloat(landing); + return result.toFixed(2); + } + return null; +}); + const hasValue = (value) => { if (value === null || value === undefined) { return false; @@ -1129,6 +1186,26 @@ const hasValue = (value) => { return true; }; +// 获取卖方名称:如果是从订单页面进入的,使用订单的卖方;否则显示 '-' +const getSupplierName = () => { + // 检查是否从订单页面进入(通过路由参数 fromOrder 判断) + if (route.query.fromOrder === 'true' && route.query.sellerName) { + return route.query.sellerName; + } + // 不是从订单页面进入的,直接显示 '-' + return '-'; +}; + +// 获取买方名称:如果是从订单页面进入的,使用订单的买方;否则显示 '-' +const getBuyerName = () => { + // 检查是否从订单页面进入(通过路由参数 fromOrder 判断) + if (route.query.fromOrder === 'true' && route.query.buyerName) { + return route.query.buyerName; + } + // 不是从订单页面进入的,直接显示 '-' + return '-'; +}; + onMounted(() => { data.id = route.query.id; data.status = route.query.status; @@ -1260,6 +1337,44 @@ onMounted(() => { } } +.weight-info-container { + display: flex; + flex-direction: row; + gap: 20px; + flex-wrap: wrap; +} + +.weight-basic-info, +.weight-calculated-info { + flex: 1; + min-width: 400px; +} + +.weight-calculated-info { + .calculated-value { + font-weight: 600; + font-size: 16px; + color: #303133; + } + + .loss-value { + &.positive-loss { + color: #67c23a; // 绿色表示增重 + } + + &.negative-loss { + color: #f56c6c; // 红色表示损耗 + } + } +} + +@media (max-width: 1200px) { + .weight-basic-info, + .weight-calculated-info { + min-width: 100%; + } +} + .media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); diff --git a/pc-cattle-transportation/src/views/shipping/createDeliveryDialog.vue b/pc-cattle-transportation/src/views/shipping/createDeliveryDialog.vue index 82820ac..101a869 100644 --- a/pc-cattle-transportation/src/views/shipping/createDeliveryDialog.vue +++ b/pc-cattle-transportation/src/views/shipping/createDeliveryDialog.vue @@ -22,12 +22,13 @@ clearable filterable style="width: 100%" + :key="`order-select-${orderList.length}`" @change="handleOrderChange" > @@ -35,6 +36,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -219,26 +268,53 @@ + + + + + + + - - - - - - - - + - + - + - + @@ -279,7 +355,7 @@ - +
- +
- +
- +
- +
- +
- +
- +
- + - + - + - + - + - + - + - + ") + List> selectDriverByIds(@Param("driverIds") List driverIds); + /** * 根据车牌号查询司机信息(已废弃:司机表不再有车牌号字段) * 该方法保留是为了兼容性,但会返回 null diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/VehicleMapper.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/VehicleMapper.java index e2f700f..1a3abed 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/VehicleMapper.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/VehicleMapper.java @@ -38,5 +38,20 @@ public interface VehicleMapper extends BaseMapper { "ORDER BY create_time DESC " + "") List selectVehicleList(@Param("licensePlate") String licensePlate); + + /** + * 批量根据车牌号查询车辆信息 + * 用于优化性能,避免 N+1 查询问题 + * @param licensePlates 车牌号列表 + * @return 车辆列表 + */ + @Select("") + List selectByLicensePlates(@Param("licensePlates") List licensePlates); } diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IOrderService.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IOrderService.java index b99605d..6d63566 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IOrderService.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IOrderService.java @@ -72,5 +72,13 @@ public interface IOrderService extends IService { * @return AjaxResult */ AjaxResult updateOrderDeliveryId(Integer deliveryId, List orderIds); + + /** + * 解除订单与运送清单的关联(将订单的deliveryId设置为null) + * + * @param orderId 订单ID + * @return AjaxResult + */ + AjaxResult unbindOrderFromDelivery(Integer orderId); } diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/DeliveryServiceImpl.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/DeliveryServiceImpl.java index 44c0e50..f026a4f 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/DeliveryServiceImpl.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/DeliveryServiceImpl.java @@ -136,228 +136,9 @@ public class DeliveryServiceImpl extends ServiceImpl i // 需要权限过滤:先查询所有数据,填充信息,过滤后再分页 list = this.list(wrapper); - // 填充关联信息(供应商、资金方、采购商、司机等) + // 使用批量查询填充关联信息(优化性能,避免 N+1 查询) 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 supplierNames = new ArrayList<>(); - List supplierMobiles = new ArrayList<>(); - for (String supplierId : supplierIds) { - if (StringUtils.isNotEmpty(supplierId.trim())) { - try { - Integer sid = Integer.parseInt(supplierId.trim()); - // 查询member和member_user表关联数据 - Map 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); - } - } else { - } - } 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 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); - } - } else { - } - } - - // 3. 查询采购商信息 - if (delivery.getBuyerId() != null) { - Map 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); - } - } else { - } - } - - // 4. 查询司机手机号(如果有司机ID) - if (delivery.getDriverId() != null) { - try { - Map 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; - logger.debug("从车辆表获取照片: 车牌号={}, 车头照片={}, 车尾照片={}", - delivery.getLicensePlate(), delivery.getCarFrontPhoto(), delivery.getCarBehindPhoto()); - } - } - } 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 earTagWrapper = new LambdaQueryWrapper<>(); - earTagWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId) - .eq(DeliveryDevice::getDeviceType, 2); - long earTagCount = deliveryDeviceService.count(earTagWrapper); - - // 统计项圈设备数量 - LambdaQueryWrapper 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 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 wornCollarWrapper = new LambdaQueryWrapper<>(); - wornCollarWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId) - .eq(DeliveryDevice::getDeviceType, 3); - List collarDevices = deliveryDeviceService.list(wornCollarWrapper); - - int wornCollarCount = 0; - for (DeliveryDevice device : collarDevices) { - // 查询xq_client表中的bandge_status - LambdaQueryWrapper 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); - } - } - }); + batchFillDeliveryRelations(list, userId); } // 数据权限过滤:非超级管理员只能看到与自己手机号相关的订单 @@ -413,223 +194,9 @@ public class DeliveryServiceImpl extends ServiceImpl i list = this.list(wrapper); total = result.getTotal(); - // 填充关联信息(供应商、资金方、采购商、司机等) + // 使用批量查询填充关联信息(优化性能,避免 N+1 查询) 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 supplierNames = new ArrayList<>(); - List supplierMobiles = new ArrayList<>(); - for (String supplierId : supplierIds) { - if (StringUtils.isNotEmpty(supplierId.trim())) { - try { - Integer sid = Integer.parseInt(supplierId.trim()); - // 查询member和member_user表关联数据 - Map 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 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 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 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 earTagWrapper = new LambdaQueryWrapper<>(); - earTagWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId) - .eq(DeliveryDevice::getDeviceType, 2); - long earTagCount = deliveryDeviceService.count(earTagWrapper); - - // 统计项圈设备数量 - LambdaQueryWrapper 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 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 wornCollarWrapper = new LambdaQueryWrapper<>(); - wornCollarWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId) - .eq(DeliveryDevice::getDeviceType, 3); - List collarDevices = deliveryDeviceService.list(wornCollarWrapper); - - int wornCollarCount = 0; - for (DeliveryDevice device : collarDevices) { - // 查询xq_client表中的bandge_status - LambdaQueryWrapper 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); - } - } - }); + batchFillDeliveryRelations(list, userId); } } @@ -799,6 +366,10 @@ public class DeliveryServiceImpl extends ServiceImpl i delivery.setLandingEntruckWeight(StringUtils.isNotEmpty(dto.getLandingEntruckWeight()) ? dto.getLandingEntruckWeight() : null); // 司机运费 delivery.setFreight(dto.getFreight()); + // 检疫证号 + if (dto.getQuarantineCertNo() != null && !dto.getQuarantineCertNo().trim().isEmpty()) { + delivery.setQuarantineTickey(dto.getQuarantineCertNo().trim()); + } // 照片 delivery.setQuarantineTickeyUrl(dto.getQuarantineTickeyUrl()); delivery.setPoundListImg(dto.getPoundListImg()); @@ -1046,6 +617,53 @@ public class DeliveryServiceImpl extends ServiceImpl i delivery.setFirmPrice(dto.getFirmPrice()); } + // 更新订单ID关联(追加模式:如果已有订单ID,则追加;如果没有,则直接设置) + if (dto.getOrderId() != null && StringUtils.isNotEmpty(dto.getOrderId())) { + String newOrderIdStr = dto.getOrderId().trim(); + String existingOrderId = delivery.getOrderId(); + + // 将新订单ID字符串按逗号分割为列表(支持多个订单ID) + List newOrderIdList = Arrays.asList(newOrderIdStr.split(",")); + newOrderIdList = newOrderIdList.stream() + .map(String::trim) + .filter(id -> !id.isEmpty()) + .collect(java.util.stream.Collectors.toList()); + + if (StringUtils.isNotEmpty(existingOrderId)) { + // 如果已有订单ID,将现有订单ID也按逗号分割 + List orderIdList = new ArrayList<>(Arrays.asList(existingOrderId.split(","))); + // 去除空白并过滤空字符串 + orderIdList = orderIdList.stream() + .map(String::trim) + .filter(id -> !id.isEmpty()) + .collect(java.util.stream.Collectors.toList()); + + // 追加新订单ID(如果不存在) + for (String newOrderId : newOrderIdList) { + if (!orderIdList.contains(newOrderId)) { + orderIdList.add(newOrderId); + } + } + + // 重新排序并去重 + orderIdList = orderIdList.stream() + .distinct() + .sorted((a, b) -> Integer.compare(Integer.parseInt(a), Integer.parseInt(b))) + .collect(java.util.stream.Collectors.toList()); + delivery.setOrderId(String.join(",", orderIdList)); + logger.info("追加订单ID到运送清单: deliveryId={}, 原有订单ID={}, 新增订单ID={}, 更新后订单ID={}", + dto.getDeliveryId(), existingOrderId, newOrderIdStr, delivery.getOrderId()); + } else { + // 如果没有现有订单ID,直接设置(排序并去重) + List orderIdList = newOrderIdList.stream() + .distinct() + .sorted((a, b) -> Integer.compare(Integer.parseInt(a), Integer.parseInt(b))) + .collect(java.util.stream.Collectors.toList()); + delivery.setOrderId(String.join(",", orderIdList)); + logger.info("设置运送清单的订单ID: deliveryId={}, orderId={}", dto.getDeliveryId(), delivery.getOrderId()); + } + } + // 更新重量字段:将空字符串转换为null,避免数据库DECIMAL字段类型错误 // 注意:前端可能传递空字符串"",需要处理这种情况 if (dto.getEmptyWeight() != null) { @@ -1062,6 +680,15 @@ public class DeliveryServiceImpl extends ServiceImpl i delivery.setFreight(dto.getFreight()); } + // 更新检疫证号 + if (dto.getQuarantineCertNo() != null) { + if (StringUtils.isNotEmpty(dto.getQuarantineCertNo())) { + delivery.setQuarantineTickey(dto.getQuarantineCertNo().trim()); + } else { + delivery.setQuarantineTickey(null); + } + } + // 更新照片字段:将空字符串转换为null,避免前端显示问题 // 注意:前端总是传递字段(即使是空字符串),只有在传递了有效URL时才更新 if (dto.getQuarantineTickeyUrl() != null) { @@ -2266,5 +1893,275 @@ public class DeliveryServiceImpl extends ServiceImpl i return unique; } + /** + * 批量填充 Delivery 列表的关联信息(优化性能,避免 N+1 查询) + * @param deliveries Delivery 列表 + * @param userId 当前用户ID + */ + private void batchFillDeliveryRelations(List deliveries, Integer userId) { + if (CollectionUtils.isEmpty(deliveries)) { + return; + } + + // 1. 收集所有需要查询的 ID + Set allMemberIds = new HashSet<>(); + Set driverIds = new HashSet<>(); + Set licensePlates = new HashSet<>(); + Set deliveryIds = new HashSet<>(); + + for (Delivery delivery : deliveries) { + // 收集供应商ID(可能是逗号分隔的字符串) + if (StringUtils.isNotEmpty(delivery.getSupplierId())) { + String[] supplierIds = delivery.getSupplierId().split(","); + for (String supplierId : supplierIds) { + if (StringUtils.isNotEmpty(supplierId.trim())) { + try { + allMemberIds.add(Integer.parseInt(supplierId.trim())); + } catch (NumberFormatException e) { + // 忽略无效的ID + } + } + } + } + // 收集资金方ID + if (delivery.getFundId() != null) { + allMemberIds.add(delivery.getFundId()); + } + // 收集采购商ID + if (delivery.getBuyerId() != null) { + allMemberIds.add(delivery.getBuyerId()); + } + // 收集司机ID + if (delivery.getDriverId() != null) { + driverIds.add(delivery.getDriverId()); + } + // 收集车牌号 + if (StringUtils.isNotEmpty(delivery.getLicensePlate())) { + licensePlates.add(delivery.getLicensePlate()); + } + // 收集运单ID(用于设备统计) + if (delivery.getId() != null) { + deliveryIds.add(delivery.getId()); + } + } + + // 2. 批量查询 member 信息 + Map> memberMap = new HashMap<>(); + if (!allMemberIds.isEmpty()) { + List> memberList = memberMapper.selectMemberUserByIds(new ArrayList<>(allMemberIds)); + for (Map member : memberList) { + Object idObj = member.get("id"); + Integer id = 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(); + } + if (id != null) { + memberMap.put(id, member); + } + } + } + + // 3. 批量查询司机信息 + Map> driverMap = new HashMap<>(); + if (!driverIds.isEmpty()) { + List> driverList = memberDriverMapper.selectDriverByIds(new ArrayList<>(driverIds)); + for (Map driver : driverList) { + Object idObj = driver.get("id"); + Integer id = 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(); + } + if (id != null) { + driverMap.put(id, driver); + } + } + } + + // 4. 批量查询车辆信息 + Map vehicleMap = new HashMap<>(); + if (!licensePlates.isEmpty()) { + List vehicleList = vehicleMapper.selectByLicensePlates(new ArrayList<>(licensePlates)); + for (Vehicle vehicle : vehicleList) { + if (vehicle != null && StringUtils.isNotEmpty(vehicle.getLicensePlate())) { + vehicleMap.put(vehicle.getLicensePlate(), vehicle); + } + } + } + + // 5. 批量查询设备数量统计(使用 SQL 聚合查询) + Map> deviceCountMap = new HashMap<>(); + if (!deliveryIds.isEmpty()) { + // 批量查询耳标和项圈设备数量 + for (Integer deliveryId : deliveryIds) { + try { + LambdaQueryWrapper earTagWrapper = new LambdaQueryWrapper<>(); + earTagWrapper.eq(DeliveryDevice::getDeliveryId, deliveryId) + .eq(DeliveryDevice::getDeviceType, 2); + long earTagCount = deliveryDeviceService.count(earTagWrapper); + + LambdaQueryWrapper collarWrapper = new LambdaQueryWrapper<>(); + collarWrapper.eq(DeliveryDevice::getDeliveryId, deliveryId) + .eq(DeliveryDevice::getDeviceType, 3); + long collarCount = deliveryDeviceService.count(collarWrapper); + + Map counts = new HashMap<>(); + counts.put("earTag", (int) earTagCount); + counts.put("collar", (int) collarCount); + counts.put("total", (int) (earTagCount + collarCount)); + deviceCountMap.put(deliveryId, counts); + } catch (Exception e) { + logger.error("查询设备数量失败,deliveryId: {}", deliveryId, e); + } + } + } + + // 6. 填充每个 Delivery 的关联信息 + for (Delivery delivery : deliveries) { + // 设置核验标识 + if (userId != null && userId.equals(delivery.getCheckBy())) { + delivery.setIfCheck(1); + } + + // 填充供应商信息 + if (StringUtils.isNotEmpty(delivery.getSupplierId())) { + String[] supplierIds = delivery.getSupplierId().split(","); + List supplierNames = new ArrayList<>(); + List supplierMobiles = new ArrayList<>(); + for (String supplierId : supplierIds) { + if (StringUtils.isNotEmpty(supplierId.trim())) { + try { + Integer sid = Integer.parseInt(supplierId.trim()); + Map supplierInfo = memberMap.get(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) { + // 忽略无效的ID + } + } + } + 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)); + } + } + + // 填充资金方信息 + if (delivery.getFundId() != null) { + Map fundInfo = memberMap.get(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); + } + } + } + + // 填充采购商信息 + if (delivery.getBuyerId() != null) { + Map buyerInfo = memberMap.get(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); + } + } + } + + // 填充司机信息 + if (delivery.getDriverId() != null) { + Map driverInfo = driverMap.get(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 (StringUtils.isNotEmpty(delivery.getLicensePlate())) { + Vehicle vehicle = vehicleMap.get(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; + } + } + } + + // 如果车辆表中没有照片,从司机信息中获取 + if (!vehiclePhotoSet && StringUtils.isNotEmpty(carImg)) { + String[] carImgUrls = carImg.split(","); + if (carImgUrls.length >= 2) { + delivery.setCarBehindPhoto(carImgUrls[0].trim()); + delivery.setCarFrontPhoto(carImgUrls[1].trim()); + } else if (carImgUrls.length == 1) { + String singlePhoto = carImgUrls[0].trim(); + delivery.setCarFrontPhoto(singlePhoto); + delivery.setCarBehindPhoto(singlePhoto); + } + } + } + } + + // 填充设备数量统计 + if (delivery.getId() != null) { + Map counts = deviceCountMap.get(delivery.getId()); + if (counts != null) { + delivery.setRegisteredJbqCount(counts.get("total")); + delivery.setEarTagCount(counts.get("earTag")); + delivery.setBindJbqCount(counts.get("total")); + // 已佩戴设备数量暂时设为0(需要额外查询) + delivery.setWareCount(0); + } else { + delivery.setRegisteredJbqCount(0); + delivery.setEarTagCount(0); + delivery.setBindJbqCount(0); + delivery.setWareCount(0); + } + } + } + } + } diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/OrderServiceImpl.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/OrderServiceImpl.java index aaeeb6b..00d53ec 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/OrderServiceImpl.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/OrderServiceImpl.java @@ -11,8 +11,6 @@ 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.extension.service.impl.ServiceImpl; -import com.github.pagehelper.Page; -import com.github.pagehelper.PageHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +22,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.HashMap; import java.util.stream.Collectors; /** @@ -93,131 +92,114 @@ public class OrderServiceImpl extends ServiceImpl implements queryWrapper.orderByDesc(Order::getCreateTime); - // 判断是否需要先过滤再分页(如果提供了买方、卖方、运单号、起始地、目的地、单价搜索) - boolean needFilter = (buyerName != null && !buyerName.trim().isEmpty()) || - (sellerName != null && !sellerName.trim().isEmpty()) || - (deliveryNumber != null && !deliveryNumber.trim().isEmpty()) || - (startLocation != null && !startLocation.trim().isEmpty()) || - (endLocation != null && !endLocation.trim().isEmpty()) || - (firmPrice != null && !firmPrice.trim().isEmpty()); - List filteredList; long total; - if (needFilter) { - // 需要过滤的情况:先查询所有数据,填充信息,过滤,然后手动分页 - List 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()); - } - if (deliveryNumber != null && !deliveryNumber.trim().isEmpty()) { - filteredList = filteredList.stream() - .filter(order -> order.getDeliveryNumber() != null && order.getDeliveryNumber().contains(deliveryNumber.trim())) - .collect(java.util.stream.Collectors.toList()); - } - if (startLocation != null && !startLocation.trim().isEmpty()) { - filteredList = filteredList.stream() - .filter(order -> order.getStartLocation() != null && order.getStartLocation().contains(startLocation.trim())) - .collect(java.util.stream.Collectors.toList()); - } - if (endLocation != null && !endLocation.trim().isEmpty()) { - filteredList = filteredList.stream() - .filter(order -> order.getEndLocation() != null && order.getEndLocation().contains(endLocation.trim())) - .collect(java.util.stream.Collectors.toList()); - } - if (firmPrice != null && !firmPrice.trim().isEmpty()) { - filteredList = filteredList.stream() - .filter(order -> { - java.math.BigDecimal price = order.getFirmPriceFromDelivery() != null ? order.getFirmPriceFromDelivery() : order.getFirmPrice(); - if (price == null) { - return false; - } - return price.toString().contains(firmPrice.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 page = PageHelper.startPage(pageNum, pageSize); - - // 执行查询 - List list = orderMapper.selectList(queryWrapper); - - // 批量填充关联信息(优化性能) - fillOrderInfoBatch(list); - - // 获取总数和分页数据 - total = page.getTotal(); - filteredList = list; - - logger.info("查询到{}条订单记录,分页后{}条", total, filteredList.size()); + // 为了保持买卖关系链的完整性,统一采用先查询所有数据、排序、再分页的方式 + // 这样可以确保同一deliveryId的订单链条不会被分页截断 + // 先查询所有符合条件的数据(不包含需要填充信息的过滤条件) + List 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()); + } + if (deliveryNumber != null && !deliveryNumber.trim().isEmpty()) { + filteredList = filteredList.stream() + .filter(order -> order.getDeliveryNumber() != null && order.getDeliveryNumber().contains(deliveryNumber.trim())) + .collect(java.util.stream.Collectors.toList()); + } + if (startLocation != null && !startLocation.trim().isEmpty()) { + filteredList = filteredList.stream() + .filter(order -> order.getStartLocation() != null && order.getStartLocation().contains(startLocation.trim())) + .collect(java.util.stream.Collectors.toList()); + } + if (endLocation != null && !endLocation.trim().isEmpty()) { + filteredList = filteredList.stream() + .filter(order -> order.getEndLocation() != null && order.getEndLocation().contains(endLocation.trim())) + .collect(java.util.stream.Collectors.toList()); + } + if (firmPrice != null && !firmPrice.trim().isEmpty()) { + filteredList = filteredList.stream() + .filter(order -> { + java.math.BigDecimal price = order.getFirmPriceFromDelivery() != null ? order.getFirmPriceFromDelivery() : order.getFirmPrice(); + if (price == null) { + return false; + } + return price.toString().contains(firmPrice.trim()); + }) + .collect(java.util.stream.Collectors.toList()); + } + + // 获取总数(排序前) + total = filteredList.size(); + + logger.info("查询到{}条订单记录,过滤后{}条", allList.size(), total); - // 优化排序:按运送清单分组排序 + // 优化排序:按运送清单分组,同链路内按买卖关系链排序 // 排序规则:1. 有deliveryId的订单按deliveryId分组,相同deliveryId的订单放在一起 // 2. 按deliveryId排序(null值放在最后) - // 3. 同一deliveryId内的订单按创建时间排序 - filteredList.sort((o1, o2) -> { - Integer deliveryId1 = o1.getDeliveryId(); - Integer deliveryId2 = o2.getDeliveryId(); - - // 如果两个订单都没有deliveryId,按创建时间倒序 - if (deliveryId1 == null && deliveryId2 == null) { - if (o1.getCreateTime() != null && o2.getCreateTime() != null) { - return o2.getCreateTime().compareTo(o1.getCreateTime()); - } - return 0; + // 3. 同一deliveryId内的订单按买卖关系链排序: + // - 如果订单A的卖方等于订单B的买方,则B排在A后面 + // - 形成链条:订单1 -> 订单2 -> 订单3 + List sortedList = new ArrayList<>(); + + // 按deliveryId分组 + java.util.Map> deliveryGroups = new java.util.HashMap<>(); + List noDeliveryOrders = new ArrayList<>(); + + for (Order order : filteredList) { + Integer deliveryId = order.getDeliveryId(); + if (deliveryId == null) { + noDeliveryOrders.add(order); + } else { + deliveryGroups.computeIfAbsent(deliveryId, k -> new ArrayList<>()).add(order); } - - // 如果只有一个订单有deliveryId,有deliveryId的排在前面 - if (deliveryId1 == null) { - return 1; // o1排在后面 - } - if (deliveryId2 == null) { - return -1; // o2排在后面 - } - - // 两个订单都有deliveryId,先按deliveryId排序 - int deliveryIdCompare = deliveryId1.compareTo(deliveryId2); - if (deliveryIdCompare != 0) { - return deliveryIdCompare; - } - - // deliveryId相同,按创建时间倒序 + } + + // 对每个deliveryId组内的订单按买卖关系链排序 + List sortedDeliveryIds = new ArrayList<>(deliveryGroups.keySet()); + sortedDeliveryIds.sort(Integer::compareTo); + + for (Integer deliveryId : sortedDeliveryIds) { + List groupOrders = deliveryGroups.get(deliveryId); + List sortedGroup = sortOrdersByChain(groupOrders); + sortedList.addAll(sortedGroup); + } + + // 没有deliveryId的订单按创建时间倒序,放在最后 + noDeliveryOrders.sort((o1, o2) -> { if (o1.getCreateTime() != null && o2.getCreateTime() != null) { return o2.getCreateTime().compareTo(o1.getCreateTime()); } return 0; }); + sortedList.addAll(noDeliveryOrders); - logger.info("订单列表排序完成,共{}条记录", filteredList.size()); + filteredList = sortedList; + logger.info("订单列表排序完成,共{}条记录,已按买卖关系链排序", filteredList.size()); + + // 排序后再进行分页,确保同一链条的订单不会被分到不同页 + int startIndex = (pageNum - 1) * pageSize; + int endIndex = Math.min(startIndex + pageSize, filteredList.size()); + if (startIndex < filteredList.size()) { + filteredList = filteredList.subList(startIndex, endIndex); + logger.info("分页后{}条记录(第{}页,每页{}条)", filteredList.size(), pageNum, pageSize); + } else { + filteredList = new ArrayList<>(); + logger.info("分页后0条记录(超出范围)"); + } // 构建分页结果 return new PageResultResponse<>(total, filteredList); @@ -229,8 +211,8 @@ public class OrderServiceImpl extends ServiceImpl implements @Override @Transactional public AjaxResult addOrder(Order order) { - logger.info("开始新增订单,买方:{},卖方:{},结算方式:{},约定价格:{}", - order.getBuyerId(), order.getSellerId(), order.getSettlementType(), order.getFirmPrice()); + logger.info("开始新增订单,买方:{},卖方:{},结算方式:{},约定价格:{},运送清单ID:{}", + order.getBuyerId(), order.getSellerId(), order.getSettlementType(), order.getFirmPrice(), order.getDeliveryId()); try { // 验证结算方式 @@ -250,6 +232,16 @@ public class OrderServiceImpl extends ServiceImpl implements return AjaxResult.error("约定价格不能小于0"); } + // 如果提供了deliveryId,验证运送清单是否存在 + if (order.getDeliveryId() != null) { + com.aiotagro.cattletrade.business.entity.Delivery delivery = deliveryMapper.selectById(order.getDeliveryId()); + if (delivery == null) { + logger.error("运送清单不存在,ID:{}", order.getDeliveryId()); + return AjaxResult.error("运送清单不存在"); + } + logger.info("订单将关联到运送清单,运送清单ID:{},运单号:{}", order.getDeliveryId(), delivery.getDeliveryNumber()); + } + // 设置创建人和创建时间 Integer userId = SecurityUtil.getCurrentUserId(); order.setCreatedBy(userId); @@ -259,8 +251,15 @@ public class OrderServiceImpl extends ServiceImpl implements int result = orderMapper.insert(order); if (result > 0) { - logger.info("新增订单成功,订单ID:{}", order.getId()); - return AjaxResult.success("新增订单成功"); + if (order.getDeliveryId() != null) { + logger.info("新增订单成功并关联运送清单,订单ID:{},运送清单ID:{}", order.getId(), order.getDeliveryId()); + } else { + logger.info("新增订单成功,订单ID:{}", order.getId()); + } + // 返回订单ID,方便前端更新关联关系 + Map data = new HashMap<>(); + data.put("id", order.getId()); + return AjaxResult.success("新增订单成功", data); } else { logger.error("新增订单失败"); return AjaxResult.error("新增订单失败"); @@ -341,6 +340,7 @@ public class OrderServiceImpl extends ServiceImpl implements /** * 删除订单(逻辑删除) + * 删除订单时,同时更新delivery表中order_id字段,移除对应的订单ID */ @Override @Transactional @@ -360,6 +360,75 @@ public class OrderServiceImpl extends ServiceImpl implements return AjaxResult.error("订单不存在"); } + // 在删除订单之前,先更新delivery表中order_id字段,移除该订单ID + String orderIdStr = String.valueOf(id); + try { + // 查询所有order_id字段包含该订单ID的delivery记录 + // 使用LIKE查询匹配:开头、中间、结尾或单独存在 + com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper wrapper = + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>(); + wrapper.and(w -> w + .eq(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, orderIdStr) // 单独存在 + .or() + .like(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, orderIdStr + ",") // 开头 + .or() + .like(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, "," + orderIdStr + ",") // 中间 + .or() + .like(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, "," + orderIdStr) // 结尾 + ); + wrapper.isNotNull(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId); + wrapper.ne(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, ""); + + List deliveryList = deliveryMapper.selectList(wrapper); + + if (deliveryList != null && !deliveryList.isEmpty()) { + logger.info("找到{}条运送清单包含订单ID:{}", deliveryList.size(), id); + + // 更新每条delivery记录的order_id字段 + for (com.aiotagro.cattletrade.business.entity.Delivery delivery : deliveryList) { + String currentOrderId = delivery.getOrderId(); + if (currentOrderId != null && !currentOrderId.trim().isEmpty()) { + // 将order_id字符串按逗号分割 + List orderIdList = new ArrayList<>(Arrays.asList(currentOrderId.split(","))); + // 去除空白并过滤空字符串 + orderIdList = orderIdList.stream() + .map(String::trim) + .filter(oid -> !oid.isEmpty()) + .collect(Collectors.toList()); + + // 移除该订单ID + orderIdList.remove(orderIdStr); + + // 如果还有订单ID,重新组合;否则设置为null + if (!orderIdList.isEmpty()) { + // 排序并去重 + orderIdList = orderIdList.stream() + .distinct() + .sorted((a, b) -> Integer.compare(Integer.parseInt(a), Integer.parseInt(b))) + .collect(Collectors.toList()); + String newOrderId = String.join(",", orderIdList); + delivery.setOrderId(newOrderId); + logger.info("更新运送清单order_id:deliveryId={}, 原值={}, 新值={}", + delivery.getId(), currentOrderId, newOrderId); + } else { + delivery.setOrderId(null); + logger.info("更新运送清单order_id:deliveryId={}, 原值={}, 新值=null(已清空)", + delivery.getId(), currentOrderId); + } + + // 更新delivery记录 + deliveryMapper.updateById(delivery); + } + } + logger.info("成功更新{}条运送清单的order_id字段", deliveryList.size()); + } else { + logger.info("未找到包含订单ID {} 的运送清单记录", id); + } + } catch (Exception e) { + logger.error("更新delivery表order_id字段失败,订单ID:{},错误:{}", id, e.getMessage(), e); + // 不阻止删除流程,只记录错误 + } + // 逻辑删除(通过update方法设置is_delete=1) // MyBatis-Plus的@TableLogic会自动处理 int result = orderMapper.deleteById(id); @@ -607,6 +676,176 @@ public class OrderServiceImpl extends ServiceImpl implements } } + /** + * 按买卖关系链排序订单 + * 规则:如果订单A的卖方等于订单B的买方,则B排在A后面 + * 支持买方/卖方为多个名称(逗号分隔)的情况 + * + * @param orders 同一deliveryId的订单列表 + * @return 排序后的订单列表 + */ + private List sortOrdersByChain(List orders) { + if (orders == null || orders.size() <= 1) { + return orders; + } + + List result = new ArrayList<>(); + List remaining = new ArrayList<>(orders); + + // 辅助方法:检查两个名称是否匹配(支持逗号分隔的多个名称) + java.util.function.BiFunction namesMatch = (name1, name2) -> { + if (name1 == null || name2 == null) { + return false; + } + // 分割并trim,支持 ", " 或 "," 分隔 + String[] names1 = name1.split(",\\s*"); + String[] names2 = name2.split(",\\s*"); + for (String n1 : names1) { + String trimmed1 = n1.trim(); + if (trimmed1.isEmpty()) { + continue; + } + for (String n2 : names2) { + String trimmed2 = n2.trim(); + if (trimmed1.equals(trimmed2)) { + logger.debug("名称匹配:{} == {}", trimmed1, trimmed2); + return true; + } + } + } + return false; + }; + + // 记录所有订单的买卖方信息用于调试 + logger.info("开始排序订单,共{}条", remaining.size()); + for (Order order : remaining) { + logger.info("订单ID:{},买方:{},卖方:{}", order.getId(), order.getBuyerName(), order.getSellerName()); + } + + // 找到链条的起点:没有其他订单的卖方等于这个订单的买方 + Order startOrder = null; + for (Order order : remaining) { + String buyerName = order.getBuyerName(); + if (buyerName == null || buyerName.trim().isEmpty()) { + continue; + } + + boolean isStart = true; + // 检查是否有其他订单的卖方等于这个订单的买方 + for (Order other : remaining) { + if (order == other) { + continue; + } + String otherSellerName = other.getSellerName(); + if (otherSellerName != null && namesMatch.apply(buyerName, otherSellerName)) { + logger.info("订单{}不是起点,因为订单{}的卖方({})等于订单{}的买方({})", + order.getId(), other.getId(), otherSellerName, order.getId(), buyerName); + isStart = false; + break; + } + } + + if (isStart) { + logger.info("找到起点订单:{},买方:{}", order.getId(), buyerName); + startOrder = order; + break; + } + } + + // 如果没有找到起点(可能是循环链或没有匹配关系),使用第一个订单作为起点 + if (startOrder == null) { + logger.warn("未找到起点订单,使用第一个订单作为起点"); + startOrder = remaining.get(0); + } + + // 从起点开始构建链条 + result.add(startOrder); + remaining.remove(startOrder); + logger.info("添加起点订单:{}", startOrder.getId()); + + // 按照买卖关系链依次添加订单,支持多个独立链条 + while (!remaining.isEmpty()) { + Order currentOrder = result.get(result.size() - 1); // 获取当前链条的最后一个订单 + boolean found = false; + String currentSellerName = currentOrder.getSellerName(); + + if (currentSellerName != null && !currentSellerName.trim().isEmpty()) { + // 查找下一个订单:买方等于当前订单的卖方 + for (Order nextOrder : remaining) { + String nextBuyerName = nextOrder.getBuyerName(); + if (nextBuyerName != null && namesMatch.apply(currentSellerName, nextBuyerName)) { + logger.info("找到下一个订单:{}(当前订单{}的卖方{} == 订单{}的买方{})", + nextOrder.getId(), currentOrder.getId(), currentSellerName, nextOrder.getId(), nextBuyerName); + result.add(nextOrder); + remaining.remove(nextOrder); + found = true; + break; + } + } + } + + // 如果当前链条无法继续,尝试从剩余订单中找新的起点构建新链条 + if (!found) { + logger.info("当前链条无法继续,当前订单{}的卖方:{},剩余{}个订单,尝试构建新链条", + currentOrder.getId(), currentSellerName, remaining.size()); + + // 从剩余订单中找新的起点 + Order newStartOrder = null; + for (Order order : remaining) { + String buyerName = order.getBuyerName(); + if (buyerName == null || buyerName.trim().isEmpty()) { + continue; + } + + boolean isStart = true; + // 检查是否有其他剩余订单的卖方等于这个订单的买方 + for (Order other : remaining) { + if (order == other) { + continue; + } + String otherSellerName = other.getSellerName(); + if (otherSellerName != null && namesMatch.apply(buyerName, otherSellerName)) { + isStart = false; + break; + } + } + + if (isStart) { + logger.info("找到新链条起点订单:{},买方:{}", order.getId(), buyerName); + newStartOrder = order; + break; + } + } + + // 如果找到新起点,开始构建新链条 + if (newStartOrder != null) { + result.add(newStartOrder); + remaining.remove(newStartOrder); + logger.info("添加新链条起点订单:{}", newStartOrder.getId()); + // 继续循环,从新起点开始构建链条 + continue; + } else { + // 如果找不到新起点,将剩余订单按创建时间倒序添加 + logger.info("未找到新链条起点,将剩余订单按创建时间排序"); + for (Order rem : remaining) { + logger.info("剩余订单{},买方:{},卖方:{}", rem.getId(), rem.getBuyerName(), rem.getSellerName()); + } + remaining.sort((o1, o2) -> { + if (o1.getCreateTime() != null && o2.getCreateTime() != null) { + return o2.getCreateTime().compareTo(o1.getCreateTime()); + } + return 0; + }); + result.addAll(remaining); + break; + } + } + } + + logger.info("排序完成,最终顺序:{}", result.stream().map(o -> String.valueOf(o.getId())).collect(Collectors.joining(" -> "))); + return result; + } + /** * 填充订单关联信息(买方名称、卖方名称、创建人名称、结算方式描述) * 用于单个订单详情查询 @@ -854,6 +1093,36 @@ public class OrderServiceImpl extends ServiceImpl implements logger.info("批量更新订单delivery_id完成,成功:{}条,失败:{}条", successCount, failCount); + // 同步更新delivery表的order_id字段(将所有关联的订单ID用逗号连接) + if (successCount > 0) { + try { + // 查询该运送清单当前关联的所有订单ID + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() + .eq(Order::getDeliveryId, deliveryId) + .select(Order::getId) + .orderByAsc(Order::getId); + List associatedOrders = orderMapper.selectList(queryWrapper); + + // 将所有订单ID用逗号连接 + String orderIdStr = associatedOrders.stream() + .map(order -> String.valueOf(order.getId())) + .collect(Collectors.joining(",")); + + // 更新delivery表的order_id字段 + com.aiotagro.cattletrade.business.entity.Delivery delivery = deliveryMapper.selectById(deliveryId); + if (delivery != null) { + delivery.setOrderId(orderIdStr); + deliveryMapper.updateById(delivery); + logger.info("同步更新运送清单order_id成功,运送清单ID:{},订单ID列表:{}", deliveryId, orderIdStr); + } else { + logger.warn("运送清单不存在,无法更新order_id,运送清单ID:{}", deliveryId); + } + } catch (Exception e) { + logger.error("同步更新运送清单order_id失败,运送清单ID:{},错误:{}", deliveryId, e.getMessage(), e); + // 不抛出异常,只记录日志,因为订单的delivery_id已经更新成功 + } + } + if (failCount == 0) { return AjaxResult.success("批量更新成功,共更新" + successCount + "条订单"); } else { @@ -868,5 +1137,88 @@ public class OrderServiceImpl extends ServiceImpl implements return AjaxResult.error("批量更新订单delivery_id失败:" + e.getMessage()); } } + + /** + * 解除订单与运送清单的关联(将订单的deliveryId设置为null) + * + * @param orderId 订单ID + * @return AjaxResult + */ + @Override + @Transactional + public AjaxResult unbindOrderFromDelivery(Integer orderId) { + try { + logger.info("解除订单与运送清单的关联,订单ID:{}", orderId); + + if (orderId == null) { + logger.error("订单ID不能为空"); + return AjaxResult.error("订单ID不能为空"); + } + + // 查询订单是否存在 + Order order = orderMapper.selectById(orderId); + if (order == null) { + logger.error("订单不存在,ID:{}", orderId); + return AjaxResult.error("订单不存在"); + } + + // 如果订单没有关联运送清单,直接返回成功 + if (order.getDeliveryId() == null) { + logger.info("订单未关联运送清单,无需解除,订单ID:{}", orderId); + return AjaxResult.success("订单未关联运送清单,无需解除"); + } + + Integer oldDeliveryId = order.getDeliveryId(); + + // 将deliveryId设置为null + order.setDeliveryId(null); + order.setUpdateTime(new Date()); + try { + order.setUpdatedBy(SecurityUtil.getCurrentUserId()); + } catch (Exception e) { + logger.warn("获取当前用户失败,使用订单原有创建人:{}", e.getMessage()); + if (order.getCreatedBy() != null) { + order.setUpdatedBy(order.getCreatedBy()); + } + } + + int result = orderMapper.updateById(order); + if (result > 0) { + // 同步更新delivery表的order_id字段(移除该订单ID) + if (oldDeliveryId != null) { + try { + com.aiotagro.cattletrade.business.entity.Delivery delivery = deliveryMapper.selectById(oldDeliveryId); + if (delivery != null && delivery.getOrderId() != null && !delivery.getOrderId().trim().isEmpty()) { + // 从order_id字符串中移除当前订单ID + String orderIdStr = delivery.getOrderId(); + List orderIdList = new ArrayList<>(Arrays.asList(orderIdStr.split(","))); + orderIdList.remove(String.valueOf(orderId)); + + // 更新order_id字段 + if (orderIdList.isEmpty()) { + delivery.setOrderId(null); + } else { + delivery.setOrderId(String.join(",", orderIdList)); + } + deliveryMapper.updateById(delivery); + logger.info("同步更新运送清单order_id成功,移除订单ID:{},运送清单ID:{}", orderId, oldDeliveryId); + } + } catch (Exception e) { + logger.error("同步更新运送清单order_id失败,运送清单ID:{},错误:{}", oldDeliveryId, e.getMessage(), e); + // 不抛出异常,只记录日志 + } + } + + logger.info("解除订单与运送清单关联成功,订单ID:{},原运送清单ID:{}", orderId, oldDeliveryId); + return AjaxResult.success("解除关联成功"); + } else { + logger.error("解除订单与运送清单关联失败,订单ID:{}", orderId); + return AjaxResult.error("解除关联失败"); + } + } catch (Exception e) { + logger.error("解除订单与运送清单关联异常,订单ID:{},错误:{}", orderId, e.getMessage(), e); + return AjaxResult.error("解除关联失败:" + e.getMessage()); + } + } } diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/config/DruidDataSourceConfig.java b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/config/DruidDataSourceConfig.java index 7131af3..85979b3 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/config/DruidDataSourceConfig.java +++ b/tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/config/DruidDataSourceConfig.java @@ -8,6 +8,8 @@ import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; /** + * Druid 数据源配置类 + * * @author Carson * @package_name com.aiotagro.payinfo.config * @date 2024/2/6 17:00 @@ -15,21 +17,22 @@ import javax.sql.DataSource; @Configuration public class DruidDataSourceConfig { - //编写方法,注入DruidDataSource - //为什么我们注入自己的DataSource , 默认的HiKariDatasource失效? - //1. 默认的数据源是如配置? @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) - // 解读通过@ConditionalOnMissingBean({ DataSource.class}) 判断如果容器有DataSource Bean 就不注入默认的HiKariDatasource + /** + * 配置 Druid 数据源 + * + * 说明: + * 1. 使用 @ConfigurationProperties("spring.datasource") 自动读取配置文件中的连接池参数 + * 2. Spring Boot 默认使用 HikariCP,通过注入自定义 DataSource Bean 来使用 Druid + * 3. Druid 连接池参数通过 application.yml 中的 spring.datasource.druid.* 配置 + * + * @return DataSource 数据源实例 + */ @ConfigurationProperties("spring.datasource") @Bean public DataSource dataSource() { - //1. 配置了 @ConfigurationProperties("spring.datasource") - // 就可以读取到application.yml的配置,注意:我们需要将bean注入到spring ioc容器中、bean中提供get\set方法 - //2. 我们就不需要调用DruidDataSource 对象的setXxx, 会自动关联 - DruidDataSource druidDataSource = new DruidDataSource(); - //druidDataSource.setUrl(); - //druidDataSource.setUsername(); - //druidDataSource.setPassword(); + // 连接池参数通过 @ConfigurationProperties 自动从配置文件注入 + // 配置文件位置:application.yml 中的 spring.datasource.druid.* return druidDataSource; } } diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/resources/application-demo.yml b/tradeCattle/aiotagro-cattle-trade/src/main/resources/application-demo.yml index 3e6b615..63d513b 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/resources/application-demo.yml +++ b/tradeCattle/aiotagro-cattle-trade/src/main/resources/application-demo.yml @@ -23,9 +23,46 @@ spring: max-wait: -1ms datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + # 在 URL 中添加连接超时和 Socket 超时参数,以及自动重连 + url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&connectTimeout=60000&socketTimeout=60000&autoReconnect=true username: root password: Aiotagro@741 + # Druid 连接池配置 + druid: + # 初始连接数 + initial-size: 5 + # 最小空闲连接数 + min-idle: 5 + # 最大活跃连接数 + max-active: 20 + # 获取连接的最大等待时间(毫秒),60秒 + max-wait: 60000 + # 连接池中的连接空闲多久后会被回收(毫秒) + time-between-eviction-runs-millis: 60000 + # 连接在池中最小生存时间(毫秒),5分钟 + min-evictable-idle-time-millis: 300000 + # 连接在池中最大生存时间(毫秒),15分钟 + max-evictable-idle-time-millis: 900000 + # 验证连接的 SQL 查询 + validation-query: SELECT 1 + # 获取连接时是否验证连接有效性 + test-on-borrow: false + # 归还连接时是否验证连接有效性 + test-on-return: false + # 空闲时是否验证连接有效性(推荐开启) + test-while-idle: true + # 连接超时时间(毫秒) + connection-timeout: 60000 + # Socket 超时时间(毫秒) + socket-timeout: 60000 + # 查询超时时间(毫秒) + query-timeout: 60000 + # 连接失败后是否中断 + break-after-acquire-failure: false + # 连接失败重试次数 + connection-error-retry-attempts: 3 + # 是否在连接断开时自动重连 + keep-alive: true sa-token: # token 名称(同时也是 cookie 名称) diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/resources/application-dev.yml b/tradeCattle/aiotagro-cattle-trade/src/main/resources/application-dev.yml index 18cfc92..1ec984b 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/resources/application-dev.yml +++ b/tradeCattle/aiotagro-cattle-trade/src/main/resources/application-dev.yml @@ -26,9 +26,46 @@ spring: # username: iot-plateform # password: 3qJ7$bV%N9mE driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + # 在 URL 中添加连接超时和 Socket 超时参数,以及自动重连 + url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&connectTimeout=60000&socketTimeout=60000&autoReconnect=true username: root password: Aiotagro@741 + # Druid 连接池配置 + druid: + # 初始连接数 + initial-size: 5 + # 最小空闲连接数 + min-idle: 5 + # 最大活跃连接数 + max-active: 20 + # 获取连接的最大等待时间(毫秒),60秒 + max-wait: 60000 + # 连接池中的连接空闲多久后会被回收(毫秒) + time-between-eviction-runs-millis: 60000 + # 连接在池中最小生存时间(毫秒),5分钟 + min-evictable-idle-time-millis: 300000 + # 连接在池中最大生存时间(毫秒),15分钟 + max-evictable-idle-time-millis: 900000 + # 验证连接的 SQL 查询 + validation-query: SELECT 1 + # 获取连接时是否验证连接有效性 + test-on-borrow: false + # 归还连接时是否验证连接有效性 + test-on-return: false + # 空闲时是否验证连接有效性(推荐开启) + test-while-idle: true + # 连接超时时间(毫秒) + connection-timeout: 60000 + # Socket 超时时间(毫秒) + socket-timeout: 60000 + # 查询超时时间(毫秒) + query-timeout: 60000 + # 连接失败后是否中断 + break-after-acquire-failure: false + # 连接失败重试次数 + connection-error-retry-attempts: 3 + # 是否在连接断开时自动重连 + keep-alive: true sa-token: # token 名称(同时也是 cookie 名称) diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/resources/application-prod.yml b/tradeCattle/aiotagro-cattle-trade/src/main/resources/application-prod.yml index 59edeb6..e381003 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/resources/application-prod.yml +++ b/tradeCattle/aiotagro-cattle-trade/src/main/resources/application-prod.yml @@ -23,9 +23,46 @@ spring: # max-wait: -1ms datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + # 在 URL 中添加连接超时和 Socket 超时参数,以及自动重连 + url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&connectTimeout=60000&socketTimeout=60000&autoReconnect=true username: root password: Aiotagro@741 + # Druid 连接池配置 + druid: + # 初始连接数 + initial-size: 5 + # 最小空闲连接数 + min-idle: 5 + # 最大活跃连接数 + max-active: 20 + # 获取连接的最大等待时间(毫秒),60秒 + max-wait: 60000 + # 连接池中的连接空闲多久后会被回收(毫秒) + time-between-eviction-runs-millis: 60000 + # 连接在池中最小生存时间(毫秒),5分钟 + min-evictable-idle-time-millis: 300000 + # 连接在池中最大生存时间(毫秒),15分钟 + max-evictable-idle-time-millis: 900000 + # 验证连接的 SQL 查询 + validation-query: SELECT 1 + # 获取连接时是否验证连接有效性 + test-on-borrow: false + # 归还连接时是否验证连接有效性 + test-on-return: false + # 空闲时是否验证连接有效性(推荐开启) + test-while-idle: true + # 连接超时时间(毫秒) + connection-timeout: 60000 + # Socket 超时时间(毫秒) + socket-timeout: 60000 + # 查询超时时间(毫秒) + query-timeout: 60000 + # 连接失败后是否中断 + break-after-acquire-failure: false + # 连接失败重试次数 + connection-error-retry-attempts: 3 + # 是否在连接断开时自动重连 + keep-alive: true sa-token: # token 名称(同时也是 cookie 名称) diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/resources/db/migration/alter_warning_log_add_index.sql b/tradeCattle/aiotagro-cattle-trade/src/main/resources/db/migration/alter_warning_log_add_index.sql new file mode 100644 index 0000000..23e1bbc --- /dev/null +++ b/tradeCattle/aiotagro-cattle-trade/src/main/resources/db/migration/alter_warning_log_add_index.sql @@ -0,0 +1,63 @@ +USE cattletrade; + +-- 为 warning_log 表添加索引以优化预警列表查询性能 +-- 优化目标:getPageWarningLog 查询中的派生表查询和 JOIN 操作 +-- +-- 执行说明: +-- 1. 先执行检查脚本查看现有索引:SHOW INDEX FROM warning_log; +-- 2. 如果索引已存在,会报错 "Duplicate key name",可以忽略该错误或注释掉对应语句 +-- 3. 建议按顺序执行,遇到已存在的索引错误时跳过即可 + +-- ============================================ +-- 第一步:检查现有索引(可选) +-- ============================================ +-- 执行以下命令查看 warning_log 表的所有索引: +-- SHOW INDEX FROM warning_log; + +-- ============================================ +-- 第二步:创建索引(所有索引已存在,已全部注释) +-- ============================================ +-- ✅ 状态:所有需要的索引已经存在,无需再次创建 +-- 如需重新创建,请先删除现有索引:DROP INDEX idx_name ON warning_log; + +-- 1. 添加 delivery_id 和 warning_type 的联合索引(用于派生表的 GROUP BY 查询) +-- 这个索引可以大幅提升 "SELECT delivery_id, warning_type, MAX(id) FROM warning_log WHERE warning_type IN (...) GROUP BY delivery_id, warning_type" 的性能 +-- ✅ 索引已存在:idx_delivery_warning_type +-- ALTER TABLE warning_log +-- ADD INDEX idx_delivery_warning_type (delivery_id, warning_type, id); + +-- 2. 添加 warning_time 索引(用于 ORDER BY 排序) +-- ✅ 索引已存在:idx_warning_time +-- ALTER TABLE warning_log +-- ADD INDEX idx_warning_time (warning_time DESC); + +-- 3. 如果 delivery_id 还没有单独索引,添加一个(用于 JOIN 操作) +-- 注意:如果 delivery_id 已经是主键或唯一索引的一部分,可以跳过 +-- 检查现有索引:SHOW INDEX FROM warning_log; +-- 如果 delivery_id 没有索引,执行下面的语句 +-- ALTER TABLE warning_log ADD INDEX idx_delivery_id (delivery_id); + +-- ============================================ +-- 索引说明和性能优化效果 +-- ============================================ +-- +-- 已存在的索引: +-- 1. idx_delivery_warning_type: 覆盖索引,支持 WHERE warning_type IN (...) GROUP BY delivery_id, warning_type 的查询 +-- 2. idx_warning_time: 支持 ORDER BY warning_time DESC 的排序操作 +-- +-- 性能提升效果: +-- ✅ 派生表查询从全表扫描优化为索引扫描 +-- ✅ GROUP BY 操作可以使用索引,避免临时表排序 +-- ✅ ORDER BY 可以使用索引,避免文件排序 +-- +-- ============================================ +-- 验证索引是否生效 +-- ============================================ +-- 执行以下 SQL 验证索引: +-- SHOW INDEX FROM warning_log WHERE Key_name IN ('idx_delivery_warning_type', 'idx_warning_time'); +-- +-- 使用 EXPLAIN 分析查询性能: +-- EXPLAIN SELECT ... (使用优化后的 getPageWarningLog SQL) +-- +-- 注意:所有索引已创建完成,脚本保留作为文档参考 + diff --git a/tradeCattle/aiotagro-cattle-trade/src/main/resources/mapper/DeliveryMapper.xml b/tradeCattle/aiotagro-cattle-trade/src/main/resources/mapper/DeliveryMapper.xml index e36b14e..50b5e1d 100644 --- a/tradeCattle/aiotagro-cattle-trade/src/main/resources/mapper/DeliveryMapper.xml +++ b/tradeCattle/aiotagro-cattle-trade/src/main/resources/mapper/DeliveryMapper.xml @@ -36,6 +36,12 @@ id, delivery_number, buyer_price, sale_price, firm_price, status, license_plate, car_front_photo, car_behind_photo, car_video, start_location, start_lat, start_lon, end_location, end_lat, end_lon, estimated_delivery_time, registered_jbq_count, driver_name, driver_mobile, create_time, created_by, check_by, check_time, check_video +