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

SpringBoot 接口防抖的一些實現(xiàn)方案

開發(fā)
所謂防抖,一是防用戶手抖,二是防網(wǎng)絡抖動。后端也應實施相應的防抖邏輯,確保在網(wǎng)絡波動的情況下不會接收并處理同一請求多次。

作為一名老碼農(nóng),在開發(fā)后端Java業(yè)務系統(tǒng),包括各種管理后臺和小程序等。在這些項目中,我設計過單/多租戶體系系統(tǒng),對接過許多開放平臺,也搞過消息中心這類較為復雜的應用,但幸運的是,我至今還沒有遇到過線上系統(tǒng)由于代碼崩潰導致資損的情況。

這其中的原因有三點:一是業(yè)務系統(tǒng)本身并不復雜;二是我一直遵循某大廠代碼規(guī)約,在開發(fā)過程中盡可能按規(guī)約編寫代碼;三是經(jīng)過多年的開發(fā)經(jīng)驗積累,我成為了一名熟練工,掌握了一些實用的技巧。

一、啥是防抖

所謂防抖,一是防用戶手抖,二是防網(wǎng)絡抖動。在Web系統(tǒng)中,表單提交是一個非常常見的功能,如果不加控制,容易因為用戶的誤操作或網(wǎng)絡延遲導致同一請求被發(fā)送多次,進而生成重復的數(shù)據(jù)記錄。

要針對用戶的誤操作,前端通常會實現(xiàn)按鈕的loading狀態(tài),阻止用戶進行多次點擊。而對于網(wǎng)絡波動造成的請求重發(fā)問題,僅靠前端是不行的。為此,后端也應實施相應的防抖邏輯,確保在網(wǎng)絡波動的情況下不會接收并處理同一請求多次。

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

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

二、思路解析

前面講了那么多,我們已經(jīng)知道接口的防抖是很有必要的了,但是在開發(fā)之前,我們需要捋清楚幾個問題。

1.哪一類接口需要防抖?

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

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

2.如何確定接口是重復的?

防抖也即防重復提交,那么如何確定兩次接口就是重復的呢?首先,我們需要給這兩次接口的調(diào)用加一個時間間隔,大于這個時間間隔的一定不是重復提交;

其次,兩次請求提交的參數(shù)比對,不一定要全部參數(shù),選擇標識性強的參數(shù)即可;

最后,如果想做的更好一點,還可以加一個請求地址的對比。

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

有兩個方案:

1.使用共享緩存

流程圖如下:

2.使用分布式鎖

流程圖如下:

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

四、具體實現(xiàn)

現(xiàn)在有一個保存用戶的接口:

@PostMapping("/add")
@RequiresPermissions(value = "add")
@Log(methodDesc = "添加用戶")
public ResponseEntity<String> add(@RequestBody AddReq addReq) {
        return userService.add(addReq);
}

AddReq.java:

package com.summo.demo.model.request;
import java.util.List;
import lombok.Data;
@Datapublic class AddReq {
    /**     * 用戶名稱     */    private String userName;
    /**     * 用戶手機號     */    private String userPhone;
    /**     * 角色ID列表     */    private List<Long> roleIdList;
}

目前數(shù)據(jù)庫表中沒有對userPhone字段做UK索引,這就會導致每調(diào)用一次add就會創(chuàng)建一個用戶,即使userPhone相同。

五、請求鎖

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

RequestLock.java:

package com.summo.demo.model.request;

import java.util.List;

import lombok.Data;

@Data
public class AddReq {

    /**
     * 用戶名稱
     */
    private String userName;

    /**
     * 用戶手機號
     */
    private String userPhone;

    /**
     * 角色ID列表
     */
    private List<Long> roleIdList;
}

@RequestLock注解定義了幾個基礎的屬性,redis鎖前綴、redis鎖時間、redis鎖時間單位、key分隔符。其中前面三個參數(shù)比較好理解,都是一個鎖的基本信息。

key分隔符是用來將多個參數(shù)合并在一起的,比如userName是張三,userPhone是123456,那么完整的key就是"張三&123456",最后再加上redis鎖前綴,就組成了一個唯一key。

