精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Spring Boot防重復提交優化策略

開發 前端
從測試的結果上看,防抖是做到了,但是隨著緩存消失、鎖失效,還是可以發起同樣的請求,所以要真正做到接口冪等性,還需要業務代碼的判斷、設置數據庫表的UK索引等操作。

啥是防抖

圖片圖片

所謂防抖,一是防用戶手抖,二是防網絡抖動。在Web系統中,表單提交是一個非常常見的功能,如果不加控制,容易因為用戶的誤操作或網絡延遲導致同一請求被發送多次,進而生成重復的數據記錄。要針對用戶的誤操作,前端通常會實現按鈕的loading狀態,阻止用戶進行多次點擊。而對于網絡波動造成的請求重發問題,僅靠前端是不行的。為此,后端也應實施相應的防抖邏輯,確保在網絡波動的情況下不會接收并處理同一請求多次。

一個理想的防抖組件或機制,我覺得應該具備以下特點:

  1. 邏輯正確,也就是不能誤判;
  2. 響應迅速,不能太慢;
  3. 易于集成,邏輯與業務解耦;
  4. 良好的用戶反饋機制,比如提示“您點擊的太快了”

思路解析

哪一類接口需要防抖?

接口防抖也不是每個接口都需要加,一般需要加防抖的接口有這幾類:

  • 用戶輸入類接口:比如搜索框輸入、表單輸入等,用戶輸入往往會頻繁觸發接口請求,但是每次觸發并不一定需要立即發送請求,可以等待用戶完成輸入一段時間后再發送請求。
  • 按鈕點擊類接口:比如提交表單、保存設置等,用戶可能會頻繁點擊按鈕,但是每次點擊并不一定需要立即發送請求,可以等待用戶停止點擊一段時間后再發送請求。
  • 滾動加載類接口:比如下拉刷新、上拉加載更多等,用戶可能在滾動過程中頻繁觸發接口請求,但是每次觸發并不一定需要立即發送請求,可以等待用戶停止滾動一段時間后再發送請求。

如何確定接口是重復的?

防抖也即防重復提交,那么如何確定兩次接口就是重復的呢?首先,我們需要給這兩次接口的調用加一個時間間隔,大于這個時間間隔的一定不是重復提交;其次,兩次請求提交的參數比對,不一定要全部參數,選擇標識性強的參數即可;最后,如果想做的更好一點,還可以加一個請求地址的對比。

分布式部署下如何做接口防抖?

有兩個方案:

使用共享緩存

流程圖如下:

圖片圖片

使用分布式鎖

流程圖如下:

圖片圖片

常見的分布式組件有Redis、Zookeeper等,但結合實際業務來看,一般都會選擇Redis,因為Redis一般都是Web系統必備的組件,不需要額外搭建。

具體實現

現在有一個保存用戶的接口

@PostMapping("/add")
@RequiresPermissions(value = "add")
@Log(methodDesc = "添加用戶")
public ResponseEntity<String> add(@RequestBody AddReq addReq) {
        return userService.add(addReq);
}
import java.util.List;
import lombok.Data;
@Datapublic class AddReq {
    /**     * 用戶名稱     */    private String userName;
    /**     * 用戶手機號     */    private String userPhone;
    /**     * 角色ID列表     */    private List<Long> roleIdList;}

目前數據庫表中沒有對userPhone字段做UK索引,這就會導致每調用一次add就會創建一個用戶,即使userPhone相同。

請求鎖

根據上面的要求,我定了一個注解@RequestLock,使用方式很簡單,把這個注解打在接口方法上即可。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

/**
 * @description 請求防抖鎖,用于防止前端重復提交導致的錯誤
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RequestLock {
    /**
     * redis鎖前綴
     *
     * @return 默認為空,但不可為空
     */
    String prefix() default "";

    /**
     * redis鎖過期時間
     *
     * @return 默認2秒
     */
    int expire() default 2;

    /**
     * redis鎖過期時間單位
     *
     * @return 默認單位為秒
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * redis  key分隔符
     *
     * @return 分隔符
     */
    String delimiter() default "&";
}

@RequestLock注解定義了幾個基礎的屬性,redis鎖前綴、redis鎖時間、redis鎖時間單位、key分隔符。其中前面三個參數比較好理解,都是一個鎖的基本信息。key分隔符是用來將多個參數合并在一起的,比如userName是張三,userPhone是123456,那么完整的key就是"張三&123456",最后再加上redis鎖前綴,就組成了一個唯一key。

