色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

如何利用MyBatis Plus去實現數據權限控制呢?

jf_ro2CN3Fa ? 來源:CSDN ? 2023-08-23 10:40 ? 次閱讀

前言背景

平時開發中遇到根據當前用戶的角色,只能查看數據權限范圍的數據需求。列表實現方案有兩種,一是在開發初期就做好判斷賽選,但如果這個需求是中途加的,或不希望每個接口都加一遍,就可以方案二加攔截器的方式。在mybatis執行sql前修改語句,限定where范圍。

當然攔截器生效后是全局性的,如何保證只對需要的接口進行攔截和轉化,就可以應用注解進行識別

因此具體需要哪些步驟就明確了

創建注解類

創建攔截器實現InnerInterceptor接口,重寫查詢方法

創建處理類,獲取數據權限 SQL 片段,設置where

將攔截器加到MyBatis-Plus插件中

自定義注解

importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public@interfaceUserDataPermission{
}

攔截器

importcom.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
importcom.baomidou.mybatisplus.core.toolkit.PluginUtils;
importcom.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
importcom.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
importlombok.*;
importnet.sf.jsqlparser.expression.Expression;
importnet.sf.jsqlparser.statement.select.PlainSelect;
importnet.sf.jsqlparser.statement.select.Select;
importnet.sf.jsqlparser.statement.select.SelectBody;
importnet.sf.jsqlparser.statement.select.SetOperationList;
importorg.apache.ibatis.executor.Executor;
importorg.apache.ibatis.mapping.BoundSql;
importorg.apache.ibatis.mapping.MappedStatement;
importorg.apache.ibatis.session.ResultHandler;
importorg.apache.ibatis.session.RowBounds;

importjava.sql.SQLException;
importjava.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper=true)
@EqualsAndHashCode(callSuper=true)
publicclassMyDataPermissionInterceptorextendsJsqlParserSupportimplementsInnerInterceptor{

/**
*數據權限處理器
*/
privateMyDataPermissionHandlerdataPermissionHandler;

@Override
publicvoidbeforeQuery(Executorexecutor,MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql)throwsSQLException{
if(InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())){
return;
}
PluginUtils.MPBoundSqlmpBs=PluginUtils.mpBoundSql(boundSql);
mpBs.sql(this.parserSingle(mpBs.sql(),ms.getId()));
}

@Override
protectedvoidprocessSelect(Selectselect,intindex,Stringsql,Objectobj){
SelectBodyselectBody=select.getSelectBody();
if(selectBodyinstanceofPlainSelect){
this.setWhere((PlainSelect)selectBody,(String)obj);
}elseif(selectBodyinstanceofSetOperationList){
SetOperationListsetOperationList=(SetOperationList)selectBody;
ListselectBodyList=setOperationList.getSelects();
selectBodyList.forEach(s->this.setWhere((PlainSelect)s,(String)obj));
}
}

/**
*設置where條件
*
*@paramplainSelect查詢對象
*@paramwhereSegment查詢條件片段
*/
privatevoidsetWhere(PlainSelectplainSelect,StringwhereSegment){

ExpressionsqlSegment=this.dataPermissionHandler.getSqlSegment(plainSelect,whereSegment);
if(null!=sqlSegment){
plainSelect.setWhere(sqlSegment);
}
}
}

攔截器處理器

基礎只涉及 = 表達式,要查詢集合范圍 in 看進階版用例

importcn.hutool.core.collection.CollectionUtil;
importlombok.SneakyThrows;
importlombok.extern.slf4j.Slf4j;
importnet.sf.jsqlparser.expression.Alias;
importnet.sf.jsqlparser.expression.Expression;
importnet.sf.jsqlparser.expression.HexValue;
importnet.sf.jsqlparser.expression.StringValue;
importnet.sf.jsqlparser.expression.operators.conditional.AndExpression;
importnet.sf.jsqlparser.expression.operators.relational.EqualsTo;
importnet.sf.jsqlparser.expression.operators.relational.ExpressionList;
importnet.sf.jsqlparser.expression.operators.relational.InExpression;
importnet.sf.jsqlparser.expression.operators.relational.ItemsList;
importnet.sf.jsqlparser.schema.Column;
importnet.sf.jsqlparser.schema.Table;
importnet.sf.jsqlparser.statement.select.PlainSelect;