六、唯一key生成

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

要做到參數(shù)可選,那么用注解的方式最好了,注解如下:

RequestKeyParam.java

package com.example.requestlock.lock.annotation;

import java.lang.annotation.*;

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

}

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

接下來就是lockKey的生成了,代碼如下:

RequestKeyGenerator.java:

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);
        //獲取方法參數(shù)
        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())) {
            //獲取方法上的多個注解(為什么是兩層數(shù)組:因為第二層數(shù)組是只有一個元素的數(shù)組)
            final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            //循環(huán)注解
            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可以放在方法的參數(shù)上,也可以放在對象的屬性上,所以這里需要進行兩次判斷,一次是獲取方法上的注解,一次是獲取對象里面屬性上的注解。

七、重復提交判斷

1.Redis緩存方式

RedisRequestLockAspect.java

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 緩存實現(xiàn)
 */
@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接口執(zhí)行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, "系統(tǒng)異常");
        }
    }
}

這里的核心代碼是stringRedisTemplate.execute里面的內(nèi)容,正如注釋里面說的“使用RedisCallback接口執(zhí)行set命令,設置鎖鍵;設置額外選項:過期時間和SET_IF_ABSENT選項”,有些同學可能不太清楚SET_IF_ABSENT是個啥?

這里我解釋一下:SET_IF_ABSENT是 RedisStringCommands.SetOption 枚舉類中的一個選項,用于在執(zhí)行 SET 命令時設置鍵值對的時候,如果鍵不存在則進行設置,如果鍵已經(jīng)存在,則不進行設置。

2.Redisson分布式方式

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

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

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

RedissonConfig.java:

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();
        // 這里假設你使用單節(jié)點的Redis服務器
        config.useSingleServer()
            // 使用與Spring Data Redis相同的地址
            .setAddress("redis://127.0.0.1:6379");
        // 如果有密碼
        //.setPassword("xxxx");
        // 其他配置參數(shù)
        //.setDatabase(0)
        //.setConnectionPoolSize(10)
        //.setConnectionMinimumIdleSize(2);
        // 創(chuàng)建RedissonClient實例
        return Redisson.create(config);
    }
}

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

RedissonRequestLockAspect.java:

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 分布式鎖實現(xiàn)
 */
@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();
            //沒有拿到鎖說明已經(jīng)有了請求了
            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, "系統(tǒng)異常");
            }
        } catch (Exception e) {
            throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "您的操作太快了,請稍后重試");
        } finally {
            //釋放鎖
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

    }
}

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

測試一下。

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

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

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

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

我在文章里面說到生成唯一key的時候沒有加用戶相關(guān)的信息,比如用戶ID、IP屬地等,真實生產(chǎn)環(huán)境建議加上這些,可以更好地減少誤判。

責任編輯:趙寧寧 來源: 碼猿技術(shù)專欄
相關(guān)推薦

2024-09-13 10:21:50

2024-05-28 09:26:46

2025-07-02 08:00:00

防抖SpringBoot開發(fā)

2025-11-07 08:05:18

2024-06-14 09:30:58

2025-06-09 01:22:00

2022-05-15 22:08:58

ReactHookdebounce

2009-11-25 13:07:53

2021-02-24 15:16:45

微服務架構(gòu)數(shù)據(jù)

2015-08-28 09:29:37

Volley框架

2021-03-30 10:46:42

SpringBoot計數(shù)器漏桶算法

2009-08-06 16:01:30

C#接口成員

2024-01-30 10:11:00

SpringBoot項目開發(fā)

2022-04-02 14:43:59

Promethues監(jiān)控

2023-12-12 10:54:55

MySQL模式InnoDB

2021-09-12 07:33:23

python管理編程

2017-11-03 09:40:27

數(shù)據(jù)庫MySQLMHA

2025-05-09 09:10:00

2013-03-29 13:17:53

XCode調(diào)試技巧iOS開發(fā)

2011-07-13 09:13:56

Android設計
點贊
收藏

51CTO技術(shù)棧公眾號

