Rumah  >  Artikel  >  pangkalan data  >  Cara SpringBoot menggabungkan Aop+Redis untuk menghalang penyerahan antara muka berulang

Cara SpringBoot menggabungkan Aop+Redis untuk menghalang penyerahan antara muka berulang

王林
王林ke hadapan
2023-05-31 10:40:061238semak imbas

Dalam projek pembangunan sebenar, antara muka yang terdedah secara luaran sering menghadapi banyak permintaan Mari kita terangkan konsep mati pucuk: Kesan daripada sebarang pelaksanaan berbilang adalah sama dengan kesan satu pelaksanaan. Mengikut maksud ini, makna terakhir ialah kesan ke atas pangkalan data hanya boleh sekali sahaja dan tidak boleh diproses berulang kali. Cara memastikan idempotensinya biasanya melibatkan kaedah berikut:

1 Pangkalan data menetapkan indeks unik untuk memastikan hanya satu keping data dimasukkan ke dalam pangkalan data.

2. Dapatkan token sebelum setiap permintaan antara muka, dan kemudian tambahkan token ini pada badan pengepala permintaan pada kali seterusnya dipadamkan Seterusnya Token dinilai sekali lagi untuk setiap permintaan.

3. Kunci pesimis atau kunci optimistik boleh memastikan bahawa SQL lain tidak boleh mengemas kini data setiap kali untuk kemas kini (apabila enjin pangkalan data innodb, keadaan pilih mestilah indeks unik untuk menghalang keseluruhan jadual daripadanya. dikunci. )

4. Tanya dahulu dan kemudian nilaikan dahulu, tanya pangkalan data untuk melihat jika data itu wujud, ia membuktikan bahawa permintaan telah dibuat, dan permintaan itu ditolak secara langsung. Jika ia tidak wujud, ia membuktikan bahawa ia adalah kali pertama masuk, dan ia terus dikeluarkan.

Mengapa kita harus menghalang penyerahan berulang antara muka?
Untuk beberapa antara muka operasi yang sensitif, seperti antara muka data baharu dan antara muka pembayaran, jika pengguna mengklik butang serah secara tidak betul beberapa kali, antara muka ini akan diminta beberapa kali, yang akhirnya boleh menyebabkan pengecualian sistem.

Bagaimana bahagian hadapan boleh dikawal?
Hujung hadapan boleh dikawal melalui js Apabila pengguna mengklik butang hantar,
1 Tetapkan butang untuk tidak dapat diklik selama beberapa saat
2 kotak akan muncul untuk mengelak daripada mengklik lagi sehingga permintaan antara muka kembali selepas
3 Klik butang untuk melompat ke halaman baharu

Walau bagaimanapun, sila ingat, jangan percaya tingkah laku pengguna, kerana anda tidak. Tidak tahu operasi pelik yang akan dilakukan pengguna, jadi perkara yang paling penting ialah Ia masih perlu diproses di bahagian belakang.

Gunakan aop+redis untuk pemprosesan pemintasan
1 Cipta kelas aspek RepeatSubmitAspect
Proses pelaksanaan: Selepas permintaan antara muka, laluan token+permintaan digunakan sebagai nilai kunci untuk membaca data dalam redis Jika kunci itu boleh ditemui, ia membuktikan bahawa ia telah diserahkan berulang kali, dan sebaliknya. Jika ia bukan penyerahan berulang, ia akan dikeluarkan terus dan kunci akan ditulis ke dalam redis dan ditetapkan untuk tamat tempoh dalam masa tertentu (saya tetapkan tamat tempoh 5s di sini)


Dalam projek web tradisional , untuk mengelakkan Untuk penyerahan berulang, pendekatan biasa ialah: bahagian belakang menjana token penyerahan unik (uuid) dan menyimpannya pada pelayan Apabila halaman memulakan permintaan, ia membawa token itu selepas mengesahkan permintaan untuk memastikan keunikan permintaan itu.
Walau bagaimanapun, kaedah rayuan memerlukan perubahan pada kedua-dua bahagian depan dan belakang Jika ia di peringkat awal projek, ia boleh dicapai Namun, pada peringkat akhir projek, banyak fungsi telah dilaksanakan adalah mustahil untuk membuat perubahan besar-besaran.

Idea
1 Sesuaikan anotasi @NoRepeatSubmit untuk menandakan semua permintaan yang diserahkan dalam Pengawal
2 Minta semua kaedah yang ditandakan dengan @NoRepeatSubmit melalui AOP
3 , dapatkan token pengguna semasa atau alamat permintaan semasa JSessionId + sebagai kunci unik untuk mendapatkan kunci yang diedarkan semula Jika ia diperoleh serentak pada masa ini, hanya satu utas boleh mendapatkannya.
4. Selepas perniagaan dilaksanakan, lepaskan kunci

Mengenai kunci yang diedarkan Redis
Menggunakan Redis adalah untuk penggunaan pengimbangan beban Jika ia adalah projek yang berdiri sendiri, anda boleh menggunakan rangkaian tempatan -Cache selamat untuk menggantikan Redis

Kod
Anotasi tersuai

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;

}

AOP

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();
    }


}

Atas ialah kandungan terperinci Cara SpringBoot menggabungkan Aop+Redis untuk menghalang penyerahan antara muka berulang. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam