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

Guava騷操作,十分鐘搞定日志脫敏需求!

開發 前端
Gauva確實是一個保藏庫,當你要造輪子的時候不妨先看看Gauva中有沒有現成的輪子。每看一次,也許就多一次驚喜。這一次是Map對象脫敏場景遇上了Guava的 Maps#transformEntries(Map<K,V1>, .Maps.EntryTransformer<? super K,? super V1,V2>) ,那么你又有什么場景偶遇了Guava呢?

Guava之于Javaer,如同Excel之于辦公達人。

都非常好用,但實際上大部分人只用到了其1%不到」的功能。

日志脫敏到底是個啥

敏感信息脫敏」實際上是隸屬于安全領域」的一個子領域,而日志脫敏」又是敏感信息脫敏」的一個子領域。

好了,打住,不閑聊這些有的沒的,直接開整:到底什么是日志脫敏?

未脫敏之前

如下有一個關于個人信息的類

public class Person {
   private Long id;
   private String name;
   private String phone;
   private String account;
   // setter and gettr ...
}

在日志脫敏之前,我們一般會這樣直接打印日志

log.info("個人信息:{}",JsonUtils.toJSONString(person));

然后打印完之后,日志大概是這樣

個人信息:{"id":1,"name":"張無忌","phone":"17709141590","account":"14669037943256249"}

那如果是這種敏感信息打印到日志中的話,安全問題是非常大的。研發人員或者其他可以訪問這些敏感日志的人就可能會故意或者不小心泄露用戶的個人信息,甚至干些啥壞事。

壞事

脫敏后

那日志脫敏最后要達到什么效果呢?

如下:需要把敏感字段中的一部分字符使用特殊符號替換掉(這里我們用*來做特殊符號)

個人信息:{"id":1,"name":"**忌","phone":"177******90","account":"146*********49"}

所以,很自然的,我們就寫了個脫敏組件」,在每一個字段上用注解」來標識每一個字段是什么類型的敏感字段,需要怎么脫敏。

比如,對于上面的個人信息,在打印日志的時候需要研發人員做兩件事:

使用脫敏組件提供的注解來標識敏感字段」

public class Person {
   // id是非敏感字段,不需要脫敏
   private Long id;
   
   @Sensitive(type = SensitiveType.Name)
   private String name;
   
   @Sensitive(type = SensitiveType.Phone)
   private String phone;
   
   @Sensitive(type = SensitiveType.Account)
   private String account;
   // setter and gettr ...
}

使用脫敏組件先脫敏,再打印日志」

如下,先使用脫敏組件提供的工具類脫敏個人信息,然后再打印日志

log.info("個人信息:{}", DataMask.toJSONString(person));

具體的使用和實現原理可以參考:

唯品會脫敏說明:https://github.sheincorp.cn/vipshop/vjtools/blob/master/vjkit/docs/data_masking.md。

日志脫敏遇到了什么問題

到這,還只是一般脫敏組件提供的功能范疇。也就是說,到這你基本上都可以在github上搜索到一些現成的解決方案。

但是到了企業里面,就不是說到這就已經結束了。

到了企業里面,你得滿足客戶(也就是研發人員)奇奇怪怪(也許只是一開始你覺得」是奇奇怪怪,但是實際上很合理)的需求。關注公眾號:碼猿技術專欄,回復關鍵詞:1111 獲取阿里內部Java性能調優實戰!

比如,我們就有研發人員提出:需要按照Map中的Key來配置脫敏規則」

啥意思呢?簡單來說,如果我有一個Map類型的數據,如下

Map<String,Object> personMap = new HashMap<>();  
personMap.put("name","張無忌");  
personMap.put("phone","17709141590");  
personMap.put("account","14669037943256249");  
personMap.put("id",1L);

那么在配置文件中指定好對應的key的脫敏規則后就可以把Map中的敏感數據也脫敏。

大概配置文件如下:

#指定Map中的key為name,name,account的value的脫敏規則分別是Name,Account,Phone
Name=name
Account=account
Phnotallow=phone

那先不管需求是否合理吧,反正客戶就是上帝,滿足再說。

然后,我們就開始實現了。

基本思路:復制Map」,然后遍歷復制后的Map,找到Key有對應脫敏規則的value,按照脫敏規則脫敏,最后使用Json框架序列化脫敏后的Map。

public class DataMask{
  // other method...
 /**
  * 將需要脫敏的字段先進行脫敏操作,最后轉成json格式
  * @param object 需要序列化的對象
  * @return 脫敏后的json格式
  */
 public static String toJSONString(Object object) {
  if (object == null) {
   return null;
  }
  try {

   // 脫敏map類型
   if (object instanceof Map) {
        return return maskMap(object);
   }

   // 其他類型
   return JsonUtil.toJSONString(object);
  } catch (Exception e) {
   return String.valueOf(object);
  }
 }
        