importjava.lang.reflect.Method;
importjava.util.List;
importjava.util.Objects;
importjava.util.Set;
importjava.util.stream.Collectors;

@Slf4j
publicclassMyDataPermissionHandler{

/**
*獲取數據權限SQL片段
*
*@paramplainSelect查詢對象
*@paramwhereSegment查詢條件片段
*@returnJSqlParser條件表達式
*/
@SneakyThrows(Exception.class)
publicExpressiongetSqlSegment(PlainSelectplainSelect,StringwhereSegment){
//待執行SQLWhere條件表達式
Expressionwhere=plainSelect.getWhere();
if(where==null){
where=newHexValue("1=1");
}
log.info("開始進行權限過濾,where:{},mappedStatementId:{}",where,whereSegment);
//獲取mapper名稱
StringclassName=whereSegment.substring(0,whereSegment.lastIndexOf("."));
//獲取方法名
StringmethodName=whereSegment.substring(whereSegment.lastIndexOf(".")+1);
TablefromItem=(Table)plainSelect.getFromItem();
//有別名用別名,無別名用表名,防止字段沖突報錯
AliasfromItemAlias=fromItem.getAlias();
StringmainTableName=fromItemAlias==null?fromItem.getName():fromItemAlias.getName();
//獲取當前mapper的方法
Method[]methods=Class.forName(className).getMethods();
//遍歷判斷mapper的所以方法,判斷方法上是否有UserDataPermission
for(Methodm:methods){
if(Objects.equals(m.getName(),methodName)){
UserDataPermissionannotation=m.getAnnotation(UserDataPermission.class);
if(annotation==null){
returnwhere;
}
//1、當前用戶Code
Useruser=SecurityUtils.getUser();
//查看自己的數據
//=表達式
EqualsTousesEqualsTo=newEqualsTo();
usesEqualsTo.setLeftExpression(newColumn(mainTableName+".creator_code"));
usesEqualsTo.setRightExpression(newStringValue(user.getUserCode()));
returnnewAndExpression(where,usesEqualsTo);
}
}
//說明無權查看,
where=newHexValue("1=2");
returnwhere;
}

}

將攔截器加到MyBatis-Plus插件中

如果你之前項目配插件 ,直接用下面方式就行

@Bean
publicMybatisPlusInterceptormybatisPlusInterceptor(){
MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();
//添加數據權限插件
MyDataPermissionInterceptordataPermissionInterceptor=newMyDataPermissionInterceptor();
//添加自定義的數據權限處理器
dataPermissionInterceptor.setDataPermissionHandler(newMyDataPermissionHandler());
interceptor.addInnerInterceptor(dataPermissionInterceptor);
interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));
returninterceptor;
}

但如果你項目之前是依賴包依賴,或有公司內部統一攔截設置好,也可以往MybatisPlusInterceptor進行插入,避免影響原有項目配置

@Bean
publicMyDataPermissionInterceptormyInterceptor(MybatisPlusInterceptormybatisPlusInterceptor){
MyDataPermissionInterceptorsql=newMyDataPermissionInterceptor();
sql.setDataPermissionHandler(newMyDataPermissionHandler());
Listlist=newArrayList<>();
//添加數據權限插件
list.add(sql);
//分頁插件
mybatisPlusInterceptor.setInterceptors(list);
list.add(newPaginationInnerInterceptor(DbType.MYSQL));
returnsql;
}

以上就是簡單版的是攔截器修改語句使用

使用方式

在mapper層添加注解即可

@UserDataPermission
ListselectAllCustomerPage(IPagepage,@Param("customerName")StringcustomerName);

基礎班只是能用,業務功能沒有特別約束,先保證能跑起來

進階版 解決兩個問題:

加了角色,用角色決定范圍

解決不是mapper層自定義sql查詢問題。

兩個是完全獨立的問題 ,可根據情況分開解決

