In tatsächlichen Entwicklungsprojekten ist eine extern verfügbar gemachte Schnittstelle häufig mit vielen Anforderungen konfrontiert. Lassen Sie uns das Konzept der Idempotenz erklären: Die Auswirkung mehrerer Ausführungen ist die gleiche wie die Auswirkung einer einzelnen Ausführung. Nach dieser Bedeutung besteht die endgültige Bedeutung darin, dass die Auswirkung auf die Datenbank nur einmalig sein kann und nicht wiederholt verarbeitet werden kann. Um die Idempotenz sicherzustellen, sind normalerweise die folgenden Methoden erforderlich:
1 Die Datenbank erstellt einen eindeutigen Index, um sicherzustellen, dass letztendlich nur ein Datenelement in die Datenbank eingefügt wird.
2. Besorgen Sie sich vor jeder Schnittstellenanforderung ein Token und fügen Sie dieses Token beim nächsten Mal zum Header der Anforderung hinzu. Wenn die Überprüfung erfolgreich ist, wird das Token gelöscht Die Anfrage wird erneut beurteilt.
3. Pessimistische Sperren oder optimistische Sperren können sicherstellen, dass andere SQL-Anweisungen nicht jedes Mal Daten aktualisieren können (wenn die Datenbank-Engine innodb ist, muss die Auswahlbedingung ein eindeutiger Index sein, um zu verhindern, dass die gesamte Tabelle gesperrt wird).
4 , Fragen Sie zuerst ab und beurteilen Sie dann, ob die Daten in der Datenbank vorhanden sind. Wenn dies der Fall ist, wird die Anfrage direkt abgelehnt Melden Sie sich zum ersten Mal an und die Anfrage wird direkt freigegeben. Warum sollten wir die wiederholte Einreichung von Schnittstellen verhindern?Bei einigen sensiblen Betriebsschnittstellen, wie z. B. neuen Datenschnittstellen und Zahlungsschnittstellen, werden diese Schnittstellen mehrmals angefordert, wenn der Benutzer fälschlicherweise mehrmals auf die Schaltfläche „Senden“ klickt, was schließlich zu Systemausnahmen führen kann.
Das Frontend kann über js gesteuert werden.
1 Die Schaltfläche ist für einige Sekunden nicht klickbar.
2 Vermeiden Sie es, erneut zu klicken, bis die Schnittstellenanforderung zurückkehrt.
3. Klicken Sie auf die Schaltfläche. Dann springen Sie zu einer neuen Seite. Denken Sie jedoch daran, niemals dem Verhalten des Benutzers zu vertrauen, da Sie nicht wissen, welche seltsamen Vorgänge der Benutzer ausführen wird Das Wichtigste ist, es im Backend zu verarbeiten.
1. Erstellen Sie die Aspektklasse RepeatSubmitAspect
Implementierungsprozess: Nach der Schnittstellenanforderung wird der Token+Anforderungspfad als Schlüsselwert zum Lesen der Daten in Redis verwendet Dies beweist, dass es wiederholt eingereicht wurde. Das Gegenteil ist nicht der Fall. Wenn es sich nicht um eine wiederholte Übermittlung handelt, wird sie direkt freigegeben und der Schlüssel wird in Redis geschrieben und so eingestellt, dass er innerhalb eines bestimmten Zeitraums abläuft (ich habe hier einen Ablauf von 5 Sekunden festgelegt)
In traditionellen Webprojekten Um wiederholte Übermittlungen zu verhindern, ist der übliche Ansatz wie folgt: Das Backend generiert ein eindeutiges Übermittlungstoken (UUID) und speichert es auf dem Server. Wenn die Seite eine Anfrage initiiert, löscht das Backend das Token nach der Überprüfung die Anfrage, um die Einzigartigkeit der Anfrage sicherzustellen.
Idee
1. Passen Sie die Annotation @NoRepeatSubmit an, um alle im Controller übermittelten Anforderungen abzufangen.
4. Nachdem das Geschäft ausgeführt wurde, geben Sie die Sperre frei
Über die verteilte Redis-Sperre
Die Verwendung von Redis dient der Lastausgleichsbereitstellung. Sie können einen lokalen Thread-sicheren Cache verwenden, um Redis zu ersetzen Code
Benutzerdefinierte Anmerkungen
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @ClassName NoRepeatSubmit * @Description 这里描述 * @Author admin * @Date 2021/3/2 16:16 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoRepeatSubmit { /** * 设置请求锁定时间 * * @return */ int lockTime() default 10; }
package com.hongkun.aop; /** * @ClassName RepeatSubmitAspect * @Description 这里描述 * @Author admin * @Date 2021/3/2 16:15 */ import com.hongkun.until.ApiResult; import com.hongkun.until.Result; import com.hongkun.until.RedisLock; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * @author liucheng * @since 2020/01/15 * 防止接口重复提交 */ @Aspect @Component public class RepeatSubmitAspect { private static final Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class); @Autowired private RedisLock redisLock; @Pointcut("@annotation(noRepeatSubmit)") public void pointCut(NoRepeatSubmit noRepeatSubmit) { } @Around("pointCut(noRepeatSubmit)") public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable { int lockSeconds = noRepeatSubmit.lockTime(); RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); Assert.notNull(request, "request can not null"); // 此处可以用token或者JSessionId String token = request.getHeader("token"); String path = request.getServletPath(); String key = getKey(token, path); String clientId = getClientId(); boolean isSuccess = redisLock.lock(key, clientId, lockSeconds,TimeUnit.SECONDS); LOGGER.info("tryLock key = [{}], clientId = [{}]", key, clientId); if (isSuccess) { LOGGER.info("tryLock success, key = [{}], clientId = [{}]", key, clientId); // 获取锁成功 Object result; try { // 执行进程 result = pjp.proceed(); } finally { // 解锁 redisLock.unlock(key, clientId); LOGGER.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId); } return result; } else { // 获取锁失败,认为是重复提交的请求 LOGGER.info("tryLock fail, key = [{}]", key); return ApiResult.success(200, "重复请求,请稍后再试", null); } } private String getKey(String token, String path) { return "00000"+":"+token + path; } private String getClientId() { return UUID.randomUUID().toString(); } }
Das obige ist der detaillierte Inhalt vonWie SpringBoot Aop+Redis kombiniert, um die wiederholte Übermittlung von Schnittstellen zu verhindern. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!