Kaynağa Gözat

订单添加字段

lutong 2 hafta önce
ebeveyn
işleme
00814224c9

+ 1 - 1
alien-dining/src/main/java/shop/alien/dining/controller/StoreOrderController.java

@@ -369,7 +369,7 @@ public class StoreOrderController {
         }
     }
 
-    @ApiOperation(value = "查询订单信息", notes = "根据订单ID查询订单完整信息(包含订单基本信息、菜品清单、价格明细)")
+    @ApiOperation(value = "查询订单信息", notes = "根据订单ID查询订单完整信息(包含订单基本信息、tableId、menuType、菜品清单、价格明细)")
     @GetMapping("/info/{orderId}")
     public R<OrderInfoVO> getOrderInfo(@ApiParam(value = "订单ID", required = true) @PathVariable Integer orderId) {
         log.info("StoreOrderController.getOrderInfo?orderId={}", orderId);

+ 38 - 16
alien-dining/src/main/java/shop/alien/dining/service/impl/SseServiceImpl.java

@@ -6,9 +6,12 @@ import org.springframework.stereotype.Service;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -27,6 +30,9 @@ public class SseServiceImpl implements shop.alien.dining.service.SseService {
     // 定时任务执行器,用于发送心跳
     private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(10);
 
+    /** 每个 SSE 连接对应的心跳任务,连接移除时必须 cancel,否则会对已 complete 的 emitter 继续 send 误报 ERROR */
+    private final ConcurrentHashMap<String, ScheduledFuture<?>> heartbeatTasks = new ConcurrentHashMap<>();
+
     @Override
     public SseEmitter createConnection(Integer tableId) {
         log.info("创建SSE连接, tableId={}", tableId);
@@ -109,14 +115,10 @@ public class SseServiceImpl implements shop.alien.dining.service.SseService {
         log.info("关闭SSE连接, tableId={}", tableId);
         ConcurrentHashMap<String, SseEmitter> tableConnections = connections.get(tableId);
         if (tableConnections != null) {
-            tableConnections.forEach((connectionId, emitter) -> {
-                try {
-                    emitter.complete();
-                } catch (Exception e) {
-                    log.error("关闭SSE连接失败, tableId={}, connectionId={}", tableId, connectionId, e);
-                }
-            });
-            connections.remove(tableId);
+            List<String> ids = new ArrayList<>(tableConnections.keySet());
+            for (String connectionId : ids) {
+                removeConnection(tableId, connectionId);
+            }
         }
     }
 
@@ -125,11 +127,19 @@ public class SseServiceImpl implements shop.alien.dining.service.SseService {
      */
     private boolean isClientDisconnect(Throwable ex) {
         if (ex == null) return false;
+        String cn = ex.getClass().getSimpleName();
+        if ("AsyncRequestNotUsableException".equals(cn) || "ClientAbortException".equals(cn)) {
+            return true;
+        }
         String msg = ex.getMessage();
         if (msg != null) {
             String lower = msg.toLowerCase();
             if (lower.contains("broken pipe") || lower.contains("connection reset")
-                    || lower.contains("connection closed") || lower.contains("an established connection was aborted")) {
+                    || lower.contains("connection closed") || lower.contains("an established connection was aborted")
+                    || lower.contains("connection abort") || lower.contains("abort") || lower.contains("stream closed")
+                    || lower.contains("closed channel") || lower.contains("already completed")
+                    || lower.contains("responsebodyemitter") || lower.contains("not usable")
+                    || msg.contains("中止") || msg.contains("已关闭")) {
                 return true;
             }
         }
@@ -140,6 +150,10 @@ public class SseServiceImpl implements shop.alien.dining.service.SseService {
      * 移除连接
      */
     private void removeConnection(Integer tableId, String connectionId) {
+        ScheduledFuture<?> hb = heartbeatTasks.remove(connectionId);
+        if (hb != null) {
+            hb.cancel(false);
+        }
         ConcurrentHashMap<String, SseEmitter> tableConnections = connections.get(tableId);
         if (tableConnections != null) {
             SseEmitter emitter = tableConnections.remove(connectionId);
@@ -160,13 +174,20 @@ public class SseServiceImpl implements shop.alien.dining.service.SseService {
      * 启动心跳任务
      */
     private void startHeartbeat(Integer tableId, String connectionId, SseEmitter emitter) {
-        scheduler.scheduleAtFixedRate(() -> {
-            try {
-                if (emitter != null) {
-                    emitter.send(SseEmitter.event()
-                            .name("heartbeat")
-                            .data("ping"));
+        final ScheduledFuture<?>[] holder = new ScheduledFuture<?>[1];
+        holder[0] = scheduler.scheduleAtFixedRate(() -> {
+            ConcurrentHashMap<String, SseEmitter> tableConnections = connections.get(tableId);
+            if (tableConnections == null || tableConnections.get(connectionId) != emitter) {
+                heartbeatTasks.remove(connectionId, holder[0]);
+                if (holder[0] != null) {
+                    holder[0].cancel(false);
                 }
+                return;
+            }
+            try {
+                emitter.send(SseEmitter.event()
+                        .name("heartbeat")
+                        .data("ping"));
             } catch (IOException e) {
                 if (isClientDisconnect(e)) {
                     log.debug("心跳时客户端已断开, tableId={}, connectionId={}", tableId, connectionId);
@@ -175,6 +196,7 @@ public class SseServiceImpl implements shop.alien.dining.service.SseService {
                 }
                 removeConnection(tableId, connectionId);
             }
-        }, 30, 30, TimeUnit.SECONDS); // 每30秒发送一次心跳
+        }, 30, 30, TimeUnit.SECONDS);
+        heartbeatTasks.put(connectionId, holder[0]);
     }
 }

+ 2 - 0
alien-dining/src/main/java/shop/alien/dining/service/impl/StoreOrderServiceImpl.java

@@ -1604,7 +1604,9 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         vo.setStoreBlurb(storeBlurb);
         vo.setStoreType(storeType);
         vo.setBusinessStatus(businessStatus);
+        vo.setTableId(order.getTableId());
         vo.setTableNumber(order.getTableNumber());
+        vo.setMenuType(order.getMenuType());
         vo.setUserReservationId(order.getUserReservationId());
         vo.setDinerCount(order.getDinerCount());
         vo.setContactPhone(order.getContactPhone());

+ 6 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/OrderInfoVO.java

@@ -46,9 +46,15 @@ public class OrderInfoVO {
     @ApiModelProperty(value = "营业状态(-1:注销中, 0:正常营业, 1:暂停营业, 2:筹建中, 99:永久关门)")
     private Integer businessStatus;
 
+    @ApiModelProperty(value = "桌台主键 store_table.id(可与桌号展示文案对照)")
+    private Integer tableId;
+
     @ApiModelProperty(value = "桌号")
     private String tableNumber;
 
+    @ApiModelProperty(value = "菜单类型:1=美食(store_cuisine) 2=通用价目(store_price),与点餐下单时一致")
+    private Integer menuType;
+
     @ApiModelProperty(value = "关联用户预约ID(user_reservation.id)")
     private Integer userReservationId;