亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

模仿J2EE的session機制的App后端會話信息管理實例

2024-07-13 10:13:43
字體:
來源:轉載
供稿:網友

此文章只將思想,不提供具體完整實現(博主太懶,懶得整理),有疑問或想了解的可以私信或評論

背景

在傳統的java web 中小型項目中,一般使用session暫存會話信息,比如登錄者的身份信息等。此機制是借用http的cookie機制實現,但是對于app來說每次請求都保存并共享cookie信息比較麻煩,并且傳統的session對集群并不友好,所以一般app后端服務都使用token來區分用戶登錄信息。

j2ee的session機制大家都很了解,使用非常方便,在傳統java web應用中很好用,但是在互聯網項目中或用得到集群的一些項目就有些問題,比如序列化問題,同步的延時問題等等,所以我們需要一個使用起來類似session的卻能解決得了集群等問題的一個工具。

方案

我們使用cache機制來解決這個問題,比較流行的redis是個nosql內存數據庫,而且帶有cache的失效機制,很適合做會話數據的存儲。而token字符串需要在第一次請求時服務器返回給客戶端,客戶端以后每次請求都使用這個token標識身份。為了對業務開發透明,我們把app的請求和響應做的報文封裝,只需要對客戶端的http請求工具類做點手腳,對服務端的mvc框架做點手腳就可以了,客戶端的http工具類修改很簡單,主要是服務端的協議封裝。

實現思路

一、制定請求響應報文協議。

二、解析協議處理token字符串。

三、使用redis存儲管理token以及對應的會話信息。

四、提供保存、獲取會話信息的API。

我們逐步講解下每一步的實現方案。

一、制定請求響應報文協議。

既然要封裝報文協議,就需要考慮什么是公共字段,什么是業務字段,報文的數據結構等。

請求的公共字段一般有token、版本、平臺、機型、imei、app來源等,其中token是我們這次的主角。

響應的公共字段一般有token、結果狀態(success,fail)、結果碼(code)、結果信息等。

報文數據結構,我們選用json,原因是json普遍、可視化好、字節占用低。

請求報文如下,body中存放業務信息,比如登錄的用戶名和密碼等。

{  "token": "客戶端token",  /**客戶端構建版本號*/  "version": 11,  /**客戶端平臺類型*/  "platform": "IOS",  /**客戶端設備型號*/  "machineModel": "Iphone 6s",  "imei": "客戶端串號(手機)",  /**真正的消息體,應為map*/  "body": {    "key1": "value1",    "key2": {      "key21": "value21"    },    "key3": [      1,    ]  }}

響應的報文

{    /**是否成功*/    "success": false,    /**每個請求都會返回token,客戶端每次請求都應使用最新的token*/    "token": "服務器為當前請求選擇的token",    /**失敗碼*/    "failCode": 1,    /**業務消息或者失敗消息*/    "msg": "未知原因",    /**返回的真實業務數據,可為任意可序列化的對象*/    "body": null  }}

二、解析協議處理token字符串。

服務端的mvc框架我們選用的是SpringMVC框架,SpringMVC也比較普遍,不做描述。

暫且不提token的處理,先解決制定報文后怎么做參數傳遞。

因為請求信息被做了封裝,所以要讓springmvc框架能正確注入我們在Controller需要的參數,就需要對報文做解析和轉換。

要對請求信息做解析,我們需要自定義springmvc的參數轉換器,通過實現HandlerMethodArgumentResolver接口可以定義一個參數轉換器

RequestBodyResolver實現resolveArgument方法,對參數進行注入,以下代碼為示例代碼,切勿拿來直用。

