国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

巧用 MyBatis Plus 實現數據權限控制

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

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

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

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

  • 創建注解類
  • 創建攔截器實現InnerInterceptor接口,重寫查詢方法
  • 創建處理類,獲取數據權限 SQL 片段,設置where
  • 將攔截器加到MyBatis-Plus插件中

上代碼(基礎版)

自定義注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

攔截器

import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.*;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SetOperationList;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.SQLException;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
publicclass MyDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {

    /**
     * 數據權限處理器
     */
    private MyDataPermissionHandler dataPermissionHandler;

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
            return;
        }
        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
        mpBs.sql(this.parserSingle(mpBs.sql(), ms.getId()));
    }

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        SelectBody selectBody = select.getSelectBody();
        if (selectBody instanceof PlainSelect) {
            this.setWhere((PlainSelect) selectBody, (String) obj);
        } elseif (selectBody instanceof SetOperationList) {
            SetOperationList setOperationList = (SetOperationList) selectBody;
            List<SelectBody> selectBodyList = setOperationList.getSelects();
            selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
        }
    }

    /**
     * 設置 where 條件
     *
     * @param plainSelect  查詢對象
     * @param whereSegment 查詢條件片段
     */
    private void setWhere(PlainSelect plainSelect, String whereSegment) {

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

攔截器處理器

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

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

import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
publicclass MyDataPermissionHandler {

    /**
     * 獲取數據權限 SQL 片段
     *
     * @param plainSelect  查詢對象
     * @param whereSegment 查詢條件片段
     * @return JSqlParser 條件表達式
     */
    @SneakyThrows(Exception.class)
    public Expression getSqlSegment(PlainSelect plainSelect, String whereSegment) {
        // 待執行 SQL Where 條件表達式
        Expression where = plainSelect.getWhere();
        if (where == null) {
            where = new HexValue(" 1 = 1 ");
        }
        log.info("開始進行權限過濾,where: {},mappedStatementId: {}", where, whereSegment);
        //獲取mapper名稱
        String className = whereSegment.substring(0, whereSegment.lastIndexOf("."));
        //獲取方法名
        String methodName = whereSegment.substring(whereSegment.lastIndexOf(".") + 1);
        Table fromItem = (Table) plainSelect.getFromItem();
        // 有別名用別名,無別名用表名,防止字段沖突報錯
        Alias fromItemAlias = fromItem.getAlias();
        String mainTableName = fromItemAlias == null ? fromItem.getName() : fromItemAlias.getName();
        //獲取當前mapper 的方法
        Method[] methods = Class.forName(className).getMethods();
        //遍歷判斷mapper 的所以方法,判斷方法上是否有 UserDataPermission
        for (Method m : methods) {
            if (Objects.equals(m.getName(), methodName)) {
                UserDataPermission annotation = m.getAnnotation(UserDataPermission.class);
                if (annotation == null) {
                    return where;
                }
                // 1、當前用戶Code
             User user = SecurityUtils.getUser();
                // 查看自己的數據
                 //  = 表達式
                 EqualsTo usesEqualsTo = new EqualsTo();
                 usesEqualsTo.setLeftExpression(new Column(mainTableName + ".creator_code"));
                 usesEqualsTo.setRightExpression(new StringValue(user.getUserCode()));
                 returnnew AndExpression(where, usesEqualsTo);
            }
        }
        //說明無權查看,
        where = new HexValue(" 1 = 2 ");
        return where;
    }

}

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

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

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    // 添加數據權限插件
    MyDataPermissionInterceptor dataPermissionInterceptor = new MyDataPermissionInterceptor();
    // 添加自定義的數據權限處理器
    dataPermissionInterceptor.setDataPermissionHandler(new MyDataPermissionHandler());
    interceptor.addInnerInterceptor(dataPermissionInterceptor);
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
}

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

@Bean
public MyDataPermissionInterceptor myInterceptor(MybatisPlusInterceptor mybatisPlusInterceptor) {
    MyDataPermissionInterceptor sql = new MyDataPermissionInterceptor();
    sql.setDataPermissionHandler(new MyDataPermissionHandler());
    List<InnerInterceptor> list = new ArrayList<>();
    // 添加數據權限插件
    list.add(sql);
    // 分頁插件
    mybatisPlusInterceptor.setInterceptors(list);
    list.add(new PaginationInnerInterceptor(DbType.MYSQL));
    return sql;
}

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

使用方式

在mapper層添加注解即可。

@UserDataPermission
List<CustomerAllVO> selectAllCustomerPage(IPage<CustomerAllVO> page, @Param("customerName")String customerName);

進階版

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

進階版 解決兩個問題:

  • 加了角色,用角色決定范圍
  • 解決不是mapper層自定義sql查詢問題。

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

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

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