唯一key生成

這里有些同學可能就要說了,直接拿參數來生成key不就行了嗎?額,不是不行,但我想問一個問題:如果這個接口是文章發布的接口,你也打算把內容當做key嗎?要知道,Redis的效率跟key的大小息息相關。所以,我的建議是選取合適的字段作為key就行了,沒必要全都加上。

要做到參數可選,那么用注解的方式最好了

import java.lang.annotation.*;

/**
 * @description 加上這個注解可以將參數設置為key
 */
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RequestKeyParam {

}

這個注解加到參數上就行,沒有多余的屬性。

接下來就是lockKey的生成了。

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

public class RequestKeyGenerator {
    /**
     * 獲取LockKey
     *
     * @param joinPoint 切入點
     * @return
     */
    public static String getLockKey(ProceedingJoinPoint joinPoint) {
        //獲取連接點的方法簽名對象
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        //Method對象
        Method method = methodSignature.getMethod();
        //獲取Method對象上的注解對象
        RequestLock requestLock = method.getAnnotation(RequestLock.class);
        //獲取方法參數
        final Object[] args = joinPoint.getArgs();
        //獲取Method對象上所有的注解
        final Parameter[] parameters = method.getParameters();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parameters.length; i++) {
            final RequestKeyParam keyParam = parameters[i].getAnnotation(RequestKeyParam.class);
            //如果屬性不是RequestKeyParam注解,則不處理
            if (keyParam == null) {
                continue;
            }
            //如果屬性是RequestKeyParam注解,則拼接 連接符 "& + RequestKeyParam"
            sb.append(requestLock.delimiter()).append(args[i]);
        }
        //如果方法上沒有加RequestKeyParam注解
        if (StringUtils.isEmpty(sb.toString())) {
            //獲取方法上的多個注解(為什么是兩層數組:因為第二層數組是只有一個元素的數組)
            final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            //循環注解
            for (int i = 0; i < parameterAnnotations.length; i++) {
                final Object object = args[i];
                //獲取注解類中所有的屬性字段
                final Field[] fields = object.getClass().getDeclaredFields();
                for (Field field : fields) {
                    //判斷字段上是否有RequestKeyParam注解
                    final RequestKeyParam annotation = field.getAnnotation(RequestKeyParam.class);
                    //如果沒有,跳過
                    if (annotation == null) {
                        continue;
                    }
                    //如果有,設置Accessible為true(為true時可以使用反射訪問私有變量,否則不能訪問私有變量)
                    field.setAccessible(true);
                    //如果屬性是RequestKeyParam注解,則拼接 連接符" & + RequestKeyParam"
                    sb.append(requestLock.delimiter()).append(ReflectionUtils.getField(field, object));
                }
            }
        }
        //返回指定前綴的key
        return requestLock.prefix() + sb;
    }
}
> 由于``@RequestKeyParam``可以放在方法的參數上,也可以放在對象的屬性上,所以這里需要進行兩次判斷,一次是獲取方法上的注解,一次是獲取對象里面屬性上的注解。

重復提交判斷

Redis緩存方式

import java.lang.reflect.Method;
import com.summo.demo.exception.biz.BizException;
import com.summo.demo.model.response.ResponseCodeEnum;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.util.StringUtils;

/**
 * @description 緩存實現
 */
@Aspect
@Configuration
@Order(2)
public class RedisRequestLockAspect {

    private final StringRedisTemplate stringRedisTemplate;

    @Autowired
    public RedisRequestLockAspect(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Around("execution(public * * (..)) && @annotation(com.summo.demo.config.requestlock.RequestLock)")
    public Object interceptor(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        RequestLock requestLock = method.getAnnotation(RequestLock.class);
        if (StringUtils.isEmpty(requestLock.prefix())) {
            throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "重復提交前綴不能為空");
        }
        //獲取自定義key
        final String lockKey = RequestKeyGenerator.getLockKey(joinPoint);
        // 使用RedisCallback接口執行set命令,設置鎖鍵;設置額外選項:過期時間和SET_IF_ABSENT選項
        final Boolean success = stringRedisTemplate.execute(
            (RedisCallback<Boolean>)connection -> connection.set(lockKey.getBytes(), new byte[0],
                Expiration.from(requestLock.expire(), requestLock.timeUnit()),
                RedisStringCommands.SetOption.SET_IF_ABSENT));
        if (!success) {
            throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "您的操作太快了,請稍后重試");
        }
        try {
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "系統異常");
        }
    }
}