解決不是mapper層自定義sql查詢問題。

例如我們名稱簡單的sql語句 直接在Service層用mybatisPluse自帶的方法

xxxxService.list(WrapperqueryWrapper)
xxxxService.page(newPage<>(),WrapperqueryWrapper)

以上這種我應該把注解加哪里呢

因為service層,本質上還是調mapper層, 所以還是在mapper層做文章,原來的mapper實現了extends BaseMapper 接口,所以能夠查詢,我們要做的就是在 mapper層中間套一個中間接口,來方便我們加注解

xxxxxMapper——》DataPermissionMapper(中間)——》BaseMapper

根據自身需要,在重寫的接口方法上加注解即可,這樣就影響原先的代碼

4d7a8f2c-40d8-11ee-a2ef-92fbcf53809c.png

importcom.baomidou.mybatisplus.core.conditions.Wrapper;
importcom.baomidou.mybatisplus.core.mapper.BaseMapper;
importcom.baomidou.mybatisplus.core.metadata.IPage;
importcom.baomidou.mybatisplus.core.toolkit.Constants;
importorg.apache.ibatis.annotations.Param;

importjava.io.Serializable;
importjava.util.Collection;
importjava.util.List;
importjava.util.Map;

publicinterfaceDataPermissionMapperextendsBaseMapper{

/**
*根據ID查詢
*
*@paramid主鍵ID
*/
@Override
@UserDataPermission
TselectById(Serializableid);

/**
*查詢(根據ID批量查詢)
*
*@paramidList主鍵ID列表(不能為null以及empty)
*/
@Override
@UserDataPermission
ListselectBatchIds(@Param(Constants.COLLECTION)CollectionidList);

/**
*查詢(根據columnMap條件)
*
*@paramcolumnMap表字段map對象
*/
@Override
@UserDataPermission
ListselectByMap(@Param(Constants.COLUMN_MAP)MapcolumnMap);

/**
*根據entity條件,查詢一條記錄
*
*@paramqueryWrapper實體對象封裝操作類(可以為null)
*/
@Override
@UserDataPermission
TselectOne(@Param(Constants.WRAPPER)WrapperqueryWrapper);

/**
*根據Wrapper條件,查詢總記錄數
*
*@paramqueryWrapper實體對象封裝操作類(可以為null)
*/
@Override
@UserDataPermission
IntegerselectCount(@Param(Constants.WRAPPER)WrapperqueryWrapper);

/**
*根據entity條件,查詢全部記錄
*
*@paramqueryWrapper實體對象封裝操作類(可以為null)
*/
@Override
@UserDataPermission
ListselectList(@Param(Constants.WRAPPER)WrapperqueryWrapper);

/**
*根據Wrapper條件,查詢全部記錄
*
*@paramqueryWrapper實體對象封裝操作類(可以為null)
*/
@Override
@UserDataPermission
List>selectMaps(@Param(Constants.WRAPPER)WrapperqueryWrapper);

/**
*根據Wrapper條件,查詢全部記錄
*

注意:只返回第一個字段的值

* *@paramqueryWrapper實體對象封裝操作類(可以為null) */ @Override @UserDataPermission ListselectObjs(@Param(Constants.WRAPPER)WrapperqueryWrapper); /** *根據entity條件,查詢全部記錄(并翻頁) * *@parampage分頁查詢條件(可以為RowBounds.DEFAULT) *@paramqueryWrapper實體對象封裝操作類(可以為null) */ @Override @UserDataPermission >EselectPage(Epage,@Param(Constants.WRAPPER)WrapperqueryWrapper); /** *根據Wrapper條件,查詢全部記錄(并翻頁) * *@parampage分頁查詢條件 *@paramqueryWrapper實體對象封裝操作類 */ @Override @UserDataPermission >>EselectMapsPage(Epage,@Param(Constants.WRAPPER)WrapperqueryWrapper); }

解決角色控制查詢范圍

引入角色,我們先假設有三種角色,按照常規的業務需求,一種是管理員查看全部、一種是部門管理查看本部門、一種是僅查看自己。

