| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162 |
- package com.ruoyi.app.utils.omg;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.data.redis.core.StringRedisTemplate;
- import org.springframework.stereotype.Component;
- import java.util.concurrent.TimeUnit;
- /**
- * OMG queryTrade 节流(防 OMG 按 MerchantID HTTP 403 自残)。
- *
- * <p>OMG 的 403 是"短时间内反复查同一笔"触发(频率),不是"下单后 N 分钟内不能查"。故只做 <b>频率</b> 节流、
- * 不做时间黑名单——已付款订单可立即查状态(notify 丢失时 /query 立刻补单)。原"40min 首查延迟"是对 OMG 节流的
- * 误读(会让已付客户等 40min 才看到状态变更,不可接受),已移除。两道闸,均基于 Redis {@code SET NX EX}(原子,无锁竞争):
- * <ol>
- * <li>per-MerchantID 令牌桶:同一商店代号每 interval(默认 3s)只放行一次 queryTrade(burst 1),
- * 非阻塞——拿不到令牌跳过本行本轮。</li>
- * <li>per-ddId 扫描间隔:{@code /query} 默认 60s、定时默认 180s 内不重复查同一订单(挡前端 2s 轮询放大)。</li>
- * </ol>
- * 参数 §9 待 OMG 官方确认后据实调整。
- */
- @Component
- public class OmgQueryThrottle {
- @Value("${omg.query-throttle.merchant-token-seconds:3}")
- private int merchantTokenSeconds;
- @Value("${omg.query-throttle.query-dd-seconds:60}")
- private int queryDdSeconds;
- @Value("${omg.query-throttle.reconcile-dd-seconds:180}")
- private int reconcileDdSeconds;
- private final StringRedisTemplate redis;
- public OmgQueryThrottle(StringRedisTemplate redis) {
- this.redis = redis;
- }
- /** per-MerchantID 令牌桶(1 token / merchantTokenSeconds,burst 1)。true=已占位可查;false=限流跳过本行本轮。 */
- public boolean tryAcquireMerchantToken(String merchantId) {
- if (merchantId == null || merchantId.isEmpty()) {
- return true;
- }
- String key = "omg:qt:token:" + merchantId;
- Boolean ok = redis.opsForValue().setIfAbsent(key, "1", merchantTokenSeconds, TimeUnit.SECONDS);
- return Boolean.TRUE.equals(ok);
- }
- /** per-ddId 扫描间隔。source="query"→/query(queryDdSeconds);其他→定时(reconcileDdSeconds)。
- * true=可处理;false=窗口内重复,跳过本轮。 */
- public boolean acquireDdIdSlot(String ddId, String source) {
- if (ddId == null || ddId.isEmpty()) {
- return true;
- }
- boolean passive = "query".equals(source);
- String key = (passive ? "omg:query:dd:" : "omg:reconcile:dd:") + ddId;
- long ttl = passive ? queryDdSeconds : reconcileDdSeconds;
- Boolean ok = redis.opsForValue().setIfAbsent(key, "1", ttl, TimeUnit.SECONDS);
- return Boolean.TRUE.equals(ok);
- }
- }
|