這里的核心代碼是stringRedisTemplate.execute里面的內容,正如注釋里面說的“使用RedisCallback接口執行set命令,設置鎖鍵;設置額外選項:過期時間和SET_IF_ABSENT選項”,有些同學可能不太清楚SET_IF_ABSENT是個啥,這里我解釋一下:SET_IF_ABSENT是 RedisStringCommands.SetOption 枚舉類中的一個選項,用于在執行 SET 命令時設置鍵值對的時候,如果鍵不存在則進行設置,如果鍵已經存在,則不進行設置。

Redisson分布式方式

Redisson分布式需要一個額外依賴,引入方式

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.10.6</version>
</dependency>

由于我之前的代碼有一個RedisConfig,引入Redisson之后也需要單獨配置一下,不然會沖突

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        // 這里假設你使用單節點的Redis服務器
        config.useSingleServer()
            // 使用與Spring Data Redis相同的地址
            .setAddress("redis://127.0.0.1:6379");
        // 如果有密碼
        //.setPassword("xxxx");
        // 其他配置參數
        //.setDatabase(0)
        //.setConnectionPoolSize(10)
        //.setConnectionMinimumIdleSize(2);
        // 創建RedissonClient實例
        return Redisson.create(config);
    }
}

配好之后,核心代碼如下:

import java.lang.reflect.Method;

import com.summo.demo.exception.biz.BizException;
import com.summo.demo.model.response.ResponseCodeEnum;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;

/**
 * @description 分布式鎖實現
 */
@Aspect
@Configuration
@Order(2)
public class RedissonRequestLockAspect {
    private RedissonClient redissonClient;

    @Autowired
    public RedissonRequestLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Around("execution(public * * (..)) && @annotation(com.summo.demo.config.requestlock.RequestLock)")
    public Object interceptor(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        RequestLock requestLock = method.getAnnotation(RequestLock.class);
        if (StringUtils.isEmpty(requestLock.prefix())) {
            throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "重復提交前綴不能為空");
        }
        //獲取自定義key
        final String lockKey = RequestKeyGenerator.getLockKey(joinPoint);
        // 使用Redisson分布式鎖的方式判斷是否重復提交
        RLock lock = redissonClient.getLock(lockKey);
        boolean isLocked = false;
        try {
            //嘗試搶占鎖
            isLocked = lock.tryLock();
            //沒有拿到鎖說明已經有了請求了
            if (!isLocked) {
                throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "您的操作太快了,請稍后重試");
            }
            //拿到鎖后設置過期時間
            lock.lock(requestLock.expire(), requestLock.timeUnit());
            try {
                return joinPoint.proceed();
            } catch (Throwable throwable) {
                throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "系統異常");
            }
        } catch (Exception e) {
            throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "您的操作太快了,請稍后重試");
        } finally {
            //釋放鎖
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

    }
}

Redisson的核心思路就是搶鎖,當一次請求搶到鎖之后,對鎖加一個過期時間,在這個時間段內重復的請求是無法獲得這個鎖,也不難理解。

測試一下。

  • 第一次提交,"添加用戶成功"

圖片圖片

  • 短時間內重復提交,"BIZ-0001:您的操作太快了,請稍后重試"

圖片圖片

  • 過幾秒后再次提交,"添加用戶成功"

圖片圖片

從測試的結果上看,防抖是做到了,但是隨著緩存消失、鎖失效,還是可以發起同樣的請求,所以要真正做到接口冪等性,還需要業務代碼的判斷、設置數據庫表的UK索引等操作。我在文章里面說到生成唯一key的時候沒有加用戶相關的信息,比如用戶ID、IP屬地等,真實生產環境建議加上這些,可以更好地減少誤判。

責任編輯:武曉燕 來源: 一安未來
相關推薦

2025-02-21 12:00:00

SpringBoot防重復提交緩存機制

2024-05-28 09:26:46

2013-11-13 11:01:14

表單表單重復提交表單策略

2013-11-13 14:39:53

表單提交開發

2025-04-15 08:40:00

數據庫悲觀鎖樂觀鎖

2025-02-28 13:00:00

SpringBoot接口接口安全

2022-12-23 08:28:42

策略模式算法

2025-06-19 09:53:30

Spring性能優化服務器