        private static String maskMap(Object object) {
  Map map = (Map) object;
  MaskJsonStr maskJsonStr = new MaskJsonStr();
  // 復制Map
  HashMap<String, Object> mapClone = new HashMap<>();
  mapClone.putAll(map);
  Map mask = maskJsonStr.maskMapValue(mapClone);
  return JsonUtil.getObjectMapper().writeValueAsString(mask);
 }
}

public class MaskJsonStr{
  // other method...
    public Map<String, Object> maskMapValue(Map<String, Object> map) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            Object val = entry.getValue();
            if (val instanceof Map) {
                maskMapValue((Map<String, Object>) val);
            } else if (val instanceof Collection) {
                Collection collVal = maskCollection(entry.getKey(), val);
                map.put(entry.getKey(), collVal);
            } else {
                // 根據key從脫敏規則中獲取脫敏規則,然后脫敏
                Object maskVal = maskString(entry.getKey(), val);
                if (maskVal != null) {
                    map.put(entry.getKey(), maskVal);
                } else {
                    map.put(entry.getKey(), val);
                }
            }
        }

        return map;
    }
}

可以說,在整體思路上,沒啥毛病」,但是往往魔鬼就在細節中」。

看到這,也許有些大神,直接從代碼中已經看出問題了。不急,我們還是悠著點來,給你10分鐘思量一下先。

1」分鐘

2」分鐘

「n」分鐘

好的,我知道的,你肯定是不會思考的。

我們直接看問題。有使用我們這個組件的研發人員找過說:我c a o,你們把我的業務對象「Map中的值修改掉了」。

我們本來想直接回懟:你們是不是姿勢不對,不會用啊。但是本著嚴(zhi)謹(qian)的(bei)原(da)則(nian)(guo),還是問了句,你在本地復現了嗎?

結果,還真被打臉了。人家既有截圖,又給我們發了可復現的代碼。真是打臉打到家了。

那到底是啥問題呢?按道理,我們肯定是測試過的,正常情況下不會有問題。那到底是什么場景下有問題呢?

我們發現:有嵌套類型的Map的時候就會有問題」

測試程序如下:

@Test  
public void testToJSONString() {  
    Map<String,Object> personMap = new HashMap<>();  
    personMap.put("name","張無忌");  
    personMap.put("phone","17709141590");  
    personMap.put("account","14669037943256249");  
    personMap.put("id",1L);  

    Map<String,Object> innerMap = new HashMap();  
    innerMap.put("name","張無忌的女兒");  
    innerMap.put("phone","18809141567");  
    innerMap.put("account","17869037943255678");  
    innerMap.put("id",2L);  
    personMap.put("daughter",innerMap);  

    System.out.println("脫敏后:"+DataMask.toJSONString(personMap));  
    System.out.println("脫敏后的原始Map對象:"+personMap);
}

輸出結果如下:

脫敏后:{"name":"**忌","id":1,"phone":"177*****590","daughter":{"phone":"188*****567","name":"****女兒","id":2,"account":"1***************8"},"account":"1***************9"}
脫敏后的原始Map對象:{phnotallow=17709141590, name=張無忌, id=1, daughter={phnotallow=188*****567, name=****女兒, id=2, account=1***************8}, account=14669037943256249}

我們發現,脫敏時是成功的,但是卻把原始對象中的內嵌innerMap對象中的值修改了」。

震驚

要知道,作為脫敏組件,你可以有點小bug,你也可以原始簡單粗暴,甚至你都可以脫敏失敗」(本該脫敏的卻沒有脫敏),但是你千萬不能修改業務中使用的對象」啊。

雖然問題很大,但是我們還是要沉著應對問題,仔細分析問題。做到泰山崩于前而色不變,才是打工人的正確處事方式。不然只會越急越慌,越慌越不冷靜,最后買1送3」(修1個bug,新引入3個bug)

簡單debug,加上看源代碼,其實這個問題還是比較容易發現的,主要問題就是在復制Map對象的姿勢不對」

如下,我們是使用這樣的方式來復制Map的。本來是想做深度clone」的,但是這種事做不到深度clone的。對于有內嵌的對象的時候只能做到淺clone」。

// 復制Map
HashMap<String, Object> mapClone = new HashMap<>();
mapClone.putAll(map);

所以,只有一層關系的簡單Map是可以脫敏成功的,且不會改變原來的Map。但是對于有嵌套的Map對象時,就會修改嵌套Map對象中的值了。

問題的原因是啥,如何解決,思路是啥

從上面的分析中就可以得出其根本原因:沒有正確地深度clone Map對象

