1. mybais拦截器
最近看公司的代码,发现mapper里面方法上有的方法添加了
@DAtaPermission
注解,查遍了也不知道是哪个框架里面的东西。问了带我的师傅,人给我回复 Intercept。下班后默默的查了起来,才知道是mybatis里面的插件功能。故查了查资料,看了看别的老哥写的东西 ,感谢感谢 下面的几篇文章
- 基于mybatis拦截器实现数据权限
- mybatis笔记-MappedStatement
-
MyBatis 插件之拦截器(Interceptor)
感谢感谢。如果想看详细的关于mybatis拦截器的工作原理和各个组件的功能,请看上面的这几篇文章。
2. 实现思路
我这里是做一个简单的权限管理 所有我在注解里面直接写好了角色,正常的应该是 在数据库里面查到。也可以根据返回的权限的sql和之前的sql做关联查询(加强sql)
- 定义权限注解
- 自定义拦截器
- 得到标注注解的方法,
- 根据注解里面的角色
- 在mapper里面传递当前登录的用户
- 判断是不是 相等,相等就放行,不相等就抛出异常(如果在springMvc中就统一处理这个异常,然后统一返回)
3. 代码
可能 bia ji 给一大堆代码,有点迷糊。慢慢看,自己做个例子就好了,我也是参考上面的几个例子的,要是不行debug运行,一步一步的看。遇到不知道的对象 就去查,不要猜
实体类(这个实体类就是模拟的,不要当真)
- 对应数据里里面的对象
package mybatisInterceptTest.eneity;
import java.io.Serializable;
import java.math.BigDecimal;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class TTest implements Serializable {
private Integer id;
private String name;
private Double age;
private static final long serialVersionUID = 1L;
}
2 数据权限对象。模拟当前登录的用户的信息
package mybatisInterceptTest.eneity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* @program: jdk8Test
* @description:
* @author: liuchen
**/
@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class DataAuth {
private Integer userId;
private String userRole;
}
自定义权限注解
package mybatisInterceptTest.intercept;
import java.lang.annotation.*;
/**
* @program: jdk8Test
* @description:
* @author: liuchen
**/
@Target({ElementType.METHOD}) //表示注解的使用范围
@Retention(RetentionPolicy.RUNTIME) //注解的保存时间
@Documented //文档显示
@Inherited //可以继承
public @interface MyIntercept {
/*
角色
*/
public String role() default "";
}
异常类
package mybatisInterceptTest.exception;
import lombok.AllArgsConstructor;
/**
* @program: jdk8Test
* @description:
* @author: liuchen
**/
public class DataAuthException extends RuntimeException {
public DataAuthException() {
}
public DataAuthException(String message) {
super(message);
}
public DataAuthException(String message, Throwable cause) {
super(message, cause);
}
public DataAuthException(Throwable cause) {
super(cause);
}
public DataAuthException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
Mapper接口
package mybatisInterceptTest.mapper;
import mybatisInterceptTest.eneity.DataAuth;
import mybatisInterceptTest.eneity.TTest;
import mybatisInterceptTest.intercept.MyIntercept;
import org.apache.ibatis.annotations.Param;
public interface TTestDao {
int deleteByPrimaryKey(Integer id);
int insert(TTest record);
@MyIntercept(role = "admin") //表示这个访问这个方法需要 admin 角色
TTest selectByPrimaryKey(@Param("id") Integer id, @Param("dataAuth") DataAuth dataAuth);
}
xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatisInterceptTest.mapper.TTestDao">
<resultMap id="BaseResultMap" type="mybatisInterceptTest.eneity.TTest">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="age" jdbcType="DECIMAL" property="age" />
</resultMap>
<sql id="Base_Column_List">
id, `name`, age
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultType="TTest">
select
<include refid="Base_Column_List" />
from t_test
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from t_test
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="mybatisInterceptTest.eneity.TTest" useGeneratedKeys="true">
insert into t_test (`name`, age)
values (#{name,jdbcType=VARCHAR}, #{age,jdbcType=DECIMAL})
</insert>
</mapper>
mybatis 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 根标签 -->
<configuration>
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/t_test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
<settings>
<setting name="logPrefix" value="dao." />
</settings>
<typeAliases>
<!--type:实体类的全路径。alias:别名,通常首字母大写-->
<!--<typeAlias type="com.zpc.mybatis.pojo.User" alias="User"/>-->
<package name="mybatisInterceptTest.eneity"/>
</typeAliases>
<plugins>
<plugin interceptor="mybatisInterceptTest.intercept.AuthLcpInterceptor">
</plugin>
</plugins>
<!-- 环境,可以配置多个,default:指定采用哪个环境 -->
<environments default="test">
<!-- id:唯一标识 -->
<environment id="test">
<!-- 事务管理器,JDBC类型的事务管理器 -->
<transactionManager type="JDBC" />
<!-- 数据源,池类型的数据源 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/t_test" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
<environment id="development">
<!-- 事务管理器,JDBC类型的事务管理器 -->
<transactionManager type="JDBC" />
<!-- 数据源,池类型的数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}" /> <!-- 配置了properties,所以可以直接引用 -->
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/TTestDao.xml" />
</mappers>
</configuration>
pom.xml文件
<dependencies>
<!--java connection-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
lo4j配置
log4j.rootLogger=DEBUG,INFO,A1
log4j.logger.org.apache=DEBUG
mybatisInterceptTest.eneity=DEBUG
mybatisInterceptTest.intercept=INFO
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
自定义的拦截器
这里面写的其实很简单,拦截执行的sql,得到方法上面的注解,看有没有权限,有就放行,没有就抛出异常。
package mybatisInterceptTest.intercept;
import lombok.extern.slf4j.Slf4j;
import mybatisInterceptTest.eneity.DataAuth;
import mybatisInterceptTest.exception.DataAuthException;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Properties;
/**
* @program: jdk8Test
* @description:
* @author: liuchen
**/
@Slf4j
@Intercepts({@Signature(method = "query", type = Executor.class,
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
})})
public class AuthLcpInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object result = null;
/**
* 思路:
* 1:找到标注注解的方法
* 2: 看当前的用户有没有权限
* 3: 有,放行。没有,就抛出异常
*/
//[mappedStatement,参数,rowBounds]
Object[] args = invocation.getArgs();
//得到mapperStatement(xml文件中的select ,update,delete,insert 对应的标签)
MappedStatement mappedStatement = (MappedStatement) args[0];
MyIntercept myIntercept = getAnnotationMethod(mappedStatement);
if (myIntercept == null) {
return invocation.proceed();
}
//得到角色
String role = myIntercept.role();
//得到参数也就是mapper接口自己写的方法里面的参数,
MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) args[1];
//得到dataAuth
DataAuth dataAuth = (DataAuth) paramMap.get("dataAuth");
//用户id
Integer userId = dataAuth.getUserId();
//用户角色
String userRole = dataAuth.getUserRole();
if (!role.equals(userRole)) {
throw new DataAuthException("当前用户没有权限");
}
//得到标注注解的方法
result = invocation.proceed();
return result;
}
/**
* 得到标注注解的方法,
*
* @return 返回注解
*/
private MyIntercept getAnnotationMethod(MappedStatement statement) throws ClassNotFoundException {
// namespace + xml文件中对应的id
String id = statement.getId();
String className = id.substring(0, id.lastIndexOf("."));
String methodName = id.substring(id.lastIndexOf(".") + 1, id.length());
Class<?> aClass = Class.forName(className);
Method[] declaredMethods = aClass.getDeclaredMethods();
MyIntercept annotation = null;
for (Method declaredMethod : declaredMethods) {
declaredMethod.setAccessible(true);
//方法名相同,并且注解是myintercept
if (methodName.equals(declaredMethod.getName()) && declaredMethod.isAnnotationPresent(MyIntercept.class)) {
log.info("------> {},{},{},{}", declaredMethod.getModifiers(), declaredMethod.getName(), declaredMethod.getParameters(), declaredMethod.getReturnType());
annotation = declaredMethod.getAnnotation(MyIntercept.class);
}
}
return annotation;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
private String getUserRoleById(Integer id) {
return "admin";
}
}
测试
package mybatisInterceptTest.mapper;
import lombok.extern.slf4j.Slf4j;
import mybatisInterceptTest.eneity.DataAuth;
import mybatisInterceptTest.eneity.TTest;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
@Slf4j
public class TTestDaoTest {
public TTestDao testDao;
public SqlSession sqlSession;
@Before
public void setUp() throws Exception {
// mybatis-config.xml
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream is = Resources.getResourceAsStream(resource);
// 构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 获取sqlSession
sqlSession = sqlSessionFactory.openSession();
this.testDao = sqlSession.getMapper(TTestDao.class);
}
@Test
public void deleteByPrimaryKey() {
log.info("{}",testDao.deleteByPrimaryKey(1));
}
@Test
public void insert() {
TTest aaa = new TTest(null, "bbb", 12.345);
log.info("{}",testDao.insert(aaa));
sqlSession.commit();
}
/**
* 这个是主测试方法,上面的俩@Test 没意思
*/
@Test
public void selectByPrimaryKey() {
log.info("{}",testDao.selectByPrimaryKey(3,new DataAuth(1,"admin")));
}
}
结果
如果是 admin 就没有问题
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL9MWbixGaykVMsdkYoJlMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL3AzMxMzNwUTM3ETNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
如果是 customer
结果
如果是SpringMVC的话,异常统一处理,返回前端就好