有了以上假設,就可以設置枚舉類編寫業務邏輯, 對是業務邏輯,所以我們只需要更改”攔截器處理器類“

建立范圍枚舉

建立角色枚舉以及范圍關聯關系

重寫攔截器處理方法

范圍枚舉

@AllArgsConstructor
@Getter
publicenumDataScope{
//Scope數據權限范圍:ALL(全部)、DEPT(部門)、MYSELF(自己)
ALL("ALL"),
DEPT("DEPT"),
MYSELF("MYSELF");
privateStringname;
}

角色枚舉

@AllArgsConstructor
@Getter
publicenumDataPermission{

//枚舉類型根據范圍從前往后排列,避免影響getScope
//Scope數據權限范圍:ALL(全部)、DEPT(部門)、MYSELF(自己)
DATA_MANAGER("數據管理員","DATA_MANAGER",DataScope.ALL),
DATA_AUDITOR("數據審核員","DATA_AUDITOR",DataScope.DEPT),
DATA_OPERATOR("數據業務員","DATA_OPERATOR",DataScope.MYSELF);

privateStringname;
privateStringcode;
privateDataScopescope;


publicstaticStringgetName(Stringcode){
for(DataPermissiontype:DataPermission.values()){
if(type.getCode().equals(code)){
returntype.getName();
}
}
returnnull;
}

publicstaticStringgetCode(Stringname){
for(DataPermissiontype:DataPermission.values()){
if(type.getName().equals(name)){
returntype.getCode();
}
}
returnnull;
}

publicstaticDataScopegetScope(Collectioncode){
for(DataPermissiontype:DataPermission.values()){
for(Stringv:code){
if(type.getCode().equals(v)){
returntype.getScope();
}
}
}
returnDataScope.MYSELF;
}
}

重寫攔截器處理類 MyDataPermissionHandler

importlombok.SneakyThrows;
importlombok.extern.slf4j.Slf4j;
importnet.sf.jsqlparser.expression.Alias;
importnet.sf.jsqlparser.expression.Expression;
importnet.sf.jsqlparser.expression.HexValue;
importnet.sf.jsqlparser.expression.StringValue;
importnet.sf.jsqlparser.expression.operators.conditional.AndExpression;
importnet.sf.jsqlparser.expression.operators.relational.EqualsTo;
importnet.sf.jsqlparser.expression.operators.relational.ExpressionList;
importnet.sf.jsqlparser.expression.operators.relational.InExpression;
importnet.sf.jsqlparser.expression.operators.relational.ItemsList;
importnet.sf.jsqlparser.schema.Column;
importnet.sf.jsqlparser.schema.Table;
importnet.sf.jsqlparser.statement.select.PlainSelect;

importjava.lang.reflect.Method;
importjava.util.List;
importjava.util.Objects;
importjava.util.Set;
importjava.util.stream.Collectors;