那很自然地,我們的解決思路就是找到一種合適的深度 clone Map對象」的方式就OK了。

然后我就問ChatGPT了,ChatGPT的回答有下面幾個方法

  1. 使用序列化和反序列化」:通過將對象序列化為字節流,然后再將字節流反序列化為新的對象,可以實現深度克隆。需要注意被克隆的對象及其引用類型成員變量都需要實現Serializable接口。
  2. 使用第三方庫」:除了上述兩種方式,還可以使用一些第三方庫,例如Apache Commons的SerializationUtils類、Google的Gson庫等,它們提供了更簡潔的方法來實現深度克隆。
  3. 使用JSON序列化和反序列化」:將對象轉換為JSON字符串,然后再將JSON字符串轉換為新的對象。需要使用JSON庫,如Jackson、Gson等。
  4. 使用Apache Commons的BeanUtils類」:BeanUtils提供了一個cloneBean()方法,可以對JavaBean進行深度克隆。需要注意,被克隆的對象及其引用類型成員變量都需要實現Serializable接口。
  5. 最佳實踐是根據需求和具體情況靈活應用」,或者采用第三方庫實現對象克隆,如 Apache Commons BeanUtils、Spring BeanUtils 等。

上面幾個方式基本上可以分為3類:

  1. 序列化和反序列化」:JDK自帶的序列化(需要實現Serializable接口);利用Gson,FastJson,Jackson等JSON序列化工具序列化后再反序列化;其他序列化框架序(如Hessain等)列化反序列化
  2. 利用第三方庫」:第三方庫直接clone對象。但都有一定的限定條件
  3. 視情況而定」:基本上沒有一種通用的方法,可以適配是有的深度clone場景。所以ChatGPT提出了這一種不是辦法的辦法

根據上面的一番探索,發現還得自己敲代碼造輪子。

那其實你仔細分析后發現:我們確實不需要通用的深度clone對象的能力。我們只需要把Map類型深度clone好就行,對于其他自定義DTO,我們是不需要深度clone的。

然后就是卡卡一頓猛敲,如下:常規遞歸操作

public class MapUtil {  
    private MapUtil() {  

    }  

    public static <K, V> Map<K, V> clone(Map<K, V> map) {  
        if (map == null || map.isEmpty()) {  
            return map;  
        }  
        Map cloneMap = new HashMap();  
        for (Map.Entry<K, V> entry : map.entrySet()) {  
            final V value = entry.getValue();  
            final K key = entry.getKey();  
            if (value instanceof Map) {  
                Map mapValue = (Map) value;  
                cloneMap.put(key, clone(mapValue));  
            } else if (value instanceof Collection) {  
                Collection collectionValue = (Collection) value;  
                cloneMap.put(key, clone(collectionValue));  
            } else {  
                cloneMap.put(key, value);  
            }  
        }  
        return cloneMap;  
    }  

    public static <E> Collection<E> clone(Collection<E> collection) {  
        if (collection == null || collection.isEmpty()) {  
            return collection;  
        }  
        Collection clonedCollection;  
        try {  
            // 有一定的風險會反射調用失敗  
            clonedCollection = collection.getClass().newInstance();  
        } catch (InstantiationException | IllegalAccessException e) {  
            // simply deal with reflect exception  
            throw new RuntimeException(e);  
        }  

        for (E e : collection) {  
            if (e instanceof Collection) {  
                Collection collectionE = (Collection) e;  
                clonedCollection.add(clone(collectionE));  
            } else if (e instanceof Map) {  
                Map mapE = (Map) e;  
                clonedCollection.add(clone(mapE));  
            } else {  
                clonedCollection.add(e);  
            }  
        }  
        return clonedCollection;  
    }  
}

然后,又是一波Junit操作,嘎嘎綠燈,收拾完事。

貌似,到這我們就可以下班了。

但是,等等,這篇文章貌似,好像,的確,應該缺點啥吧?

這尼瑪不是講的是Guava嗎,到這為止,貌似跟Guava毛關系沒有啊!!!

那我們繼續,容你再思考一下,到現在為止,我們的深度clone方案有什么問題。

好的,我懂的。咱看到這,圖的就是一個樂,你讓我思考,這不是強人所難嗎

思考,思考是不可能存在的。

那咱們就直接看問題:就是啥都好,問題就是性能比較差!!!」

如果你是一個老司機,你可能看到上面的代碼就已經感覺到了,性能是相對比較低的。

基本上,Map層級越深,字段越多,內嵌的集合對象元素越多,性能越差!!!

至于差到什么程度,其實是可以通過 微基準測試框架「JMH」來實際測試一下就知道了。這里我就不測試了。

那我們還有什么辦法來解決這個性能問題嗎?