日韩激情一区| av大片在线| 男人的天堂亚洲在线| 伊人久久大香线蕉av一区二区| 99re精彩视频| 国内高清免费在线视频| www欧美成人18+| 国产精品视频永久免费播放| 国产探花在线播放| 中日韩免视频上线全都免费| 欧美福利一区二区| 国产午夜大地久久| 日韩三级影院| caoporn国产精品| 国产精品久久在线观看| 久久久久久久久久一区二区三区| 欧洲专线二区三区| 日韩欧美的一区| 午夜欧美福利视频| 日本乱理伦在线| 国产欧美日韩精品在线| 国产99在线免费| 一级做a爱片久久毛片| 亚洲欧美成人| 久久99国产精品久久久久久久久| mm131丰满少妇人体欣赏图| 亚洲精品在线国产| 欧美三级日韩三级| 99精品人妻少妇一区二区| 成视频免费观看在线看| 国产性色一区二区| 极品日韩久久| 亚洲乱色熟女一区二区三区| 日韩和欧美一区二区三区| 久久久之久亚州精品露出| 国产喷水在线观看| 精品美女久久| 精品亚洲国产成av人片传媒| 欧美一级大片免费看| 欧洲午夜精品| 欧美色欧美亚洲另类二区| av动漫在线看| 第一av在线| 亚洲男人的天堂在线aⅴ视频| 日本免费高清不卡| 视频污在线观看| 高清久久久久久| 91黄在线观看| 国产三级按摩推拿按摩| 免费在线观看不卡| 国产精品久久久久av免费| 天堂а√在线中文在线新版| 夜夜嗨一区二区三区| 欧美激情手机在线视频| 免费人成年激情视频在线观看| 国产精品久久久久无码av| 色偷偷偷亚洲综合网另类| 日本二区在线观看| 久久av超碰| 亚洲人成在线观看| 欧洲av一区二区三区| 国产99亚洲| 亚洲午夜未删减在线观看 | 日韩精品一区二区不卡| 国内在线观看一区二区三区| 国产69精品久久久久9| xxxxxx国产| 亚洲女人av| 国产精品高潮呻吟久久av野狼| 探花国产精品一区二区| 欧美a级理论片| 国产欧美日韩视频| 国产日韩精品suv| 成人性视频免费网站| 国内不卡一区二区三区| 精品视频一二三| 国产欧美日韩麻豆91| 国产91av视频在线观看| 宅男在线观看免费高清网站| 亚洲一区二区精品3399| 北条麻妃69av| 国产香蕉久久| 日韩欧美亚洲国产精品字幕久久久| 男人添女人荫蒂国产| 精品网站aaa| 亚洲性日韩精品一区二区| 狂野欧美性猛交| 韩日精品视频| 日本欧美在线视频| 亚洲一区二区三区网站| 国产成人精品亚洲777人妖| 国产一区二区三区色淫影院| 好男人免费精品视频| 国产精品伦理在线| av在线免费观看国产| 亚洲插插视频| 欧美福利一区二区| 亚洲第一黄色网址| 久久国产电影| 久久久久女教师免费一区| 日韩精品一区二区亚洲av| 美国一区二区三区在线播放 | 99免费视频观看| 亚洲精品777| 日韩精品极品视频免费观看| 成人黄色短视频| 亚洲三级影院| 成人欧美一区二区三区在线湿哒哒| 色婷婷综合视频| 国产精品不卡视频| 久久婷婷国产精品| 日韩精品亚洲专区在线观看| 亚洲欧洲美洲在线综合| 久久艹精品视频| 日韩和欧美一区二区| 国产乱码精品一区二区三区卡 | 欧美精品日韩三级| 成年人视频免费| 高清不卡在线观看av| 亚洲欧洲精品一区| 欧美片第一页| 亚洲国产中文字幕在线观看| 五月天av网站| 喷水一区二区三区| 欧美久久久久久一卡四| 51精品视频| 日韩午夜小视频| 极品美妇后花庭翘臀娇吟小说| 久久aⅴ乱码一区二区三区| 97超碰人人模人人爽人人看| 免费网站成人| 色系网站成人免费| 国产在线观看无码免费视频| 国语自产精品视频在线看8查询8| 亚洲精品免费网站| 91美女视频在线| 日本乱码高清不卡字幕| 亚洲精品视频大全| 99综合精品| 成人免费视频观看视频| 成人直播在线| 欧美一区二区二区| 很污很黄的网站| 蜜臀av亚洲一区中文字幕| 日韩欧美亚洲日产国产| 精品3atv在线视频| 亚洲精品视频播放| 91九色丨porny丨肉丝| 99天天综合性| 怡红院av亚洲一区二区三区h| 大奶一区二区三区| 久久久久久亚洲精品中文字幕| 亚洲第一免费视频| 亚洲一二三四区| 国产白袜脚足j棉袜在线观看| 欧美日韩hd| 成人做爰66片免费看网站| 日本一级理论片在线大全| 日韩一卡二卡三卡国产欧美| 深夜福利影院在线观看| 成人午夜碰碰视频| 黄色国产一级视频| 亚洲综合小说图片| 国产成人精品在线| caoporn国产精品免费视频| 精品视频色一区| 亚洲精品一区二区三区在线播放| 国产一区二区成人久久免费影院| 无码毛片aaa在线| 亚洲超碰在线观看| 羞羞色国产精品| 麻豆app在线观看| 欧美日韩在线亚洲一区蜜芽| 97在线观看视频免费| 国产成人h网站| 欧美老熟妇喷水| 凹凸成人精品亚洲精品密奴| 91精品啪aⅴ在线观看国产| 最新日本在线观看| 亚洲精品福利在线观看| 精品久久久久久久久久久国产字幕 | 色视频在线观看| 在线观看av一区二区| 亚洲xxxx3d动漫| 9色porny自拍视频一区二区| 国产视频在线视频| 中文不卡在线| 久久99九九| 日本国产亚洲| 91禁国产网站| 日本暖暖在线视频| 精品欧美一区二区久久| 精品无码一区二区三区的天堂| 亚洲视频免费在线| 黄色短视频在线观看| 精品一区二区免费| 毛片在线播放视频| 日韩成人免费| 国模一区二区三区私拍视频| 国产激情欧美| 91av在线不卡| yellow91字幕网在线| 亚洲乱码一区二区| 精品国产一级片| 色老头久久综合| 久久国产露脸精品国产| 国产精品污网站| 99re久久精品国产| 激情av综合网| 成人在线观看a| 亚洲一本视频| 做爰高潮hd色即是空| 蜜臀91精品国产高清在线观看| 91在线免费观看网站| 都市激情亚洲综合| 国内揄拍国内精品| 麻豆电影在线播放| 国产亚洲精品一区二区| 午夜国产在线视频| 日韩欧美国产综合| 国产又大又黑又粗| 在线亚洲免费视频| 91国产精品电影| 欧美熟女一区二区| 337p亚洲精品色噜噜噜| 五月婷婷激情五月| 精品国产乱码久久久久酒店| 国产大学生自拍| 国产精品高潮久久久久无| 91网站免费入口| 91丝袜美腿高跟国产极品老师| 潘金莲一级淫片aaaaa| 国产一区二区三区免费观看| 免费看污黄网站| 美女视频一区免费观看| 天天夜碰日日摸日日澡性色av| 欧美视频福利| 337p亚洲精品色噜噜狠狠p| 久久精品免费一区二区三区| 视频一区二区三区免费观看| 免费黄色成人| 欧美另类网站| 欧美人妖在线| 神马影院我不卡| 欧美亚洲国产激情| 色噜噜一区二区| 欧美精品一二| 日韩在线三区| 第四色成人网| 中文字幕一区二区三区四区五区 | 日韩精品专区| 日本欧美爱爱爱| 高清av一区二区三区| 国产精品美女久久久久久免费| 国产在线|日韩| 国产精品久久久久久久久久ktv | 在线观看日韩电影| 天天干天天操天天操| 日本高清无吗v一区| 天天综合久久综合| 欧美日韩在线一区二区| 国产精品丝袜黑色高跟鞋| 欧美一区2区视频在线观看| www.超碰在线.com| 亚洲国产另类 国产精品国产免费| 国产91免费看| 国产视频精品一区二区三区| 国产毛片av在线| www.亚洲男人天堂| 18+视频在线观看| 久久久久久中文字幕| 中文字幕在线看片| 国产精品久久久亚洲| www999久久| 国产另类第一区| 激情综合网站| 亚洲三区在线| 欧美日韩日本国产亚洲在线| 日本xxxxxxxxxx75| 老司机免费视频久久| 亚洲天堂国产视频| 丁香激情综合国产| 懂色av粉嫩av蜜乳av| 亚洲国产精品传媒在线观看| 国产精品视频一区二区三| 香蕉影视欧美成人| 中文字幕在线观看第二页| 日韩欧美国产综合| 国产美女视频一区二区三区| 久久天天躁狠狠躁夜夜爽蜜月| 18video性欧美19sex高清| 国产精品美女久久久久久免费| 亚洲图色一区二区三区| 免费看污久久久| 66久久国产| 欧美精品一区免费| 国产一区二区导航在线播放| 深爱五月激情网| 亚洲精品中文在线| www.国产一区二区| 日韩三级电影网址| 日韩国产福利| 久久精品青青大伊人av| 深夜在线视频| 91gao视频| 精品国产午夜| 免费人成自慰网站| 理论电影国产精品| 国产精品九九九九九| 一区二区不卡在线播放| 中文字幕网址在线| 日韩av在线免费观看一区| 97超碰资源站在线观看| 国产成人jvid在线播放| 97超碰成人| 中文字幕黄色大片| 国产精品国产对白熟妇| 国产一区二区女| 蜜桃av免费观看| 色婷婷综合中文久久一本| 亚洲精品18p| 久久久精品久久| 国产一区二区三区朝在线观看| 国产一区精品在线| 国产精品v亚洲精品v日韩精品| 污网站免费在线| 久久精品视频网| 在线观看中文字幕视频| 精品99久久久久久| 日本理论片午伦夜理片在线观看| 国产区精品在线观看| 成人黄色av| 丁香婷婷激情网| 国产亚洲污的网站| 日本中文字幕第一页| 亚洲精品99久久久久| 福利写真视频网站在线| 不卡视频一区二区三区| 综合久久十次| 婷婷激情综合五月天| 亚洲色图另类专区| 国产精品毛片一区视频播| 北条麻妃久久精品| 欧美a视频在线| 一区二区三区四区欧美| 蜜臀91精品一区二区三区| 山东少妇露脸刺激对白在线| 91久久精品国产91性色tv | 91麻豆国产香蕉久久精品| 日韩精品一区二区三| 亚洲精品电影在线| 欧美gv在线| 欧美一区二区三区在线播放| 久久国产毛片| 精品成人av一区二区三区| 一本色道久久综合亚洲aⅴ蜜桃| 欧美黄色小说| 国产精品旅馆在线| 希岛爱理一区二区三区| 一级黄色在线播放| 一区二区三区蜜桃| 人妻少妇精品无码专区| 欧美在线视频免费| 欧美精品尤物在线观看| 中文字幕 日韩 欧美| 成人免费在线观看入口| 国产熟女一区二区三区五月婷| 欧美美女15p| 欧美深夜视频| 午夜剧场成人观在线视频免费观看| 极品美乳网红视频免费在线观看 | 日本免费成人| 国产卡一卡二在线| 成人黄色小视频在线观看| 中文字幕视频网站| 中文字幕欧美专区| 日韩精品一区国产| 免费看一级大黄情大片| 欧美激情自拍偷拍| 精品二区在线观看| 欧美最猛性xxxxx免费| 色喇叭免费久久综合网| 成年人性生活视频| 欧美日韩一区二区三区在线免费观看| 浮生影视网在线观看免费| 91久久久久久久久久| 亚洲三级国产| 天天色天天综合| 欧美精品一区二区高清在线观看| 爱情电影社保片一区| 日韩 欧美 自拍| 99精品1区2区| 国产永久免费视频| 欧美肥臀大乳一区二区免费视频| 五月国产精品| 肉色超薄丝袜脚交| 日韩欧美国产网站| 日本一本在线免费福利| 色姑娘综合av| 99re这里只有精品首页|