@Slf4j
publicclassMyDataPermissionHandler{

privateRemoteRoleServiceremoteRoleService;
privateRemoteUserServiceremoteUserService;


/**
*獲取數據權限SQL片段
*
*@paramplainSelect查詢對象
*@paramwhereSegment查詢條件片段
*@returnJSqlParser條件表達式
*/
@SneakyThrows(Exception.class)
publicExpressiongetSqlSegment(PlainSelectplainSelect,StringwhereSegment){
remoteRoleService=SpringUtil.getBean(RemoteRoleService.class);
remoteUserService=SpringUtil.getBean(RemoteUserService.class);

//待執行SQLWhere條件表達式
Expressionwhere=plainSelect.getWhere();
if(where==null){
where=newHexValue("1=1");
}
log.info("開始進行權限過濾,where:{},mappedStatementId:{}",where,whereSegment);
//獲取mapper名稱
StringclassName=whereSegment.substring(0,whereSegment.lastIndexOf("."));
//獲取方法名
StringmethodName=whereSegment.substring(whereSegment.lastIndexOf(".")+1);
TablefromItem=(Table)plainSelect.getFromItem();
//有別名用別名,無別名用表名,防止字段沖突報錯
AliasfromItemAlias=fromItem.getAlias();
StringmainTableName=fromItemAlias==null?fromItem.getName():fromItemAlias.getName();
//獲取當前mapper的方法
Method[]methods=Class.forName(className).getMethods();
//遍歷判斷mapper的所以方法,判斷方法上是否有UserDataPermission
for(Methodm:methods){
if(Objects.equals(m.getName(),methodName)){
UserDataPermissionannotation=m.getAnnotation(UserDataPermission.class);
if(annotation==null){
returnwhere;
}
//1、當前用戶Code
Useruser=SecurityUtils.getUser();
//2、當前角色即角色或角色類型(可能多種角色)
SetroleTypeSet=remoteRoleService.currentUserRoleType();

DataScopescopeType=DataPermission.getScope(roleTypeSet);
switch(scopeType){
//查看全部
caseALL:
returnwhere;
caseDEPT:
//查看本部門用戶數據
//創建IN表達式
//創建IN范圍的元素集合
ListdeptUserList=remoteUserService.listUserCodesByDeptCodes(user.getDeptCode());
//把集合轉變為JSQLParser需要的元素列表
ItemsListdeptList=newExpressionList(deptUserList.stream().map(StringValue::new).collect(Collectors.toList()));
InExpressioninExpressiondept=newInExpression(newColumn(mainTableName+".creator_code"),deptList);
returnnewAndExpression(where,inExpressiondept);
caseMYSELF:
//查看自己的數據
//=表達式
EqualsTousesEqualsTo=newEqualsTo();
usesEqualsTo.setLeftExpression(newColumn(mainTableName+".creator_code"));
usesEqualsTo.setRightExpression(newStringValue(user.getUserCode()));
returnnewAndExpression(where,usesEqualsTo);
default:
break;
}
}

}
//說明無權查看,
where=newHexValue("1=2");
returnwhere;
}
}

以上就是全篇知識點, 需要注意的點可能有:

記得把攔截器加到MyBatis-Plus的插件中,確保生效

要有一個業務賽選標識字段, 這里用的創建人 creator_code, 也可以用dept_code 等等。





審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 處理器
    +關注

    關注

    68

    文章

    19259

    瀏覽量

    229653
  • SQL
    SQL
    +關注

    關注

    1

    文章

    762

    瀏覽量

    44117