如果我們還是集中于深度clone對象上做文章,去找尋性能更高的深度clone框架或者是類庫的話,那么其實這個問題就已經走偏」了。

我們再來回顧一下我們之前的問題:

我們需要對Map對象按照key來脫敏,所以我們選擇了深度clone Map,然后對Map遍歷按照key脫敏后序列化。

那其實我們最終要解決的問題是:Map對象序列化后的字符串得是按照key的脫敏規則脫敏后的字符串。

所以,其實我們除了深度clone這一條路外,還有另外兩條路:

  • 1.自定義Map類型的序列化器」:在序列化Map的時候,如果有脫敏規則則應用脫敏規則來序列化Map
  • 2.轉換Map為脫敏后的Map」:自定義Map對象,將脫敏規則作為轉換函數來把普通的Map轉換為脫敏的Map

對于第一種方式,要看你使用的Json框架是否支持(一般Map類型用的都是內置的Map序列化器,不一定可以自定義)。

那第二種方式,到底應該如何玩呢. 如下:

// 脫敏轉換函數
public interface MaskFunction<K, V, E> {
    /**
     * @param k key
     * @param v value
     * @return 根據key和value得到脫敏(如果有需要的話)后的值
     */
    E mask(K k, V v);
}
// 自定義MaskMap對象,經過MaskFunction函數,將普通Map轉換為脫敏后的Map
public class MaskMap extends HashMap {

    private MaskFunction maskFunction;

    public MaskMap(MaskFunction maskFunction) {
        this.maskFunction = maskFunction;
    }

    @Override
    public Object get(Object key) {
        Object value = super.get(key);
        if (value == null) {
            return null;
        }
        return maskFunction.mask(key, value);
    }
    // other function to  override ...
}

如上,Map不再是「clone」的玩法,而是轉換」的玩法,所以,這種操作是非常輕量級」的。

但是這種玩法也有缺點:比較麻煩,需要override好多方法(不然就需要熟讀Map序列化器來找到最小化需要override的方法,并且不太靠譜),并且全部都要是轉換」的玩法。

終于,終于,終于,這個時候該輪到我們「Guava」登場了。

輪到我了

Guava登場

用過Guava的人都知道,Guava中很多好用的方法,但是我們平常用的多的也就是那幾個。所以這個時候你說需要轉換Map,那我們是不是可以去Guava中看看有沒有現成的方案,沒準有驚喜!!!

一看就是驚喜:

Maps#transformEntries(Map<K,V1>, Maps.EntryTransformer<? super K,? super V1,V2>)

Returns a view of a map whose values are derived from the original map's entries. In contrast to transformValues, this method's entry-transformation logic may depend on the key as well as the value. All other properties of the transformed map, such as iteration order, are left intact.

返回一個Map的試圖,其中它的值是從原來map中entry派生出來的。相較于transformValues方法,這個基于entry的轉換邏輯是既依賴于key又依賴于value。變換后的映射的所有其他屬性(例如迭代順序)均保持不變

這不正是我們上面需要實現的 MaskMap嗎!!!

除此之外,我們還需要支持,對集合類型的脫敏轉換」,再去看一下呢,又是驚喜!!!

圖片圖片

到這,基本上,我們已經在Gauva中找到了我們所需要的全部元素了。下面就是簡單集合脫敏規則使用下就好了

public class MaskEntryTransformer implements Maps.EntryTransformer<Object, Object, Object> {
    private static final Maps.EntryTransformer<Object, Object, Object> MASK_ENTRY_TRANSFORMER = new MaskEntryTransformer();

    private MaskEntryTransformer() {

    }

    public static Maps.EntryTransformer<Object, Object, Object> getInstance() {
        return MASK_ENTRY_TRANSFORMER;
    }

    @Override
    public Object transformEntry(Object objectKey, Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Map) {
            Map valueMap = (Map) value;
            return Maps.transformEntries(valueMap, this);
        }
        final Maps.EntryTransformer<Object, Object, Object> thisFinalMaskEntryTransformer = this;
        if (value instanceof Collection) {
            Collection valueCollection = (Collection) value;
            if (valueCollection.isEmpty()) {
                return valueCollection;
            }
            return Collections2.transform(valueCollection, new Function<Object, Object>() {
                @Override
                public Object apply(Object input) {
                    if (input == null) {
                        return null;
                    }
                    if (input instanceof Map) {
                        Map inputValueMap = (Map) input;
                        return Maps.transformEntries(inputValueMap, thisFinalMaskEntryTransformer);
                    }
                    if (input instanceof Collection) {
                        Collection inputValueCollection = (Collection) input;
                        return Collections2.transform(inputValueCollection, this);
                    }
                    if (!(objectKey instanceof String)) {
                        return input;
                    }
                    final String key = (String) objectKey;
                    return transformPrimitiveType(key, input);
                }
            });
        }
        if (!(objectKey instanceof String)) {
            return value;
        }
        final String key = (String) objectKey;
        return transformPrimitiveType(key, value);
    }

    /**
     * 按照脫敏規則脫敏基本數據類型
     *
     * @param key
     * @param value
     * @return
     */
    private Object transformPrimitiveType(final String key, final Object value) {
        // ...
    }
}

