Geleneksel endüstri veya İnternet endüstrisi ne olursa olsun, çoğu işlemin idempotent olmasını sağlamalıyız Basitçe söylemek gerekirse, kullanıcı kaç kez tıklarsa tıklasın, kaç işlem olursa olsun, sonuç aynı ve benzersizdir. Ve bu şirketin projesinde, böyle idempotent bir sorunla karşılaştım, yani kullanıcının bakiyesi yeniden yükleme, sipariş oluşturma ve sipariş ödemesi Kullanıcı kaç kez tıklarsa tıklasın, sadece bir yeniden yükleme kaydı ve bir yeni sipariş kaydı olacak. Bir sipariş ödeme kaydı.
En çok kullanılan çözümler Redis'e dayanmaktadır.
Çözüm: Redis + token
İşleme akışı: Verileri göndermeden önce, ön uç sunucudan bir jeton için başvurmalı ve jetonu (sona erme süreli) redise koymalıdır; veri gönderildiğinde jetonu getirin, jeton başarıyla silinirse jetonun süresinin dolmadığı anlamına gelir ve daha sonra iş mantığı gerçekleştirilir, aksi takdirde Diğer bir deyişle, jetonun süresi dolmuştur ve ön uçtan verileri tekrar tekrar göndermemesini ister.
Ve farklı bir şema kullanacağım. Ön uç ve arka uç yerleştirme şu anda yarı yolda olduğundan, ön uçun belirteç istemek için arabirimi artırmasını istemiyorum (sonuçta arka uç bunu halledebilir, bu nedenle ön uç öğrencileri rahatsız etmeyin).
Çözüm: özel açıklama + dağıtılmış kilit
İşleme akışı: idempotence gerektiren arabirimlere özel açıklamalar ekleyin. Ardından, etrafındaki yöntemde bir yön, mantık yazın: dağıtılmış bir kilit elde etmeye çalışın (sona erme süresi ile), başarı tekrarlanan bir gönderimin olmadığını gösterir, aksi takdirde tekrarlanan gönderme.
1. Redis bağımlılığı ekleyin:
< bağımlılık >
< Grup kimliği > org.springframework.boot < /Grup kimliği >
< artifactId > Spring-boot-starter-data-redis < / artifactId >
< /bağımlılık >
2. Özel ek açıklamalar:
/ **
* @author Howinfun
* @desc özel açıklama: dağıtılmış kilit
* @tarih 2019/11/12
* /
@Target (ElementType.METHOD)
@Retention (RetentionPolicy.RUNTIME)
public @interface CacheLock {
/ ** anahtar öneki * /
Dize öneki () varsayılan "";
/ ** Süresi dolacak saniye sayısı, varsayılan 5 saniyedir * /
int expire () varsayılan 5;
/ ** Zaman aşımı birimi, varsayılan saniye * /
TimeUnit timeUnit () varsayılan TimeUnit.SECONDS;
/ ** Anahtar ayırıcı (varsayılan :) * /
Dize sınırlayıcı () varsayılan ":";
}
3. Özel durum:
/ **
* @author Howinfun
* @desc özel yönü
* @tarih 2019/11/12
* /
@Görünüş
@Bileşen
public class LockCheckAspect {
/ ** lua * /
private static final String RELEASE_LOCK_LUA_SCRIPT = "redis.call ('get', KEYS) == ARGV ise redis.call ('del', KEYS) döndürürse 0 end döndür";
@Autowired
özel RedisTemplate < Dize, Nesne > redisTemplate;
// Yöntemi CacheLock ek açıklamasıyla geliştirin
@Pointcut ("@ annotation (cn.gdmcmc.system.api.config.aop.CacheLock)")
public void pointCut () {}
@Around ("pointCut ()")
public Object around (ProceedingJoinPoint joinPoint) Throwable {
// Kullanıcının benzersiz kişisel bilgileri, cep telefonu numarası gibi işletmeye göre elde edilebilir
Dize telefon = .....;
MethodSignature imzası = (MethodSignature) joinPoint.getSignature ();
Yöntem yöntemi = imza.getMethod ();
CacheLock cacheLock = method.getAnnotation (CacheLock.class);
Dize öneki = cacheLock.prefix ();
if (StringUtils.isBlank (önek)) {
yeni GlobalException ("CacheLock öneki boş olamaz");
}
// ekleme anahtarı
Dize sınırlayıcı = cacheLock.delimiter ();
StringBuilder sb = new StringBuilder ();
sb.append (önek) .append (sınırlayıcı) .append (telefon);
final String lockKey = sb.toString ();
final String UUID = cn.hutool.core.lang.UUID.fastUUID (). toString ();
Deneyin {
// Kilidi al
boole başarısı = redisTemplate.opsForValue (). setIfAbsent (lockKey, UUID, cacheLock.expire (), cacheLock.timeUnit ());
eğer (! başarılı) {
yeni CustomDeniedException oluştur ("Tekrar tekrar göndermeyin");
}
Nesne sonucu = joinPoint.proceed ();
dönüş sonucu;
}en sonunda {
// Son olarak kilidi açmayı unutmayın
DefaultRedisScript < Uzun > redisScript = yeni DefaultRedisScript < > (RELEASE_LOCK_LUA_SCRIPT, Long.class);
Uzun sonuç = redisTemplate.execute (redisScript, Collections.singletonList (lockKey), UUID);
}
}
}
4. Bu noktada idempotent olması gereken arabirimlere @CacheLock ek açıklamalarını ekleyin.
@Filmdenkare
@RequestMapping (value = "/ charge")
@AllArgsConstructor
public class ChargeController {
@PostMapping ("/ startCharge")
@CacheLock (prefix = "şarj")
public Result startCharge (@RequestBody @Validated ({ChargeQuery.QRCodeNotBlank.class}) ChargeQuery sorgusu) {
this.chargeChargeService.startCharge (sorgu) döndür;
}
}
Orijinal: https://blog.csdn.net/Howinfun/article/details/103062184