Просмотр исходного кода

feat: 消息列表回填订单缩略图 orderImage(#671 补充)

外卖消息取 pos_order.food 快照第一行的 image 键(App 下单传入,键名已按线上
真实数据确认);闪送消息批查寄件凭证(SENDER)取 sortOrder 最小一张。解析
非法/无凭证时留空,前端降级不显示。无表结构变更。
qmj 22 часов назад
Родитель
Сommit
4df9dd34b7

+ 2 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/PushMessage.java

@@ -65,6 +65,8 @@ public class PushMessage
     /** 以下为列表展示回填字段,不落库 */
     private transient String shopName;
     private transient String shAddress;
+    /** 订单缩略图:外卖=第一行商品图(food 快照 image 键);闪送=寄件凭证第一张 */
+    private transient String orderImage;
     private transient String pickupAddress;
     private transient String pickupAddressDetail;
     private transient String deliveryAddress;

+ 94 - 3
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PushMessageServiceImpl.java

@@ -1,6 +1,8 @@
 package com.ruoyi.system.service.impl;
 
+import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -9,6 +11,8 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@@ -16,9 +20,11 @@ import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.system.mapper.PushMessageMapper;
 import com.ruoyi.system.mapper.PosOrderMapper;
 import com.ruoyi.system.mapper.flash.FlashDeliveryOrderMapper;
+import com.ruoyi.system.mapper.flash.FlashDeliveryOrderImageMapper;
 import com.ruoyi.system.domain.PosOrder;
 import com.ruoyi.system.domain.PushMessage;
 import com.ruoyi.system.domain.flash.FlashDeliveryOrder;
+import com.ruoyi.system.domain.flash.FlashDeliveryOrderImage;
 import com.ruoyi.system.service.IPushMessageService;
 
 /**
@@ -39,6 +45,9 @@ public class PushMessageServiceImpl extends ServiceImpl<BaseMapper<PushMessage>,
     @Autowired
     private FlashDeliveryOrderMapper flashDeliveryOrderMapper;
 
+    @Autowired
+    private FlashDeliveryOrderImageMapper flashDeliveryOrderImageMapper;
+
     /**
      * 查询推送消息
      *
@@ -134,7 +143,8 @@ public class PushMessageServiceImpl extends ServiceImpl<BaseMapper<PushMessage>,
                 : flashDeliveryOrderMapper.selectList(new LambdaQueryWrapper<FlashDeliveryOrder>().in(FlashDeliveryOrder::getOrderNo, flashOrderNos))
                 .stream().filter(order -> order.getOrderNo() != null)
                 .collect(Collectors.toMap(FlashDeliveryOrder::getOrderNo, Function.identity(), (first, second) -> first));
-        fillOrderInfo(messages, foodOrders, flashOrders);
+        Map<Long, String> flashFirstImages = firstSenderImages(flashOrders.values());
+        fillOrderInfo(messages, foodOrders, flashOrders, flashFirstImages);
     }
 
     /** 收集当页消息的订单指针:push_type=1 归入外卖单号,push_type=3 归入闪送单号。 */
@@ -157,9 +167,9 @@ public class PushMessageServiceImpl extends ServiceImpl<BaseMapper<PushMessage>,
         }
     }
 
-    /** 按指针把商家名/地址写回消息;订单已删或不存在的消息保持原样。 */
+    /** 按指针把商家名/地址/缩略图写回消息;订单已删或不存在的消息保持原样。 */
     private void fillOrderInfo(List<PushMessage> messages, Map<String, PosOrder> foodOrders,
-                               Map<String, FlashDeliveryOrder> flashOrders)
+                               Map<String, FlashDeliveryOrder> flashOrders, Map<Long, String> flashFirstImages)
     {
         for (PushMessage message : messages)
         {
@@ -174,6 +184,7 @@ public class PushMessageServiceImpl extends ServiceImpl<BaseMapper<PushMessage>,
                 {
                     message.setShopName(order.getPosName());
                     message.setShAddress(order.getShAddress());
+                    message.setOrderImage(firstFoodImage(order.getFood()));
                 }
             }
             else if (Integer.valueOf(3).equals(message.getPushType()))
@@ -185,8 +196,88 @@ public class PushMessageServiceImpl extends ServiceImpl<BaseMapper<PushMessage>,
                     message.setPickupAddressDetail(order.getPickupAddressDetail());
                     message.setDeliveryAddress(order.getDeliveryAddress());
                     message.setDeliveryAddressDetail(order.getDeliveryAddressDetail());
+                    if (order.getId() != null)
+                    {
+                        message.setOrderImage(flashFirstImages.get(order.getId()));
+                    }
                 }
             }
         }
     }