xxxxService.list(Wrapper<T> queryWrapper)
xxxxService.page(new Page<>(),Wrapper<T> queryWrapper)

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

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

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

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

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;

publicinterface DataPermissionMapper<T> extends BaseMapper<T> {

    /**
     * 根據 ID 查詢
     *
     * @param id 主鍵ID
     */
    @Override
    @UserDataPermission
    T selectById(Serializable id);

    /**
     * 查詢(根據ID 批量查詢)
     *
     * @param idList 主鍵ID列表(不能為 null 以及 empty)
     */
    @Override
    @UserDataPermission
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /**
     * 查詢(根據 columnMap 條件)
     *
     * @param columnMap 表字段 map 對象
     */
    @Override
    @UserDataPermission
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根據 entity 條件,查詢一條記錄
     *
     * @param queryWrapper 實體對象封裝操作類(可以為 null)
     */
    @Override
    @UserDataPermission
    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根據 Wrapper 條件,查詢總記錄數
     *
     * @param queryWrapper 實體對象封裝操作類(可以為 null)
     */
    @Override
    @UserDataPermission
    Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

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

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

    /**
     * 根據 Wrapper 條件,查詢全部記錄
     * <p>注意: 只返回第一個字段的值</p>
     *
     * @param queryWrapper 實體對象封裝操作類(可以為 null)
     */
    @Override
    @UserDataPermission
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根據 entity 條件,查詢全部記錄(并翻頁)
     *
     * @param page         分頁查詢條件(可以為 RowBounds.DEFAULT)
     * @param queryWrapper 實體對象封裝操作類(可以為 null)
     */
    @Override
    @UserDataPermission
    <E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根據 Wrapper 條件,查詢全部記錄(并翻頁)
     *
     * @param page         分頁查詢條件
     * @param queryWrapper 實體對象封裝操作類
     */
    @Override
    @UserDataPermission
    <E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

解決角色控制查詢范圍

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

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

  • 建立范圍枚舉
  • 建立角色枚舉以及范圍關聯關系
  • 重寫攔截器處理方法

范圍枚舉

@AllArgsConstructor
@Getter
public enum DataScope {
    // Scope 數據權限范圍 : ALL(全部)、DEPT(部門)、MYSELF(自己)
    ALL("ALL"),
    DEPT("DEPT"),
    MYSELF("MYSELF");
    private String name;
}

角色枚舉

@AllArgsConstructor
@Getter
publicenum DataPermission {

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

    private String name;
    private String code;
    private DataScope scope;


    public static String getName(String code) {
        for (DataPermission type : DataPermission.values()) {
            if (type.getCode().equals(code)) {
                return type.getName();
            }
        }
        returnnull;
    }

    public static String getCode(String name) {
        for (DataPermission type : DataPermission.values()) {
            if (type.getName().equals(name)) {
                return type.getCode();
            }
        }
        returnnull;
    }

    public static DataScope getScope(Collection<String> code) {
        for (DataPermission type : DataPermission.values()) {
            for (String v : code) {
                if (type.getCode().equals(v)) {
                    return type.getScope();
                }
            }
        }
        return DataScope.MYSELF;
    }
}

重寫攔截器處理類 MyDataPermissionHandler

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

import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
publicclass MyDataPermissionHandler {

    private RemoteRoleService remoteRoleService;
    private RemoteUserService remoteUserService;


    /**
     * 獲取數據權限 SQL 片段
     *
     * @param plainSelect  查詢對象
     * @param whereSegment 查詢條件片段
     * @return JSqlParser 條件表達式
     */
    @SneakyThrows(Exception.class)
    public Expression getSqlSegment(PlainSelect plainSelect, String whereSegment) {
        remoteRoleService = SpringUtil.getBean(RemoteRoleService.class);
        remoteUserService = SpringUtil.getBean(RemoteUserService.class);

        // 待執行 SQL Where 條件表達式
        Expression where = plainSelect.getWhere();
        if (where == null) {
            where = new HexValue(" 1 = 1 ");
        }
        log.info("開始進行權限過濾,where: {},mappedStatementId: {}", where, whereSegment);
        //獲取mapper名稱
        String className = whereSegment.substring(0, whereSegment.lastIndexOf("."));
        //獲取方法名
        String methodName = whereSegment.substring(whereSegment.lastIndexOf(".") + 1);
        Table fromItem = (Table) plainSelect.getFromItem();
        // 有別名用別名,無別名用表名,防止字段沖突報錯
        Alias fromItemAlias = fromItem.getAlias();
        String mainTableName = fromItemAlias == null ? fromItem.getName() : fromItemAlias.getName();
        //獲取當前mapper 的方法
        Method[] methods = Class.forName(className).getMethods();
        //遍歷判斷mapper 的所以方法,判斷方法上是否有 UserDataPermission
        for (Method m : methods) {
            if (Objects.equals(m.getName(), methodName)) {
                UserDataPermission annotation = m.getAnnotation(UserDataPermission.class);
                if (annotation == null) {
                    return where;
                }
                // 1、當前用戶Code
                User user = SecurityUtils.getUser();
                // 2、當前角色即角色或角色類型(可能多種角色)
                Set<String> roleTypeSet = remoteRoleService.currentUserRoleType();
                
                DataScope scopeType = DataPermission.getScope(roleTypeSet);
                switch (scopeType) {
                    // 查看全部
                    case ALL:
                        return where;
                    case DEPT:
                        // 查看本部門用戶數據
                        // 創建IN 表達式
                        // 創建IN范圍的元素集合
                        List<String> deptUserList = remoteUserService.listUserCodesByDeptCodes(user.getDeptCode());
                        // 把集合轉變為JSQLParser需要的元素列表
                        ItemsList deptList = new ExpressionList(deptUserList.stream().map(StringValue::new).collect(Collectors.toList()));
                        InExpression inExpressiondept = new InExpression(new Column(mainTableName + ".creator_code"), deptList);
                        returnnew AndExpression(where, inExpressiondept);
                    case MYSELF:
                        // 查看自己的數據
                        //  = 表達式
                        EqualsTo usesEqualsTo = new EqualsTo();
                        usesEqualsTo.setLeftExpression(new Column(mainTableName + ".creator_code"));
                        usesEqualsTo.setRightExpression(new StringValue(user.getUserCode()));
                        returnnew AndExpression(where, usesEqualsTo);
                    default:
                        break;
                }
            }

        }
        //說明無權查看,
        where = new HexValue(" 1 = 2 ");
        return where;
    }
}

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

  • 記得把攔截器加到MyBatis-Plus的插件中,確保生效。
  • 要有一個業務賽選標識字段, 這里用的創建人 creator_code, 也可以用dept_code 等等。
責任編輯:武曉燕 來源: 碼猿技術專欄
相關推薦

2025-05-26 03:20:00

SpringMyBatis數據權限

2024-02-28 09:35:52

2021-09-27 07:56:41

MyBatis Plu數據庫批量插入

2024-12-20 16:49:15

MyBatis開發代碼

2022-09-29 10:06:56

SQLMySQL服務端

2024-07-31 09:56:20

2024-10-22 08:47:03

2021-02-01 00:04:13

Dictionary數據批量

2023-06-07 08:08:37

MybatisSpringBoot

2010-09-01 16:43:26

Squid ACLSquid訪問列表Squid

2023-12-13 12:20:36

SpringMySQL數據源

2023-11-02 09:03:24

權限管理系統

2020-05-25 16:05:17

前端限控制設分離

2022-01-07 07:29:08

Rbac權限模型

2023-07-29 22:02:06

MyBatis數據庫配置

2024-03-01 19:35:54

Mybatis開發

2021-01-05 05:36:39

設計Spring Boot填充

2025-02-13 07:59:13

2011-03-03 15:02:22

proftpd權限

2011-03-03 11:13:11

Pureftpd
點贊
收藏

51CTO技術棧公眾號

av在线播放av| 国产欧美激情| 亚洲男人天天操| 成全电影播放在线观看国语| 色婷婷综合久久久中文一区二区| 黄页网址大全在线播放| 日韩欧美在线国产| 男人和女人做事情在线视频网站免费观看 | 美乳视频一区二区| 精品一区二区在线观看| 欧美 丝袜 自拍 制服 另类| 国产精品久久久久精k8| 激情在线视频| 国产一区二区三区在线播放免费观看| 成人污版视频| 亚洲最大成人网色| 久久国产精品色婷婷| 日韩精品一区二区三区久久| 亚洲成人一区二区| 九九精品调教| 日韩经典一区| 久久久久久网址| 日本欧美韩国国产| 91国偷自产一区二区使用方法| 在线观影网站| 91精品国产一区二区三区| 黄动漫在线看| 欧美一区二区视频观看视频| 99re免费视频精品全部| 在线观看免费播放网址成人| 亚洲一二三四区不卡| aaa日本高清在线播放免费观看| 综合av色偷偷网| 老牛国内精品亚洲成av人片| 欧美日韩亚洲在线 | 成人春色激情网| 黄色免费成人| 91网站在线观看免费| 国产精品网站导航| а天堂8中文最新版在线官网| 亚洲成人精品av| 成人爽a毛片| 国产在线精品一区二区中文| 不卡电影一区二区三区| 亚洲精品套图| 色吧影院999| 欧美1级日本1级| 午夜欧美福利视频| 7777精品伊人久久久大香线蕉完整版 | 色网站在线看| 一区二区三欧美| 精品中文一区| 欧美黄色免费网址| 亚洲成年人影院| 国产成年精品| 欧美极品美女视频网站在线观看免费| 精品96久久久久久中文字幕无| 国产在线拍揄自揄拍无码| 久久综合久久综合久久综合| 在线播放色视频| 亚洲国产精品福利| 人人狠狠综合久久亚洲婷婷| 99精品一级欧美片免费播放| 久久综合久久综合亚洲| 久久久久免费看黄a片app| 欧美视频在线观看 亚洲欧| 国产盗摄一区二区| 国产精品久久久久久网站| 美女视频黄 久久| 91精品大全| 国产精品成人在线| 一区在线观看视频| ww久久综合久中文字幕| 香蕉视频免费版| 欧美精品日韩一区| 亚洲精品一二三区区别| 99视频精品免费| 日韩天堂在线视频| 日本视频一区二区三区| 免费黄网站在线| 日本sm极度另类视频| 国产精品99久久久久| 免费黄色片在线观看| 91精品久久久久久久久久另类| av电影在线观看一区| 深夜成人影院| 日韩影院一区| 91精品国产91久久久久久最新毛片| 网曝91综合精品门事件在线| www欧美激情| 正在播放国产一区| 国产成人免费视频网站高清观看视频| 国产精品视频二区三区| 成人免费xxxxx在线观看| 欧美激情一区不卡| 成人在线免费| 116极品美女午夜一级| 亚洲日本中文字幕| 91美女视频网站| 伊人久久大香| 亚州福利视频| 青青草一区二区| 国产99精品在线观看| 日韩精彩视频在线观看| 国产欧美高清视频在线| 成人毛片免费| 亚洲日本伦理| 久草视频国产在线| 国产97免费视| 欧美成人女星排名| 九九精品视频在线看| 精品国产露脸精彩对白| 天堂影院一区二区| 麻豆av在线导航| 99一区二区三区| 欧美日韩亚洲精品一区二区三区| 欧美一区三区| 1024在线视频| 国产精品久久九九| 欧美少妇bbb| 国产剧情久久久久久| 在线观看久久久久久| 国产欧美精品xxxx另类| 亚洲免费伊人电影| 成人动态视频| 国产又大又黄又猛| 97视频色精品| 一本色道亚洲精品aⅴ| 欧美日韩三级| 日韩欧美另类一区二区| 黄页免费在线观看视频| 欧美自拍视频在线| 亚洲成av人片在线观看| 免费亚洲网站| 在线女人免费视频| 男人插曲女人视频免费| 成人动漫网站在线观看| 精品国产欧美一区二区| 国产一区二区在线看| 狠狠一区二区三区| 日日躁夜夜躁人人揉av五月天| 国产精品播放| 亚洲国产福利在线| 一区免费观看视频| 亚洲人metart人体| 日韩一区二区三区在线免费观看| 欧美高清中文字幕| 26uuu国产精品视频| 在线国产电影不卡| 日韩精品欧美精品| 国内福利写真片视频在线 | 精品精品国产高清a毛片牛牛 | 亚洲欧洲偷拍精品| 亚洲精品第1页| 欧美久久九九| 亚洲不卡系列| 日本高清视频网站www| 欧美日韩亚洲在线| 日韩亚洲欧美中文在线| 一本到一区二区三区| 韩国av一区二区三区四区| 北条麻妃国产九九九精品小说| 自拍亚洲图区| 香港三级经典全部种子下载| 日本一区二区在线视频观看| 日韩在线观看网站| 欧美午夜电影在线| 久久久久88色偷偷免费| 最新国产拍偷乱拍精品| 91久久精品无嫩草影院| 幼a在线观看| 天天槽夜夜槽| 日本xxx免费| 成人av播放| 色噜噜狠狠狠综合曰曰曰88av | 国产精品91久久久| 91精品国产全国免费观看| 91视频观看视频| 欧美日韩四区| 亚洲精品大片| 黄色av网址在线免费观看| 欧美少妇在线观看| 亚洲最大福利网| 日韩视频一区在线| 日韩欧美你懂的| 17c精品麻豆一区二区免费| 久久婷婷麻豆| 欧美美女在线| av成人综合| 涩涩涩视频在线观看| 欧美性猛交xxx乱大交3蜜桃| 激情亚洲综合网| 激情五月宗合网| 国产偷国产偷亚洲高清97cao| 青青在线视频一区二区三区| 亚洲天堂2020| 亚洲经典中文字幕| 欧美日韩精品电影| 日韩欧美亚洲国产一区| 9i在线看片成人免费|