Bläddra i källkod

fix: 停车券链接 414 Request-URI Too Large

两处修改:
1. checkParkingCoupon 返回短码链接而非第三方长 URL,避免浏览器
   location.href 直接跳转长 URL 触发第三方服务器 414
2. ParkingCouponController.jump 对 >= 2000 字符的长 URL 改用
   服务端代理拉取页面,注入 <base> 标签后返回,避免 302 重定
   向导致浏览器 GET 请求携带超长 URL

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 1 dag sedan
förälder
incheckning
c5f4bd4e8f

+ 112 - 18
car-wash-miniapp/src/main/java/com/kym/miniapp/controller/ParkingCouponController.java

@@ -1,46 +1,140 @@
 package com.kym.miniapp.controller;
 
 import cn.dev33.satoken.annotation.SaIgnore;
+import com.kym.common.utils.HttpUtil;
+import com.kym.entity.User;
+import com.kym.entity.WashOrder;
+import com.kym.service.MpMsgTemplateService;
+import com.kym.service.UserService;
 import com.kym.service.WashOrderService;
 import com.kym.service.cache.KymCache;
 import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
+import java.net.URL;
+import java.util.UUID;
 
 /**
  * 停车优惠链接跳转
  * @author skyline
  * @since 2024-08-07
  */
+@Slf4j
 @RestController
 @RequestMapping("/parking-coupon")
 public class ParkingCouponController {
 
-	private final WashOrderService washOrderService;
+    private static final int MAX_REDIRECT_URL_LENGTH = 2000;
 
-    public ParkingCouponController(WashOrderService washOrderService) {
+    private final WashOrderService washOrderService;
+    private final MpMsgTemplateService mpMsgTemplateService;
+    private final UserService userService;
+
+    @Value("${kym.domain}")
+    private String DOMAIN;
+
+    public ParkingCouponController(WashOrderService washOrderService,
+                                   MpMsgTemplateService mpMsgTemplateService,
+                                   UserService userService) {
         this.washOrderService = washOrderService;
+        this.mpMsgTemplateService = mpMsgTemplateService;
+        this.userService = userService;
+    }
+
+    /**
+     * 停车优惠链接跳转
+     */
+    @SaIgnore
+    @GetMapping
+    public void jump(HttpServletResponse response, @RequestParam String code) throws IOException {
+        var url = KymCache.INSTANCE.getParkingCouponUrl(code);
+        if (url == null || url.isEmpty()) {
+            response.setContentType("text/html; charset=utf-8");
+            response.getWriter().write("""
+                <!DOCTYPE html>
+                <html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
+                <title>链接已失效</title>
+                <style>body{font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#f5f5f5}
+                .card{background:#fff;padding:32px 24px;border-radius:8px;text-align:center;box-shadow:0 2px 12px rgba(0,0,0,.08)}
+                h2{color:#e74c3c;margin:0 0 8px}p{color:#666;margin:0}</style>
+                </head><body><div class="card"><h2>链接已失效</h2><p>停车优惠券链接已过期或不存在,请联系客服</p></div></body></html>""");
+            return;
+        }
+
+        if (url.length() < MAX_REDIRECT_URL_LENGTH) {
+            response.sendRedirect(url);
+            return;
+        }
+
+        // URL 过长时使用服务端代理,避免浏览器 GET 请求触发第三方服务器 414
+        try {
+            String content = HttpUtil.get(url);
+            String origin = extractOrigin(url);
+            content = content.replaceAll("(?i)<head([^>]*)>", "<head$1><base href=\"" + origin + "\">");
+            response.setContentType("text/html; charset=utf-8");
+            response.getWriter().write(content);
+        } catch (Exception e) {
+            log.error("代理停车券页面失败, url: {}", url, e);
+            response.sendRedirect(url);
+        }
+    }
+
+    /**
+     * 查询是否符合停车优惠
+     */
+    @SaIgnore
+    @GetMapping("/checkParkingCoupon")
+    public String checkParkingCoupon(@RequestParam String mobilePhone) {
+        return washOrderService.checkParkingCoupon(mobilePhone);
     }
 
     /**
-	 * 停车优惠链接跳转
-	 */
+     * [测试] 生成停车券短链接,返回 url 可直接在浏览器打开测试
+     */
     @SaIgnore
-	@GetMapping
-	public void jump(HttpServletResponse response, @RequestParam String code) throws IOException {
-		// 从缓存中获取链接并跳转
-		var url = KymCache.INSTANCE.getParkingCouponUrl(code);
-		response.sendRedirect(url);
-	}
-
-	/**
-	 * 查询是否符合停车优惠
-	 */
+    @GetMapping("/testLink")
+    public String testLink(@RequestParam String mobilePhone) {
+        return washOrderService.checkParkingCoupon(mobilePhone);
+    }
+
+    /**
+     * [测试] 发送停车券微信模板消息,需要用户已关注公众号且有 mpOpenid
+     */
     @SaIgnore
-	@GetMapping("/checkParkingCoupon")
-	public String checkParkingCoupon(@RequestParam String mobilePhone) {
-		return washOrderService.checkParkingCoupon(mobilePhone);
-	}
+    @GetMapping("/testSend")
+    public String testSend(@RequestParam String mobilePhone) {
+        var user = userService.lambdaQuery().eq(User::getMobilePhone, mobilePhone).one();
+        if (user == null) {
+            return "用户不存在: " + mobilePhone;
+        }
+        var orders = washOrderService.lambdaQuery()
+                .eq(WashOrder::getUserId, user.getId())
+                .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_已支付)
+                .orderByDesc(WashOrder::getId)
+                .list();
+        if (orders.isEmpty()) {
+            return "该用户没有已支付订单,无法构造模板消息参数";
+        }
+        var washOrder = orders.get(0);
+        var parkingCouponUrl = KymCache.INSTANCE.getParkingQrCodeUrlByStationId(washOrder.getStationId());
+        var code = UUID.randomUUID().toString();
+        KymCache.INSTANCE.setParkingCouponCode(code, parkingCouponUrl, 3600 * 2L);
+        var url = DOMAIN + "/api/parking-coupon?code=" + code;
+        mpMsgTemplateService.sendParkingCouponMsg(washOrder, url);
+        return "已发送, url=" + url;
+    }
+
+    private static String extractOrigin(String url) {
+        try {
+            URL u = new URL(url);
+            int port = u.getPort();
+            return u.getProtocol() + "://" + u.getHost() + (port > 0 ? ":" + port : "") + "/";
+        } catch (Exception e) {
+            return url;
+        }
+    }
 
 }