那脫敏的地方只要轉換一下Map就可以了,如下:

public class DataMask {
    /**
     * 將需要脫敏的字段先進行脫敏操作,最后轉成json格式
     * @param object 需要序列化的對象
     * @return 脫敏后的json格式
     */
    public static String toJSONString(Object object) {
        if (object == null) {
                return null;
        }
        try {
                if (object instanceof Map) {
                        Map maskMap = Maps.transformEntries((Map) object, MaskEntryTransformer.getInstance());
                        return JsonUtil.toJSONString(maskMap);
                }

                return JsonUtil.toJSONString(object, jsonFilter);
        } catch (Exception e) {
                return object.toString();
        }
    }
}

一點疑問

我們調用了transformEntries方法,是可以根據key(可以找到脫敏規則)和value(可以判斷類型)來轉換的。但是Map中的有些API實際上可能只有value(比如values())或者只有key(比如get()方法)的,那這種EntryTransformer是如何生效的?

你是不是有那么一點好奇呢?

我們就不去想了,直接看代碼:

  • 對于get()方,只有key參數:

但是比較容易通過key拿到對應的value,然后把key和value傳給轉換函數就可以了

public V2 get(Object key) {  
    V1 value = fromMap.get(key);  
    return (value != null || fromMap.containsKey(key))  
    ? transformer.transformEntry((K) key, value)  
    : null;  
}

這里的實現其實有一個細節,不知道大家注意沒有。

就是這一個判斷:(value != null || fromMap.containsKey(key)) ,我們上面實現的時候直接沒有考慮到值為null的情況(但value為null,但是有對應的key的時候還是應該要調用轉換函數的)

  • 對于values()方,只返回值
public Collection<V2> values() {  
    return new Values<K, V2>(this);  
}

也是一樣,返回一個轉換后的 Collection,其中Collection中的值也是經過 transformer轉換的。

而對于迭代器也是一樣的,都有對應的實現類把轉換邏輯放進去了。

Guava閑聊

Guava的Veiw思想

其實上面的這種 Veiw的基本思想,在Guava中有非常多的場景應用的,如

  • com.google.common.base.Splitter#split
  • com.google.common.collect.Lists#partition
  • com.google.common.collect.Sets#difference
  • ...other

這種 Veiw的基本思想,其實就是懶加載」的思想:在調用的時候不實際做轉換,而是在實際使用的時候會做轉換」

比如:com.google.common.base.Splitter#split在調用的時候實際上并不是立刻馬上去分隔字符串,而是返回一個Iterable的View對象。只是在實際迭代這個Iterable的View對象時才會實際去切分字符串。

在比如com.google.common.collect.Sets#difference在調用的時候并不會立刻馬上去做兩個集合的差操作,而是返回一個 SetView的View對象,只有在實際使用這個View對象的API的時候才會真正做差操作。

注意點:這種思想大部分場景下,會是性能友好型的,但是也有例外。我們要分清楚場景。

比如,分割字符串的方法com.google.common.base.Splitter#split,如果調用完之后不做任何操作,或者只會遍歷一次(大部分場景都是只遍歷一次),那么這其實就是最好的方法。但是如果調用完之后,還需要遍歷很多次,那么這種場景下,性能可能不是最好的。

所以,對于我們來講,如果Guava工具包中沒有我們需要的View的方法,那么我們可以自己按照這個思想來造一個。如果已經有了,要區分場景使用.

簡單聊一下Guava API的兼容性

升級過Guava版本的同學可能深有體會,升級Guava是一件比較頭疼的事情。因為「Gauva API的兼容性是做得很差」的。(當然這里主要還是因為大部分同學沒有認真閱讀官方的文檔導致的)

因為,官網首頁就直接提醒了Guava API的兼容性原則:

圖片圖片

具體請參考:Guava官網https://guava.dev/。

這對于我們做基礎組件的同學來講,是一件尤其需要注意的事情。因為一旦使用了前后不兼容的API,那么使用組件的應用很可能因為API不兼容,導致無法運行的問題。

所以對于做組件的同學,對于Guava的使用一定要慎重:能不用就不用,必須要用的話一定不能使用以@Beta注解標注的方法或者類。

總結