原文標題:巧用 MyBatis Plus 實現數據權限控制

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    如何利用STM32接口接收和探測實現數據的接收和發送

    如何利用STM32接口接收和探測實現數據的接收和發送?其代碼是如何
    發表于 11-17 07:12

    如何利用DW1000實現數據的接受與發送

    如何利用DW1000實現數據的接受與發送
    發表于 02-11 06:40

    Lora sx1278是怎樣利用串口協議實現數據傳輸的

    Lora sx1278是怎樣利用串口協議實現數據傳輸的?其代碼該怎樣
    發表于 02-21 06:37

    MyBatis實現原理

    本文主要詳細介紹了MyBatis實現原理。mybatis底層還是采用原生jdbc來對數據庫進行操作的,只是通過 SqlSessionFactory,SqlSession Execut
    的頭像 發表于 02-24 11:25 ?6485次閱讀
    <b class='flag-5'>MyBatis</b>的<b class='flag-5'>實現</b>原理

    Mybatis-Plus Mybatis增強工具包

    ./oschina_soft/gitee-mybatis-plus.zip
    發表于 06-13 11:34 ?1次下載
    <b class='flag-5'>Mybatis-Plus</b> <b class='flag-5'>Mybatis</b>增強工具包

    MyBatis-Plus的使用與測試

    本文主要介紹mybatis-plus這款插件,針對springboot用戶。包括引入,配置,使用,以及擴展等常用的方面做一個匯總整理,盡量包含大家常用的場景內容。
    的頭像 發表于 08-22 11:56 ?1311次閱讀

    Fluent Mybatis、原生MybatisMybatis Plus對比

    mapper中再組裝參數。那對比原生Mybatis, Mybatis Plus或者其他框架,FluentMybatis提供了哪些便利
    的頭像 發表于 09-15 15:41 ?1431次閱讀

    SpringBoot 實現異步記錄復雜日志

    基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、
    發表于 12-22 10:35 ?468次閱讀

    SpringBoot+ElasticSearch實現模糊查詢功能

    基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、
    的頭像 發表于 12-30 14:00 ?1001次閱讀

    MyBatis-Plus為什么不支持聯表

    `的所有功能`MyBatis Plus Join`同樣擁有;框架的使用方式和`MyBatis Plus`一樣簡單,幾行代碼就能實現聯表查詢的
    的頭像 發表于 02-28 15:19 ?2451次閱讀
    <b class='flag-5'>MyBatis-Plus</b>為什么不支持聯表

    基于Mybatis攔截器實現數據范圍權限

    前端的菜單和按鈕權限都可以通過配置來實現,但很多時候,后臺查詢數據數據權限需要通過手動添加SQL來
    的頭像 發表于 06-20 09:57 ?1326次閱讀
    基于<b class='flag-5'>Mybatis</b>攔截器<b class='flag-5'>實現</b><b class='flag-5'>數據</b>范圍<b class='flag-5'>權限</b>

    如何實現基于Mybatis攔截器實現數據范圍權限

    前端的菜單和按鈕權限都可以通過配置來實現,但很多時候,后臺查詢數據數據權限需要通過手動添加SQL來
    的頭像 發表于 06-20 09:59 ?1235次閱讀
    如何<b class='flag-5'>實現</b>基于<b class='flag-5'>Mybatis</b>攔截器<b class='flag-5'>實現</b><b class='flag-5'>數據</b>范圍<b class='flag-5'>權限</b><b class='flag-5'>呢</b>?

    你還在手寫join聯表查詢?MyBatis-Plus這樣寫太香了!

    眾所周知,mybatis plus 封裝的 mapper 不支持 join,如果需要支持就必須自己實現。但是對于大部分的業務場景來說,都需要多表 join,要不然就沒必要采用關系型
    的頭像 發表于 07-07 10:19 ?2663次閱讀
    你還在手寫join聯表查詢?<b class='flag-5'>MyBatis-Plus</b>這樣寫太香了!

    不好意思,list.contain 重該換換了!

    基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、
    的頭像 發表于 09-14 15:50 ?470次閱讀
    不好意思,list.contain <b class='flag-5'>去</b>重該換換了!

    使用mybatis切片實現數據權限控制

    一、使用方式 數據權限控制需要對查詢出的數據進行篩選,對業務入侵最少的方式就是利用mybatis
    的頭像 發表于 07-09 17:26 ?361次閱讀
    使用<b class='flag-5'>mybatis</b>切片<b class='flag-5'>實現</b><b class='flag-5'>數據</b><b class='flag-5'>權限</b><b class='flag-5'>控制</b>
    主站蜘蛛池模板: 久久精品久精品99热| 久久视热频国只有精品| 中文字幕va一区二区三区| 免费看亚洲| 国产一区二区精品视频| bbwxxxx交女警| 中文无码熟妇人妻AV在线| 日日摸夜夜添夜夜爽出水| 久久久久久久国产精品视频| 公开超碰在线视频| s8sp视频高清在线播放| 伊人网青青草| 一个人看的HD免费高清视频| 黑人巨茎大战白人女40CMO| 日韩经典欧美一区二区三区| 久久99re6热在线播放| 国产免费看黄的私人影院| 大稥焦伊人一本dao| 成人免费毛片观看| 成人五级毛片免费播放| 97精品在线观看| adc我们的永久网址| georgielyall装修工| 94色94色永久网站| 99无码熟妇丰满人妻啪啪| 99久久国产综合精品网成人影院| 中国老太太xxx| 97人妻精品全国免费视频| 97se se| asian4you裸模| 国产成人教育视频在线观看| 国产精品成人影院在线观看| yellow日本动漫高清| 亚婷婷洲AV久久蜜臀无码| 视频专区亚洲欧美日韩| 色哟哟网站入口在线观看视频| 日本性xxx| 亚洲国产精品免费观看| 猪蜜蜜网站在线观看电视剧| 99久久免费看国产精品| 高h乱一受多攻男男|