+ 13 - 2
car-wash-service/src/main/java/com/kym/service/impl/WashOrderServiceImpl.java

@@ -32,6 +32,7 @@ import com.kym.service.cache.KymCache;
 import com.kym.service.mybatisplus.MyBaseServiceImpl;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 
@@ -41,6 +42,7 @@ import java.time.LocalTime;
 import java.time.temporal.TemporalAdjusters;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 import java.util.stream.Collectors;
 
 /**
@@ -61,6 +63,9 @@ public class WashOrderServiceImpl extends MyBaseServiceImpl<WashOrderMapper, Was
     private final UserService userService;
     private final OrderSettlementService orderSettlementService;
 
+    @Value("${kym.domain}")
+    private String DOMAIN;
+
     public WashOrderServiceImpl(AwoaraService awoaraService, AccountService accountService,
                                 @Lazy WashStationService washStationService, UserService userService,
                                 @Lazy OrderSettlementService orderSettlementService) {
@@ -487,7 +492,10 @@ public class WashOrderServiceImpl extends MyBaseServiceImpl<WashOrderMapper, Was
                 .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_已支付)
                 .orderByDesc(WashOrder::getId).list();
         if (!orderList.isEmpty() && orderList.stream().collect(Collectors.summarizingInt(WashOrder::getAmount)).getSum() >= WashOrder.PARKING_COUPON_MIN_AMOUNT) {
-            return KymCache.INSTANCE.getParkingQrCodeUrlByStationId(orderList.get(0).getStationId());
+            var parkingCouponUrl = KymCache.INSTANCE.getParkingQrCodeUrlByStationId(orderList.get(0).getStationId());
+            var code = UUID.randomUUID().toString();
+            KymCache.INSTANCE.setParkingCouponCode(code, parkingCouponUrl, 3600 * 2L);
+            return DOMAIN + "/api/parking-coupon?code=" + code;
         }
 
         // 未找到符合条件的已支付订单,尝试查找僵死订单并主动对账结算
@@ -521,7 +529,10 @@ public class WashOrderServiceImpl extends MyBaseServiceImpl<WashOrderMapper, Was
                         .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_已支付)
                         .orderByDesc(WashOrder::getId).list();
                 if (!orderList.isEmpty() && orderList.stream().collect(Collectors.summarizingInt(WashOrder::getAmount)).getSum() >= WashOrder.PARKING_COUPON_MIN_AMOUNT) {
-                    return KymCache.INSTANCE.getParkingQrCodeUrlByStationId(orderList.get(0).getStationId());
+                    var parkingCouponUrl = KymCache.INSTANCE.getParkingQrCodeUrlByStationId(orderList.get(0).getStationId());
+                    var code = UUID.randomUUID().toString();
+                    KymCache.INSTANCE.setParkingCouponCode(code, parkingCouponUrl, 3600 * 2L);
+                    return DOMAIN + "/api/parking-coupon?code=" + code;
                 }
             }
         }