Gauva確實是一個保藏庫,當你要造輪子的時候不妨先看看Gauva中有沒有現成的輪子。每看一次,也許就多一次驚喜。

這一次是Map對象脫敏場景遇上了Guava的 Maps#transformEntries(Map<K,V1>, .Maps.EntryTransformer<? super K,? super V1,V2>) ,那么你又有什么場景偶遇了Guava呢?,可以在評論區聊一下。

責任編輯:武曉燕 來源: 碼猿技術專欄
相關推薦

2023-04-12 11:18:51

甘特圖前端

2023-11-30 10:21:48

虛擬列表虛擬列表工具庫

2023-12-08 13:19:00

前端Reactour流行庫

2020-12-17 06:48:21

SQLkafkaMySQL

2023-12-11 13:05:21

2019-04-01 14:59:56

負載均衡服務器網絡

2024-10-08 11:12:12

2021-09-07 09:40:20

Spark大數據引擎

2022-06-16 07:31:41

Web組件封裝HTML 標簽

2024-06-19 09:58:29

2023-12-21 11:39:47

2015-09-06 09:22:24

框架搭建快速高效app

2024-05-13 09:28:43

Flink SQL大數據

2012-07-10 01:22:32

PythonPython教程

2022-08-26 09:01:07

CSSFlex 布局

2023-07-15 18:26:51

LinuxABI

2024-11-07 16:09:53

2019-09-16 09:14:51

2009-10-09 14:45:29

VB程序

2017-10-10 05:05:33

KerasRNN序列學習
點贊
收藏

51CTO技術棧公眾號