+
+    /**
+     * 外卖订单缩略图:food 快照数组第一行的 image 键(行结构由 App 下单传入,
+     * 含 id/name/image/price/otherPrice/number 等);food 为空或非法返回 null。
+     */
+    private String firstFoodImage(String food)
+    {
+        if (StringUtils.isEmpty(food))
+        {
+            return null;
+        }
+        try
+        {
+            JSONArray rows = JSONArray.parseArray(food);
+            if (rows == null || rows.isEmpty())
+            {
+                return null;
+            }
+            JSONObject first = rows.getJSONObject(0);
+            String image = first == null ? null : first.getString("image");
+            return StringUtils.isEmpty(image) ? null : image;
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+    /** 闪送订单缩略图:批量查寄件凭证(SENDER),每单取 sortOrder 最小的一张;无凭证返回空。 */
+    private Map<Long, String> firstSenderImages(Collection<FlashDeliveryOrder> orders)
+    {
+        if (orders == null || orders.isEmpty())
+        {
+            return Collections.emptyMap();
+        }
+        Set<Long> orderIds = new LinkedHashSet<>();
+        for (FlashDeliveryOrder order : orders)
+        {
+            if (order != null && order.getId() != null)
+            {
+                orderIds.add(order.getId());
+            }
+        }
+        if (orderIds.isEmpty())
+        {
+            return Collections.emptyMap();
+        }
+        List<FlashDeliveryOrderImage> images = flashDeliveryOrderImageMapper.selectList(
+                new LambdaQueryWrapper<FlashDeliveryOrderImage>()
+                        .in(FlashDeliveryOrderImage::getOrderId, orderIds)
+                        .eq(FlashDeliveryOrderImage::getProofType, "SENDER"));
+        if (images == null || images.isEmpty())
+        {
+            return Collections.emptyMap();
+        }
+        Map<Long, FlashDeliveryOrderImage> firstImages = new HashMap<>();
+        for (FlashDeliveryOrderImage image : images)
+        {
+            if (image == null || image.getOrderId() == null || StringUtils.isEmpty(image.getImageUrl())
+                    || !"SENDER".equals(image.getProofType()))
+            {
+                continue;
+            }
+            firstImages.merge(image.getOrderId(), image,
+                    (current, candidate) -> sortKey(candidate) < sortKey(current) ? candidate : current);
+        }
+        Map<Long, String> result = new HashMap<>();
+        firstImages.forEach((orderId, image) -> result.put(orderId, image.getImageUrl()));
+        return result;
+    }
+
+    /** 凭证展示顺序;sortOrder 缺失时排最后。 */
+    private int sortKey(FlashDeliveryOrderImage image)
+    {
+        return image.getSortOrder() == null ? Integer.MAX_VALUE : image.getSortOrder();
+    }
 }

+ 71 - 0
ruoyi-system/src/test/java/com/ruoyi/system/service/impl/PushMessageServiceImplTest.java

@@ -7,7 +7,9 @@ import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
 import com.ruoyi.system.domain.PosOrder;
 import com.ruoyi.system.domain.PushMessage;
 import com.ruoyi.system.domain.flash.FlashDeliveryOrder;
+import com.ruoyi.system.domain.flash.FlashDeliveryOrderImage;
 import com.ruoyi.system.mapper.PosOrderMapper;
+import com.ruoyi.system.mapper.flash.FlashDeliveryOrderImageMapper;
 import com.ruoyi.system.mapper.flash.FlashDeliveryOrderMapper;
 import org.apache.ibatis.builder.MapperBuilderAssistant;
 import org.junit.jupiter.api.BeforeAll;
@@ -37,6 +39,7 @@ class PushMessageServiceImplTest {
 
     private final PosOrderMapper posOrderMapper = mock(PosOrderMapper.class);
     private final FlashDeliveryOrderMapper flashDeliveryOrderMapper = mock(FlashDeliveryOrderMapper.class);
+    private final FlashDeliveryOrderImageMapper flashDeliveryOrderImageMapper = mock(FlashDeliveryOrderImageMapper.class);
     private final PushMessageServiceImpl service = new PushMessageServiceImpl();
 
     @BeforeAll
@@ -45,12 +48,14 @@ class PushMessageServiceImplTest {
         MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
         TableInfoHelper.initTableInfo(assistant, PosOrder.class);
         TableInfoHelper.initTableInfo(assistant, FlashDeliveryOrder.class);
+        TableInfoHelper.initTableInfo(assistant, FlashDeliveryOrderImage.class);
     }
 
     @BeforeEach
     void setUp() {
         ReflectionTestUtils.setField(service, "posOrderMapper", posOrderMapper);
         ReflectionTestUtils.setField(service, "flashDeliveryOrderMapper", flashDeliveryOrderMapper);
+        ReflectionTestUtils.setField(service, "flashDeliveryOrderImageMapper", flashDeliveryOrderImageMapper);
     }
 
     @Test
@@ -97,6 +102,55 @@ class PushMessageServiceImplTest {
         verify(posOrderMapper, never()).selectList(any(Wrapper.class));
     }
 
+    @Test
+    void foodOrderMessageBackfillsFirstFoodImage() {
+        PushMessage message = message("991790045286244", 1);
+        PosOrder order = new PosOrder();
+        order.setDdId("991790045286244");
+        order.setPosName("阿寶早餐店");
+        // food 快照行结构(App 下单传入):id/name/image/price/otherPrice/number/ask/beizhu/addType
+        order.setFood("[{\"id\":9079,\"name\":\"草莓冰淇淋\",\"image\":\"/profile/upload/2026/08/18/a.png\",\"price\":55,\"otherPrice\":20,\"number\":1},"
+                + "{\"id\":9080,\"name\":\"蛋餅\",\"image\":\"/profile/upload/2026/08/18/b.png\",\"price\":40,\"otherPrice\":0,\"number\":2}]");
+        when(posOrderMapper.selectList(any(Wrapper.class))).thenReturn(List.of(order));
+
+        service.attachOrderInfo(List.of(message));
+
+        // 只取第一行商品的 image
+        assertEquals("/profile/upload/2026/08/18/a.png", message.getOrderImage());
+    }
+
+    @Test
+    void foodOrderWithoutUsableFoodJsonLeavesOrderImageNull() {
+        PushMessage message = message("991790045286244", 1);
+        PosOrder order = new PosOrder();
+        order.setDdId("991790045286244");
+        order.setFood("not-a-json");
+        when(posOrderMapper.selectList(any(Wrapper.class))).thenReturn(List.of(order));
+
+        service.attachOrderInfo(List.of(message));
+
+        assertNull(message.getOrderImage());
+    }
+
+    @Test
+    void flashOrderMessageBackfillsFirstSenderImage() {
+        PushMessage message = message("F20260924001", 3);
+        FlashDeliveryOrder order = new FlashDeliveryOrder();
+        order.setId(77L);
+        order.setOrderNo("F20260924001");
+        order.setPickupAddress("台中市西屯區文華路100號");
+        when(flashDeliveryOrderMapper.selectList(any(Wrapper.class))).thenReturn(List.of(order));
+        when(flashDeliveryOrderImageMapper.selectList(any(Wrapper.class))).thenReturn(List.of(
+                senderImage(77L, 1, "/profile/upload/2026/09/24/second.png"),
+                senderImage(77L, 0, "/profile/upload/2026/09/24/first.png"),
+                proofImage(77L, "PICKUP", "/profile/upload/2026/09/24/pickup.png")));
+
+        service.attachOrderInfo(List.of(message));
+
+        // 寄件凭证按 sortOrder 取第一张;取件/送达凭证不作为缩略图
+        assertEquals("/profile/upload/2026/09/24/first.png", message.getOrderImage());
+    }
+
     @Test
     void messageWithoutDdIdStaysUntouched() {
         PushMessage message = message(null, 1);
@@ -144,4 +198,21 @@ class PushMessageServiceImplTest {
         message.setPushType(pushType);
         return message;
     }
+
+    private FlashDeliveryOrderImage senderImage(Long orderId, int sortOrder, String url) {
+        return proofImage(orderId, "SENDER", url, sortOrder);
+    }
+
+    private FlashDeliveryOrderImage proofImage(Long orderId, String proofType, String url) {
+        return proofImage(orderId, proofType, url, 0);
+    }
+
+    private FlashDeliveryOrderImage proofImage(Long orderId, String proofType, String url, int sortOrder) {
+        FlashDeliveryOrderImage image = new FlashDeliveryOrderImage();
+        image.setOrderId(orderId);
+        image.setProofType(proofType);
+        image.setImageUrl(url);
+        image.setSortOrder(sortOrder);
+        return image;
+    }
 }