@Override  public Object resolveArgument(MethodParameter parameter,      ModelAndViewContainer mavContainer, NativeWebRequest webRequest,      WebDataBinderFactory binderFactory) throws Exception {    String requestBodyStr = webRequest.getParameter(requestBodyParamName);//獲取請求報文,可以使用任意方式傳遞報文,只要在這獲取到就可以    if(StringUtils.isNotBlank(requestBodyStr)){      String paramName = parameter.getParameterName();//獲取Controller中參數名      Class<?> paramClass = parameter.getParameterType();//獲取Controller中參數類型      /* 通過json工具類解析報文 */      JsonNode jsonNode = objectMapper.readTree(requestBodyStr);      if(paramClass.equals(ServiceRequest.class)){//ServiceRequest為請求報文對應的VO        ServiceRequest serviceRequest = objectMapper.readValue(jsonNode.traverse(),ServiceRequest.class);        return serviceRequest;//返回這個object就是注入到參數中了,一定要對應類型,否則異常不容易捕獲      }      if(jsonNode!=null){//從報文中查找Controller中需要的參數        JsonNode paramJsonNode = jsonNode.findValue(paramName);        if(paramJsonNode!=null){          return objectMapper.readValue(paramJsonNode.traverse(), paramClass);        }              }    }    return null;  }

將自己定義的參數轉換器配置到SrpingMVC的配置文件中<mvc:argument-resolvers>

<mvc:argument-resolvers>  <!-- 統一的請求信息處理,從ServiceRequest中取數據 -->     <bean id="requestBodyResolver" class="com.niuxz.resolver.RequestBodyResolver">       <property name="objectMapper"><bean class="com.shoujinwang.utils.json.ObjectMapper"></bean></property>       <!-- 配置請求中ServiceRequest對應的字段名,默認為requestBody -->       <property name="requestBodyParamName"><value>requestBody</value></property>     </bean></mvc:argument-resolvers>

這樣就可以使報文中的參數能被springmvc正確識別了。

接下來我們要對token做處理了,我們需要添加一個SrpingMVC攔截器將每次請求都攔截下來,這屬于常用功能,不做細節描述

Matcher m1 =Pattern.compile("/"token/":/"(.*?)/"").matcher(requestBodyStr);  if(m1.find()){  token = m1.group(1);}tokenMapPool.verifyToken(token);//對token做公共處理,驗證

這樣就簡單的獲取到了token了,可以做公共處理了。

三、使用redis存儲管理token以及對應的會話信息。

其實就是寫一個redis的操作工具類,因為使用了spring作為項目主框架,而且我們用到redis的功能并不多,所以直接使用spring提供的CacheManager功能

配置org.springframework.data.redis.cache.RedisCacheManager

<!-- 緩存管理器 全局變量等可以用它存取--><bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">  <constructor-arg>    <ref bean="redisTemplate"/>  </constructor-arg>  <property name="usePrefix" value="true" />  <property name="cachePrefix">    <bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix">      <constructor-arg name="delimiter" value=":@WebServiceInterface"/>    </bean>  </property>  <property name="expires"><!-- 緩存有效期 -->    <map>      <entry>        <key><value>tokenPoolCache</value></key><!-- tokenPool緩存名 -->        <value>2592000</value><!-- 有效時間 -->      </entry>    </map>  </property></bean>

四、提供保存、獲取會話信息的API。

通過以上前戲我們已經把token處理的差不多了,接下來我們要實現token管理工作了

我們需要讓業務開發方便的保存獲取會話信息,還要使token是透明的。

import java.util.HashMap;import java.util.Map;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.cache.Cache;import org.springframework.cache.Cache.ValueWrapper;import org.springframework.cache.CacheManager;/** *  * 類      名:  TokenMapPoolBean * 描      述:  token以及相關信息調用處理類 * 修 改 記 錄:   * @version  V1.0 * @date  2016年4月22日 * @author  NiuXZ * */public class TokenMapPoolBean {      private static final Log log = LogFactory.getLog(TokenMapPoolBean.class);    /** 當前請求對應的token*/  private ThreadLocal<String> currentToken;    private CacheManager cacheManager;    private String cacheName;    private TokenGenerator tokenGenerator;    public TokenMapPoolBean(CacheManager cacheManager, String cacheName, TokenGenerator tokenGenerator) {    this.cacheManager = cacheManager;    this.cacheName = cacheName;    this.tokenGenerator = tokenGenerator;    currentToken = new ThreadLocal<String>();  }    /**   * 如果token合法就返回token,不合法就創建一個新的token并返回,   * 將token放入ThreadLocal中 并初始化一個tokenMap   * @param token   * @return token   */  public String verifyToken(String token) {    //    log.info("校驗Token:/""+token+"/"");    String verifyedToken = null;    if (tokenGenerator.checkTokenFormat(token)) {      //      log.info("校驗Token成功:/""+token+"/"");      verifyedToken = token;    }    else {      verifyedToken = newToken();    }    currentToken.set(verifyedToken);    Cache cache = cacheManager.getCache(cacheName);    if (cache == null) {      throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName);    }    ValueWrapper value = cache.get(verifyedToken);    //token對應的值為空,就創建一個新的tokenMap放入緩存中    if (value == null || value.get() == null) {      verifyedToken = newToken();      currentToken.set(verifyedToken);      Map<String, Object> tokenMap = new HashMap<String, Object>();      cache.put(verifyedToken, tokenMap);    }    return verifyedToken;  }    /**   * 生成新的token   * @return token   */  private String newToken() {    Cache cache = cacheManager.getCache(cacheName);    if (cache == null) {      throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName);    }    String newToken = null;    int count = 0;    do {      count++;      newToken = tokenGenerator.generatorToken();    }    while (cache.get(newToken) != null);    //    log.info("創建Token成功:/""+newToken+"/" 嘗試生成:"+count+"次");    return newToken;  }    /**   * 獲取當前請求的tokenMap中對應key的對象   * @param key   * @return 當前請求的tokenMap中對應key的屬性,模擬session   */  public Object getAttribute(String key) {    Cache cache = cacheManager.getCache(cacheName);    if (cache == null) {      throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName);    }    ValueWrapper tokenMapWrapper = cache.get(currentToken.get());    Map<String, Object> tokenMap = null;    if (tokenMapWrapper != null) {      tokenMap = (Map<String, Object>) tokenMapWrapper.get();    }    if (tokenMap == null) {      verifyToken(currentToken.get());      tokenMapWrapper = cache.get(currentToken.get());      tokenMap = (Map<String, Object>) tokenMapWrapper.get();    }    return tokenMap.get(key);  }    /**   * 設置到當前請求的tokenMap中,模擬session<br>   * TODO:此種方式設置attribute有問題:<br>   * 1、可能在同一token并發的情況下執行cache.put(currentToken.get(),tokenMap);時,<br>   *   tokenMap可能不是最新,會導致丟失數據。<br>   * 2、每次都put整個tokenMap,數據量太大,需要優化<br>   * @param key value   */  public void setAttribute(String key, Object value) {    Cache cache = cacheManager.getCache(cacheName);    if (cache == null) {      throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName);    }    ValueWrapper tokenMapWrapper = cache.get(currentToken.get());    Map<String, Object> tokenMap = null;    if (tokenMapWrapper != null) {      tokenMap = (Map<String, Object>) tokenMapWrapper.get();    }    if (tokenMap == null) {      verifyToken(currentToken.get());      tokenMapWrapper = cache.get(currentToken.get());      tokenMap = (Map<String, Object>) tokenMapWrapper.get();    }    log.info("TokenMap.put(key=" + key + ",value=" + value + ")");    tokenMap.put(key, value);    cache.put(currentToken.get(), tokenMap);  }    /**    * 獲取當前線程綁定的用戶token   * @return token   */  public String getToken() {    if (currentToken.get() == null) {      //初始化一次token      verifyToken(null);    }    return currentToken.get();  }    /**   * 刪除token以及tokenMap   * @param token   */  public void removeTokenMap(String token) {    if (token == null) {      return;    }    Cache cache = cacheManager.getCache(cacheName);    if (cache == null) {      throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName);    }    log.info("刪除Token:token=" + token);    cache.evict(token);  }    public CacheManager getCacheManager() {    return cacheManager;  }    public void setCacheManager(CacheManager cacheManager) {    this.cacheManager = cacheManager;  }    public String getCacheName() {    return cacheName;  }    public void setCacheName(String cacheName) {    this.cacheName = cacheName;  }    public TokenGenerator getTokenGenerator() {    return tokenGenerator;  }    public void setTokenGenerator(TokenGenerator tokenGenerator) {    this.tokenGenerator = tokenGenerator;  }    public void clear() {    currentToken.remove();  }  }

這里用到了ThreadLocal變量是因為servlet容器一個請求對應一個線程,在一個請求的生命周期內都是處于同一個線程中,而同時又有多個線程共享token管理器,所以需要這個線程本地變量來保存token字符串。

注意事項:

1、verifyToken方法的調用,一定要在每次請求最開始調用。并且在請求結束后調用clear做清除,以免下次有未知異常導致verifyToken未被執行,卻在返回時從ThreadLocal里取出token返回。(這個bug困擾我好幾天,公司n個開發檢查代碼也沒找到,最后我經過測試發現是在發生404的時候沒有進入攔截器,所以就沒有調用verifyToken方法,導致返回的異常信息中的token為上一次請求的token,導致詭異的串號問題。嗯,記我一大鍋)。

2、客戶端一定要在封裝http工具的時候把每次token保存下來,并用于下一次請求。公司ios開發請的外包,但是外包沒按要求做,在未登錄時,不保存token,每次傳遞的都是null,導致每次請求都會創建一個token,服務器創建了大量的無用token。

使用

使用方式也很簡單,以下是封裝的登錄管理器,可以參考一下token管理器對于登陸管理器的應用

import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.cache.Cache;import org.springframework.cache.Cache.ValueWrapper;import org.springframework.cache.CacheManager;import com.niuxz.base.Constants;/** *  * 類      名:  LoginManager * 描      述:  登錄管理器 * 修 改 記 錄:   * @version  V1.0 * @date  2016年7月19日 * @author  NiuXZ * */public class LoginManager {      private static final Log log = LogFactory.getLog(LoginManager.class);    private CacheManager cacheManager;    private String cacheName;    private TokenMapPoolBean tokenMapPool;    public LoginManager(CacheManager cacheManager, String cacheName, TokenMapPoolBean tokenMapPool) {    this.cacheManager = cacheManager;    this.cacheName = cacheName;    this.tokenMapPool = tokenMapPool;  }  public void login(String userId) {    log.info("用戶登錄:userId=" + userId);    Cache cache = cacheManager.getCache(cacheName);    ValueWrapper valueWrapper = cache.get(userId);    String token = (String) (valueWrapper == null ? null : valueWrapper.get());    tokenMapPool.removeTokenMap(token);//退出之前登錄記錄    tokenMapPool.setAttribute(Constants.LOGGED_USER_ID, userId);    cache.put(userId, tokenMapPool.getToken());  }    public void logoutCurrent(String phoneTel) {    String curUserId = getCurrentUserId();    log.info("用戶退出:userId=" + curUserId);    tokenMapPool.removeTokenMap(tokenMapPool.getToken());//退出登錄    if (curUserId != null) {      Cache cache = cacheManager.getCache(cacheName);      cache.evict(curUserId);      cache.evict(phoneTel);    }  }    /**   * 獲取當前用戶的id   * @return   */  public String getCurrentUserId() {    return (String) tokenMapPool.getAttribute(Constants.LOGGED_USER_ID);  }    public CacheManager getCacheManager() {    return cacheManager;  }    public String getCacheName() {    return cacheName;  }    public TokenMapPoolBean getTokenMapPool() {    return tokenMapPool;  }    public void setCacheManager(CacheManager cacheManager) {    this.cacheManager = cacheManager;  }    public void setCacheName(String cacheName) {    this.cacheName = cacheName;  }    public void setTokenMapPool(TokenMapPoolBean tokenMapPool) {    this.tokenMapPool = tokenMapPool;  }  }

下面是一段常見的發送短信驗證碼接口,有的應用也是用session存儲驗證碼,我不建議用這種方式,存session弊端相當大。大家看看就好,不是我寫的

public void sendValiCodeByPhoneNum(String phoneNum, String hintMsg, String logSuffix) {    validatePhoneTimeSpace();    // 獲取6位隨機數    String code = CodeUtil.getValidateCode();    log.info(code + "------->" + phoneNum);    // 調用短信驗證碼下發接口    RetStatus retStatus = msgSendUtils.sendSms(code + hintMsg, phoneNum);    if (!retStatus.getIsOk()) {      log.info(retStatus.toString());      throw new ThrowsToDataException(ServiceResponseCode.FAIL_INVALID_PARAMS, "手機驗證碼獲取失敗,請稍后再試");    }    // 重置session    tokenMapPool.setAttribute(Constants.VALIDATE_PHONE, phoneNum);    tokenMapPool.setAttribute(Constants.VALIDATE_PHONE_CODE, code.toString());    tokenMapPool.setAttribute(Constants.SEND_CODE_WRONGNU, 0);    tokenMapPool.setAttribute(Constants.SEND_CODE_TIME, new Date().getTime());    log.info(logSuffix + phoneNum + "短信驗證碼:" + code);  }

處理響應

有的同學會問了 那么響應的報文封裝呢?

@RequestMapping("record")@ResponseBodypublic ServiceResponse record(String message){  String userId = loginManager.getCurrentUserId();   messageBoardService.recordMessage(userId, message);  return ServiceResponseBuilder.buildSuccess(null);}

其中ServiceResponse是封裝的響應報文VO,我們直接使用springmvc的@ResponseBody注解就好了。關鍵在于這個builder。

import org.apache.commons.lang3.StringUtils;import com.niuxz.base.pojo.ServiceResponse;import com.niuxz.utils.spring.SpringContextUtil;import com.niuxz.web.server.token.TokenMapPoolBean;/** *  * 類 名: ServiceResponseBuilder *  * @version V1.0 * @date 2016年4月25日 * @author NiuXZ * */public class ServiceResponseBuilder {  /**   * 構建一個成功的響應信息   *    * @param body   * @return 一個操作成功的 ServiceResponse   */  public static ServiceResponse buildSuccess(Object body) {    return new ServiceResponse(        ((TokenMapPoolBean) SpringContextUtil.getBean("tokenMapPool"))            .getToken(),        "操作成功", body);  }  /**   * 構建一個成功的響應信息   *    * @param body   * @return 一個操作成功的 ServiceResponse   */  public static ServiceResponse buildSuccess(String token, Object body) {    return new ServiceResponse(token, "操作成功", body);  }  /**   * 構建一個失敗的響應信息   *    * @param failCode   *      msg   * @return 一個操作失敗的 ServiceResponse   */  public static ServiceResponse buildFail(int failCode, String msg) {    return buildFail(failCode, msg, null);  }  /**   * 構建一個失敗的響應信息   *    * @param failCode   *      msg body   * @return 一個操作失敗的 ServiceResponse   */  public static ServiceResponse buildFail(int failCode, String msg,      Object body) {    return new ServiceResponse(        ((TokenMapPoolBean) SpringContextUtil.getBean("tokenMapPool"))            .getToken(),        failCode, StringUtils.isNotBlank(msg) ? msg : "操作失敗", body);  }}

由于使用的是靜態工具類的形式,不能通過spring注入tokenMapPool(token管理器)對象,則通過spring提供的api獲取。然后構建響應信息的時候直接調用tokenMapPool的getToken()方法,此方法會返回當前線程綁定的token字符串。再次強調在請求結束后一定要手動調用clear(我通過全局攔截器調用)。

以上這篇模仿J2EE的session機制的App后端會話信息管理實例就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持VeVb武林網。

 

注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩在线视频导航| 亚洲国产精品国自产拍av秋霞| 亚洲999一在线观看www| 丝袜亚洲另类欧美重口| 亚洲老板91色精品久久| 色噜噜狠狠狠综合曰曰曰88av| 成人精品久久一区二区三区| 亚洲免费视频在线观看| 视频在线一区二区| 久久精品视频中文字幕| 久久手机免费视频| 精品一区二区三区四区在线| 国产精品第三页| 成人免费xxxxx在线观看| 欧洲成人在线观看| 亚洲免费成人av电影| 操日韩av在线电影| 亚洲欧洲午夜一线一品| 日韩精品欧美激情| 久久99久久99精品中文字幕| 欧美日韩视频免费播放| 在线日韩精品视频| 欧美激情精品久久久久久久变态| 日韩av大片免费看| 欧美色视频日本版| 热久久这里只有精品| 欧美性猛交xxxx黑人猛交| 国产免费一区二区三区在线能观看| 国产亚洲精品美女久久久久| 在线精品高清中文字幕| 亚洲字幕一区二区| 国产精品69精品一区二区三区| 亚洲电影在线观看| 日韩一区视频在线| 日韩av影片在线观看| 日韩av中文字幕在线播放| 国产精品国产自产拍高清av水多| 亚洲精品欧美日韩专区| 国产一区在线播放| 久久精品青青大伊人av| 在线观看亚洲视频| 国产精品久久久久久一区二区| 精品动漫一区二区| 欧美插天视频在线播放| 日韩欧美国产高清91| 久久久精品久久| 最新国产成人av网站网址麻豆| 欧美激情一级二级| 国产在线拍揄自揄视频不卡99| 韩国欧美亚洲国产| 久久久久久久国产精品视频| 青青青国产精品一区二区| 久热在线中文字幕色999舞| 欧美一级片久久久久久久| 日韩在线中文字| 欧美色图在线视频| 欧美大尺度激情区在线播放| 日韩精品在线视频观看| 欧美日韩国产成人在线| 欧美—级高清免费播放| 国内精品模特av私拍在线观看| 国产精品吊钟奶在线| 欧美精品久久久久久久免费观看| 久久成人综合视频| 亚洲一区二区三区sesese| 欧美性开放视频| 欧美第一淫aaasss性| 深夜成人在线观看| 91精品国产99久久久久久| 成人性生交大片免费看小说| 亚洲a∨日韩av高清在线观看| 欧美最近摘花xxxx摘花| 亚洲综合小说区| 日韩av最新在线观看| 精品国产一区二区三区久久狼黑人| 国产一区二区三区视频在线观看| 日韩av在线播放资源| 国产亚洲综合久久| 日韩激情av在线免费观看| 国产精品美女主播| 国产欧美日韩中文字幕| 亚洲欧美日韩天堂| 九九久久久久99精品| 亚洲欧美日韩精品久久亚洲区| 蜜臀久久99精品久久久无需会员| 亚洲人午夜精品| 欧美日韩一区二区在线播放| 另类少妇人与禽zozz0性伦| 国色天香2019中文字幕在线观看| 一区二区三区www| 亚洲已满18点击进入在线看片| 亚洲图片在线综合| 亚洲аv电影天堂网| 97超级碰碰碰| 日本精品中文字幕| 亚洲第一在线视频| 操日韩av在线电影| 久久精品成人一区二区三区| 一区二区亚洲精品国产| 久久视频国产精品免费视频在线| 欧美日韩中国免费专区在线看| 久久99热精品这里久久精品| 亚洲高清免费观看高清完整版| 久久成人国产精品| 另类天堂视频在线观看| 成人欧美一区二区三区在线湿哒哒| 欧美成人四级hd版| 日韩精品中文字幕视频在线| 国产精品成av人在线视午夜片| 不卡在线观看电视剧完整版| 精品一区二区亚洲| 国产精品高潮呻吟视频| 日韩视频免费看| 久久久久久午夜| 精品中文字幕在线2019| 亚洲女同性videos| 国产精品热视频| 久久69精品久久久久久久电影好| 亚洲最新av网址| 久久在线免费观看视频| 亚洲精品按摩视频| 在线观看国产精品日韩av| 91禁国产网站| 国产在线98福利播放视频| 久久久久久久97| 欧美激情图片区| 欧美裸体视频网站| 日本成熟性欧美| 在线播放国产一区中文字幕剧情欧美| 国产日韩一区在线| 国产视频福利一区| 有码中文亚洲精品| 欧美精品激情在线| 久久人人爽人人爽人人片av高请| 欧美激情日韩图片| 欧美天天综合色影久久精品| 亚洲欧美在线磁力| 精品国产欧美一区二区五十路| 日韩在线小视频| 日韩在线国产精品| 91亚洲国产精品| 国产婷婷97碰碰久久人人蜜臀| 亚洲**2019国产| 国产精品www色诱视频| 中文字幕精品久久| 日韩麻豆第一页| 亚洲奶大毛多的老太婆| 国产成人91久久精品| 国产一区二区三区毛片| 欧美理论电影在线播放| 久久国产精品99国产精| 91九色综合久久| 欧美日本高清视频| 亚洲精品二三区| 日韩精品免费在线视频| 国产91免费观看| 日本最新高清不卡中文字幕| 97精品国产aⅴ7777| 欧美老女人性视频| 91在线视频成人| 欧美成人h版在线观看| 日日骚av一区| 91久久综合亚洲鲁鲁五月天| yw.139尤物在线精品视频|