亚洲一二三区精品| 欧美一级免费看| 野花视频免费在线观看| 2019中文字幕在线电影免费| 久久综合久久综合久久综合| 国产在线观看精品| www日韩精品| 日韩在线第七页| 亚洲电影中文字幕| 97人人爽人人| 少妇视频一区| 一区二区三区不卡在线观看| 日本高清不卡三区| 亚洲精品成人区在线观看| 青青草97国产精品免费观看 | 久久精品国产秦先生| 欧美精品www| 国产3级在线观看| 一区二区导航| 亚洲国产日韩欧美在线99| 欧美成年人视频在线观看| sm性调教片在线观看| 中文字幕在线不卡国产视频| 欧美一区二区综合| 好男人www在线视频| 久久激五月天综合精品| 国产不卡av在线免费观看| 欧美卡一卡二卡三| 成人在线电影在线观看视频| 亚洲精品一区二区久| 岛国av免费观看| 精品中文视频| 欧美日韩国产一区| 国产精品99久久免费黑人人妻| 欧美hdxxx| 亚洲免费观看高清完整版在线观看熊| 日韩视频在线播放| 蜜桃视频在线观看网站| hitomi一区二区三区精品| 91久久偷偷做嫩草影院| 999精品国产| 久久精品国产精品亚洲综合| 国产成人免费av电影| 免费看日批视频| 国产欧美短视频| 欧美激情videoshd| 免费网站看av| 国产一区日韩一区| 欧美激情视频一区| 午夜偷拍福利视频| 亚洲高清资源| 久久久久久久亚洲精品| 国产一级视频在线| 影音先锋一区| 午夜精品一区二区三区在线播放| 久久中文字幕无码| 在线欧美亚洲| 热99精品只有里视频精品| 欧美一区二区三区网站| 首页亚洲欧美制服丝腿| 国产精品久久久久久av福利软件| 亚洲高清在线看| 蜜臀av一级做a爰片久久| 国产精品视频yy9099| 中文字幕在线观看高清| 国产在线看一区| 亚洲最大成人免费视频| 亚洲毛片在线播放| 99精品国产99久久久久久白柏| 欧美国产视频在线观看| 成人在线免费视频| 亚洲视频综合在线| 日韩亚洲欧美一区二区| 国产99在线| 91久久奴性调教| 一区二区久久精品| 成人免费在线电影网| 精品亚洲一区二区三区四区五区| 欧美大波大乳巨大乳| 久久人人88| 欧美激情欧美狂野欧美精品| 天天爽夜夜爽夜夜爽精品| 日韩电影一区二区三区| 亚洲最大福利网| 亚洲三区在线播放| 中文字幕一区二区三区色视频| 欧美中文字幕在线观看视频 | 亚洲av成人片无码| 免费久久精品| 久久久精品亚洲| 国产乡下妇女做爰| 蜜乳av一区二区| 国产精品免费视频一区二区| 国产精品一区二区三区四区色| 国产精品久久久久久久久搜平片| 精品视频在线观看一区二区| av资源亚洲| 91麻豆精品国产91久久久资源速度 | 免费在线国产视频| 欧美视频二区36p| 久久精品久久99| 亚洲三级性片| 欧美激情一级二级| 亚洲天堂免费av| av网站一区二区三区| 伊人av成人| 是的av在线| 日韩精品中文字幕一区二区三区 | 男人的天堂久久久| 国产精品v亚洲精品v日韩精品| 欧美亚洲在线视频| www.欧美国产| 国产区在线观看成人精品| av在线播放天堂| 99精品美女视频在线观看热舞| 亚洲国产精品电影在线观看| 久久国产高清视频| 日精品一区二区三区| 狠狠色综合网站久久久久久久| 哥也色在线视频| 欧美午夜精品一区二区蜜桃| 一区二区三区免费在线观看视频| 久久精品视频观看| 久久中文字幕av一区二区不卡| 国产一区二区三区日韩欧美| 日本中文字幕在线免费观看| 国产精品538一区二区在线| 亚洲欧美综合一区| 欧美xnxx| 一区二区三区视频免费在线观看| 亚洲免费激情视频| 国产91综合网| 伊人网在线免费| 国产精久久一区二区| 中文字幕精品久久久久| 亚洲 国产 日韩 欧美| 久久亚洲精品国产精品紫薇| 国产v片免费观看| 91精品久久久久久综合五月天| 免费91在线视频| 亚洲综合视频在线播放| 亚洲国产精品激情在线观看| 99草草国产熟女视频在线| 久久99免费视频| 国产成人精品久久二区二区| 免费av在线电影| 日本韩国欧美三级| 人人爽人人爽人人片| 日韩精品免费专区| 亚洲人体一区| 精品视频一二| 欧美激情中文字幕乱码免费| 好吊视频一二三区| 精品国产乱码久久久久久婷婷| 中国极品少妇videossexhd| 99热免费精品| 欧美一区二区三区四区夜夜大片| 日本成人片在线| 自拍偷拍亚洲一区| 国产精品久久久久久久久久久久久久久久久久 | 亚洲日本欧美在线| 国产亚洲高清一区| 久久久久久九九九| 天堂成人在线| 玖玖综合伊人| 欧美综合一区二区| 色哟哟一一国产精品| 国产精品综合在线视频| 日本福利视频在线观看| 黄色美女久久久| 国产成人综合一区二区三区| 欧美一级二级三级区| 欧美一级专区免费大片| 国产午夜精品一区二区理论影院| 99免费精品视频| 成人在线观看a| 国产精品二区不卡| 国产精品久久久久av福利动漫| 涩涩涩在线视频| 日韩亚洲在线观看| 欧美性受xxxx狂喷水| 色综合色综合色综合| 艳妇荡乳欲伦69影片| 成人美女视频在线看| 欧美亚洲日本在线观看| 欧美欧美天天天天操| 欧美一区二区高清在线观看| 国产一区二区三区黄网站| 97涩涩爰在线观看亚洲| 99re在线视频| 亚洲精品美女久久久久| 一级特黄aa大片| 激情亚洲一区二区三区四区| 貂蝉被到爽流白浆在线观看| 成人午夜精品一区二区三区| 精品日韩久久久| 亚洲人成免费| 一区不卡字幕| 久久综合影院| 国产精品入口免费| 午夜精品久久久久久毛片| 69久久夜色精品国产69乱青草| 求av网址在线观看| 亚洲无线码在线一区观看| 好吊色一区二区| 91精品国产一区二区三区香蕉| 免费看日批视频| 亚洲成人免费在线| 91高清免费观看| 国产精品伦理在线| japanese中文字幕| 精品一级视频| 精品国产百合女同互慰| 国产精品免费无遮挡无码永久视频| 亚洲女人****多毛耸耸8| 性久久久久久久久久| 国产成人日日夜夜| 日韩高清第一页| 先锋影音久久久| 亚洲精品蜜桃久久久久久| 久久高清精品| 日韩精品欧美一区二区三区| 女同久久另类99精品国产| 99国内精品久久久久久久软件| 美女视频一区| 国产精品久久久久久久久粉嫩av| 久草在线资源福利站| 久久久久久午夜| 特级毛片在线| 久久色免费在线视频| av男人的天堂在线| 一区二区三区www| 福利视频在线看| 亚洲天堂av在线免费观看| 五月激情六月婷婷| 亚洲成成品网站| 蜜桃久久一区二区三区| 精品欧美一区二区在线观看| 国产乱淫片视频| 制服丝袜av成人在线看| 在线观看国产黄| 欧美三级电影一区| 中文字幕免费播放| 欧美色男人天堂| 一起草av在线| 欧美一级电影网站| 性猛交xxxx乱大交孕妇印度| 日韩三级视频在线看| 精品久久久无码中文字幕| 欧美一区二区福利视频| 国内精品久久久久久久久久久 | 综合色就爱涩涩涩综合婷婷| 国产在线一区二区三区播放| 欧美一级全黄| 免费一区二区三区| 久久av免费看| 最新精品视频| 欧美三级不卡| 5月婷婷6月丁香| 久久蜜桃精品| gai在线观看免费高清| 国产一区日韩二区欧美三区| 国产精品嫩草69影院| 99久久久久久| 神马久久久久久久久久久| 中文字幕一区二区三区乱码在线| 一区二区在线观看免费视频| 午夜精品成人在线| 国产男人搡女人免费视频| 69久久夜色精品国产69蝌蚪网| www.com欧美| 亚洲精品一区久久久久久| av片在线看| 欧美精品18videos性欧| 中老年在线免费视频| 国产精品网址在线| 18国产精品| 色婷婷精品国产一区二区三区| 天天综合一区| 国模无码视频一区二区三区| 蜜桃av噜噜一区| 男男一级淫片免费播放| 久久久精品2019中文字幕之3| sm捆绑调教视频| 欧美日韩久久久久| 一本色道久久综合精品婷婷| 亚洲国产一区二区三区四区 | 色综合久久88| 久久久久久久| 99国精产品一二二线| 国产一区二区三区日韩精品 | 日韩精品五月天| 国产精品偷伦视频免费观看了| 久久这里只有精品视频网| 日本午夜在线观看| 91精品1区2区| 日韩中文字幕影院| 色多多国产成人永久免费网站| 美女精品视频| 成人黄色激情网| 亚洲丝袜啪啪| 久久久久久免费看| 国内精品不卡在线| 丁香花五月婷婷| 岛国av一区二区三区| av综合在线观看| 一本大道久久加勒比香蕉| 美女网站在线看| 91日韩久久| 91精品国产成人观看| 我看黄色一级片| 久久久精品2019中文字幕之3| 国产精久久久久久| 日韩欧美一区二区视频| 日韩在线资源| 国产精品普通话| 日韩理论电影中文字幕| 免费视频爱爱太爽了| 国内久久婷婷综合| 夫妇露脸对白88av| 一本一道波多野结衣一区二区| 亚洲精品国产精品乱码不卡| 久久精品国产91精品亚洲| 九九九伊在线综合永久| 久久综合九色99| 99在线精品免费视频九九视| 亚洲精品乱码久久久久久蜜桃欧美| 国产精品久久一卡二卡| а中文在线天堂| 一区二区三区天堂av| 午夜精品成人av| 麻豆av一区二区三区| aⅴ色国产欧美| 三级男人添奶爽爽爽视频 | 做爰无遮挡三级| 亚洲欧美制服另类日韩| 欧美xxxhd| 麻豆av一区二区三区| 欧美亚洲免费| 99久久久无码国产精品性| 黄网动漫久久久| 亚洲日本在线播放| 欧美最近摘花xxxx摘花| 国产成人一区| 国产三级国产精品国产专区50| 日本一区二区三区四区| 中文区中文字幕免费看| 日韩在线观看免费高清完整版| 涩涩涩久久久成人精品| 免费看av软件| 国产99久久精品| 欧美精品亚洲精品日韩精品| 日韩精品久久久久久福利| 亚洲精品88| 日本一区二区精品视频| 男女男精品网站| www欧美com| 精品剧情v国产在线观看在线| 久久亚洲导航| 欧美xxxx黑人又粗又长密月| 久久这里只有| 中文字幕美女视频| 日韩欧美资源站| 成人免费图片免费观看| 欧美激情第六页| 美国十次了思思久久精品导航| 美国一级片在线观看| 日韩欧美中文字幕一区| 僵尸再翻生在线观看免费国语| 欧洲精品一区色| 激情久久五月天| 久久9999久久免费精品国产| 国产午夜精品麻豆| 玖玖精品在线| 国产黄色片免费在线观看| 国产日韩影视精品| www.欧美国产| 国产97在线播放| 91精品国产麻豆国产在线观看| 熟妇高潮一区二区| 在线观看成人小视频| 羞羞电影在线观看www| 久久综合毛片| 老司机午夜精品| 欧美成人aaaaⅴ片在线看| 一区二区三区日韩在线| 99re6热只有精品免费观看| 日日摸天天爽天天爽视频| 亚洲精品中文字幕乱码三区| 青青色在线视频| 91最新在线免费观看| 久久只有精品| 国产真实乱人偷精品视频| 中文字幕视频一区二区在线有码| 一区二区三区在线资源| 人妻丰满熟妇av无码区app| 玉米视频成人免费看| 东热在线免费视频| 狠狠色狠狠色综合人人| 极品美女销魂一区二区三区|