2025-05-28 05:10:00

策略Spring性能

2024-09-27 08:25:47

2025-07-28 04:00:00

2025-11-19 07:59:57

2021-04-26 08:54:17

Spring BootSecurity防重登錄

2025-06-23 08:55:00

架構Vue重復提交

2024-12-03 10:59:36

2022-05-25 09:00:00

令牌JWT安全

2025-05-27 03:33:00

Spring高并發接口

2022-10-11 14:58:00

性能優化Java

2020-02-26 15:35:17

Spring Boot項目優化JVM調優

2024-09-09 05:30:00

數據庫Spring
點贊
收藏

51CTO技術棧公眾號

中文字幕一区二区人妻电影| 久操网在线观看| 久草视频免费在线| 成人在线免费| 9久草视频在线视频精品| 久久精品视频在线观看| mm1313亚洲国产精品无码试看| 亚洲精品免费在线观看视频| 久久国产精品成人免费观看的软件| 黄网动漫久久久| dy888夜精品国产专区| 日本黄色激情视频| 日韩三区在线| 久久免费精品国产久精品久久久久| 欧美精品一二区| 亚洲天堂av一区二区| 国产一区二区三区福利| 亚洲日本视频| 亚洲福利视频网| 欧美交换配乱吟粗大25p| 国产情侣激情自拍| 国产精品久久久久久| 欧美日韩综合一区| 久久久av电影| 蜜臀久久99精品久久久酒店新书 | 亚洲色图14p| 黄网av在线| 丰满放荡岳乱妇91ww| 久久91精品国产91久久跳| 免费黄频在线观看| 中文字幕伦理免费在线视频 | 51午夜精品国产| 自拍偷拍亚洲色图欧美| 一本久道久久综合无码中文| 日韩免费av| 欧美精品一级二级| 美女黄色片网站| 精品人妻伦一区二区三区久久| 伊人情人综合网| 日韩精品一区二区在线| 天堂8在线天堂资源bt| 蜜臀av午夜精品| 在线一区欧美| 一区二区三区四区精品| av中文字幕网址| 婷婷在线播放| 成人aa视频在线观看| 欧美一级黄色网| 一级特黄曰皮片视频| 亚洲福利影视| 亚洲一区二区四区蜜桃| 久久精品二区| 在线观看国产精品视频| 欧美精品二区| 亚洲国产精品一区二区三区| 男女曰b免费视频| 欧美激情午夜| k8久久久一区二区三区| 国产成人福利网站| 中国一级片在线观看| 第一区第二区在线| 色国产综合视频| 公共露出暴露狂另类av| 亚洲精品无遮挡| 久久综合五月| 欧美麻豆久久久久久中文| 日韩一级视频在线观看| 电影中文字幕一区二区| 精品久久久久久国产91| 亚洲免费久久| 日本高清视频免费观看| 美女mm1313爽爽久久久蜜臀| 久久久久久噜噜噜久久久精品| 久久丫精品国产亚洲av不卡| 伊人亚洲精品| 色综合天天综合网天天狠天天| 中文字幕久久一区| 天堂91在线| 蜜桃在线一区二区三区| 午夜精品久久久久久久久久久久 | 日韩av片专区| 美女搞黄视频在线观看| 中文字幕日韩一区| 欧美深深色噜噜狠狠yyy| 97超碰人人草| 日韩精品一二三| 国语自产精品视频在免费| 少妇人妻好深好紧精品无码| 国产女人18毛片水真多18精品| 日韩欧美亚洲范冰冰与中字| 97中文字幕在线| 日韩大片在线永久免费观看网站| 91视频国产资源| 国产精品18毛片一区二区| 中文字幕一区二区在线视频| 亚洲少妇一区| 久久久久久国产精品美女| 亚洲视频重口味| 精品国内自产拍在线观看视频| 日韩成人中文字幕| 9.1在线观看免费| 国产电影一区二区| 欧美日韩综合在线| 一区二区三区韩国| 成人日韩精品| 色欧美片视频在线观看在线视频| 亚洲中文字幕无码av永久| 丝袜综合欧美| 一区二区在线免费| avove在线观看| 蜜桃视频在线观看免费视频网站www| 国产午夜亚洲精品理论片色戒| 极品尤物一区二区三区| 天天干视频在线| 成人h版在线观看| 国产日韩在线一区二区三区| 亚洲精品一级片| 丁香天五香天堂综合| 国产精品久久精品国产| 亚洲精品综合网| 成人综合在线网站| 国产日韩欧美二区| 欧洲一级在线观看| 国产日韩欧美a| 小说区图片区图片区另类灬| 幼a在线观看| 中文字幕一区在线| 国产精品av免费| 影音先锋男人资源在线| 亚洲国产一区在线观看| 99在线精品免费视频| 在线观看涩涩| 欧美性猛交xxxx黑人交| 亚洲欧美久久久久| 成人豆花视频| 欧美精品一区二区三| 野外性满足hd| 日韩精品看片| 色在人av网站天堂精品| 日韩av无码中文字幕| 久久av最新网址| 国产精品嫩草视频| 国产普通话bbwbbwbbw| 北条麻妃一区二区三区| 蜜桃传媒视频麻豆第一区免费观看| 丁香婷婷在线观看| 亚洲人成网站精品片在线观看| 久久99久久99精品| 波多视频一区| 欧美另类变人与禽xxxxx| 日本中文字幕精品| 一呦二呦三呦国产精品| 久久激情视频免费观看| 久久久久久久久精| 久久综合图片| 97伦理在线四区| 男人天堂资源在线| 亚洲男人的天堂在线aⅴ视频 | 久久久久91| 91色在线视频| 青青视频在线观| 亚洲天堂2016| 91视频最新入口| 国产精品99久久免费| 亚洲精品www| 992在线观看| 国产欧美日韩一区二区三区在线| 国产在线一区二区三区| 亚洲欧美综合在线观看| **欧美大码日韩| 人妻有码中文字幕| 538任你躁精品视频网免费| 国产一区二区精品丝袜| 国产亚洲欧美精品久久久www| 日日夜夜一区二区| 国产精品视频免费一区二区三区| av电影在线网| 欧美视频裸体精品| 岛国大片在线免费观看| 水蜜桃久久夜色精品一区| 91av在线不卡| 精品国产18久久久久久| 亚洲国产精品二十页| 日日碰狠狠添天天爽超碰97| aa亚洲一区一区三区| 亚洲女同精品视频| 精品处破女学生| 黄色资源网久久资源365| 欧美日韩在线一区二区三区| 成人免费观看在线观看| 日韩三级视频中文字幕| 青青草自拍偷拍| 久久久蜜桃一区二区人| 精品乱码一区二区三区| 欧美v亚洲v| 欧美一区日本一区韩国一区| 久久久精品成人| 丝袜脚交一区二区| 免费成人在线观看av| av中文资源在线资源免费观看| 日韩视频免费观看高清完整版| 国产免费美女视频| 麻豆91在线播放| 亚洲欧美日韩精品综合在线观看| **欧美日韩在线观看| 亚洲男人av在线| 一区二区三区福利视频| 91在线国产观看| 国产男女无遮挡| 亚洲人成网站77777在线观看 | 国产91精品在线| 亚洲香蕉成人av网站在线观看 | 大地资源网3页在线观看| 精品视频一区二区不卡| 五月婷婷欧美激情| 麻豆视频观看网址久久| 一区二区三区免费看| 国产剧情一区二区在线观看| 欧美成人免费网| 精品免费久久久| 亚洲成在线观看| 中国极品少妇videossexhd| 99riav1国产精品视频| 蜜桃传媒一区二区| 99久久综合国产精品二区| 视频在线观看99| 这里只有久久精品视频| 国产精品夫妻自拍| 国产九九九视频| 亚洲狠狠婷婷| 欧洲成人一区二区| 日本免费成人| 欧美日韩成人在线视频| 黄色成人一级片| 色综合中文字幕| 蜜桃av.com| 成人免费精品视频| 国产又黄又猛视频| 久久久久久免费视频| 97伦理在线四区| 久久电影tv| 久久久精品视频成人| 天天干视频在线观看| 欧美中文字幕一区二区三区| 成人在线观看小视频| av电影在线观看一区| 亚洲欧美日韩一级| 欧美国产综合| 欧美极品色图| 国产一区二区三区亚洲综合| 91sa在线看| 麻豆网在线观看| 日韩精品一二三四区| 91肉色超薄丝袜脚交一区二区| 亚洲成人一二三| 国产探花视频在线| aaa国产一区| 特级西西444www| 一本久道久久综合狠狠爱| 亚洲在线欧美| 狼人精品一区二区三区在线| 国产九九精品视频| 国产黄大片在线观看| 久久精品亚洲热| 激情视频在线观看免费| 日韩欧美一区二区久久婷婷| www.久久精品视频| 一区二区三区精品| 国产综合精品久久久久成人av| 大桥未久av一区二区三区中文| 欧美一级裸体视频| 99视频在线精品国自产拍免费观看| 亚洲欧美成人一区| 亚洲女娇小黑人粗硬| 成人一区二区在线| 白嫩亚洲一区二区三区| 日产精品99久久久久久| 成人高潮aa毛片免费| 久久亚洲综合国产精品99麻豆精品福利 | 97超碰资源站在线观看| 伊人久久男人天堂| 你懂的视频在线| 亚洲护士老师的毛茸茸最新章节| 国产精品一区二区免费视频| 日本高清成人免费播放| 日本少妇久久久| 一二三四区精品视频| av黄色免费在线观看| 久久精品无码一区二区三区| 手机免费看av片| 国产精品538一区二区在线| 艹b视频在线观看| 日韩电影一二三区| 无码无遮挡又大又爽又黄的视频| 99这里有精品| 黄页免费在线观看视频| 激情欧美一区| 免费超爽大片黄| 激情国产一区| 久久久亚洲国产精品| 极品少妇一区二区三区| 视频一区二区视频| 五月开心六月丁香综合色啪| 一区二区在线中文字幕电影视频| 欧美精品一区二区久久| 日本精品一区二区三区视频| 亚洲区小说区图片区qvod按摩| 久久综合久久久| 亚洲精品合集| 欧洲一区二区日韩在线视频观看免费| 天海翼亚洲一区二区三区| 久久99国产精品| 欧美美女在线观看| 日韩免费av电影| 波多野结衣的一区二区三区| 亚洲欧美日韩国产yyy| 国产精品成人a在线观看| 综合操久久久| 亚洲国产精品久久久天堂| 天天操天天干天天玩| 午夜精品免费| 欧美一区二区激情| 亚洲永久字幕| 黄色av免费在线播放| 奇米精品一区二区三区四区| www.国产视频.com| 国产精品一区二区不卡| 91超薄肉色丝袜交足高跟凉鞋| www.av亚洲| 99国产精品免费| 亚洲乱码国产乱码精品精可以看| 欧美毛片在线观看| 懂色av影视一区二区三区| 懂色av蜜臀av粉嫩av分享吧最新章节| 欧美亚男人的天堂| 国产91视频在线| 精品亚洲一区二区三区四区五区| 国产一级免费在线观看| 日韩中文在线观看| 日本小视频在线免费观看| 5278欧美一区二区三区| 福利一区在线| 成人免费视频观看视频| 九九久久精品| 超碰在线免费观看97| 18成人免费观看视频| 香蕉视频网站入口| 国产盗摄女厕一区二区三区| 亚洲AV无码国产成人久久| 亚洲特黄一级片| 91精品国产综合久久久蜜臀九色| 欧美三区在线观看| 蜜臀av在线观看| www.久久色.com| 午夜激情电影在线播放| 91久久国产精品91久久性色| 欧美成人专区| 国产人妻互换一区二区| 久久婷婷一区| 无码人妻一区二区三区精品视频| 国产调教视频一区| 国产无码精品视频| 欧美二区在线观看| 精品乱码一区二区三四区视频 | 香蕉视频一区二区| 欧美人狂配大交3d怪物一区| 天天操天天插天天射| 免费91在线视频| 国产原创一区| 美国av一区二区三区| 欧美午夜一区| 999这里有精品| 久久久久久免费| 国产精品500部| 日韩一二三四区| 欧美被日视频| 国产精品高潮呻吟久久av野狼 | 精品视频久久久久久| 3d玉蒲团在线观看| 国产日韩精品视频| 国产成人调教视频在线观看| 阿v天堂2018| 国产成人免费视频网站高清观看视频 | 日韩有吗在线观看| 亚洲五月六月| 水蜜桃久久夜色精品一区的特点| xxxwww国产| 亚洲一级二级在线| 99国产精品99| 久久精品国产视频| 欧美日韩国产网站| 欧美一区二区影视| 久久亚洲不卡| 波多野结衣片子| 日韩欧美在线视频免费观看| 丝袜+亚洲+另类+欧美+变态| 国外色69视频在线观看| 精品国产导航| 蜜臀av无码一区二区三区| 高清不卡在线观看av|