mybatis sql拦截器配置多租户(极简版)

6 篇文章 0 订阅
3 篇文章 0 订阅

               任务目标:

                        利用sql拦截器对sql进行二次修改,自动携带租户查询

                废话少说,直接上代码

                 首先注册sql拦截器SqlInterceptor找不到没关系,这是我自己定义的

package com.example.pidog.config;

import com.example.pidog.interceptor.SqlInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.List;




@Configuration
public class MybatisConfig {

    @Autowired
    private List<SqlSessionFactory> sqlSessionFactoryList;

    @PostConstruct
    public void addMySqlInterceptor() {
        SqlInterceptor interceptor = new SqlInterceptor();
        for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
            sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
        }
    }
}
package com.example.pidog.interceptor;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.example.pidog.handlers.TenantSqlHandle;
import com.example.pidog.util.StaticMethodGetBean;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.RowBounds;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;


@Setter
@Accessors(chain = true)
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class SqlInterceptor implements Interceptor {
  
    @SuppressWarnings("unchecked")
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
        Object parameter = invocation.getArgs()[1];
        System.out.println(parameter);
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        String id = mappedStatement.getId();
        if (filterMethodById(id)) {
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = boundSql.getSql();//注意 这里是最终执行的sql 也就是我们接下来需要修改的sql
            sql = new TenantSqlHandle().tenantHandle(id,mappedStatement.getSqlCommandType(),sql);
            Field field = boundSql.getClass().getDeclaredField("sql");
            field.setAccessible(true);
            field.set(boundSql, sql);
        }
        return invocation.proceed();
    }





    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    /**
     * 根据获取到执行 id 找到对应的方法
     *
     * @param id 根据 MappedStatement 获取到的 id 属性
     * @return 是否是 searchByQuery 方法
     */
    private boolean filterMethodById(String id) {
        String[] split = id.split("\\.");
        return CollectionUtil.contains(CollectionUtil.newArrayList("selectById", "updateById"), split[split.length - 1]);
    }
}
package com.example.pidog.handlers;

import com.example.pidog.threadLocal.TenantThreadLocal;
import javafx.util.Pair;
import org.apache.ibatis.mapping.SqlCommandType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Locale;

/**
 * 租户sql处理器
 */
public class TenantSqlHandle {

       //这里没什么好说的 拼接修改sql字符串就是了
    public String tenantHandle(String id, SqlCommandType sqlCommandType, String sql) {
        if (TenantThreadLocal.getCurrentDept() == null) {
            return sql;
        }
        Pair<String, String> dept = TenantThreadLocal.getCurrentDept();
        if (sql.contains("WHERE")) {
            int whereIndexOf = sql.indexOf("WHERE");
            StringBuilder sb = new StringBuilder(sql);
            sb.insert(whereIndexOf + "WHERE".length(), " " + dept.getKey() + "=" + dept.getValue() + " and ");
            return sb.toString();
        }
        return sql;
    }
}
package com.example.pidog.threadLocal;

import com.example.pidog.domain.User;
import javafx.util.Pair;

public class TenantThreadLocal {
    /**
     * 构造函数私有
     */
    private TenantThreadLocal() {
    }

    private static final ThreadLocal<Pair<String,String>> TENANT_THREAD_LOCAL =
            new ThreadLocal<>();

    /**
     * 清除租户信息
     */
    public static void clear() {
        TENANT_THREAD_LOCAL.remove();
    }

    /**
     * 存储当前租户
     */
    public static void set(Pair<String, String> dept) {
        TENANT_THREAD_LOCAL.set(dept);
    }

    /**
     * 获取当前租户id
     */
    public static Pair<String, String> getCurrentDept() {
        return TENANT_THREAD_LOCAL.get();
    }
}

最后将租户的数据保存在线程变量里

接下来去请求拦截器里修改一下,自动从请求头取出租户id,放在线程变量中。

package com.example.pidog.interceptor;

import com.example.pidog.annotation.authority.AuthUtil;
import com.example.pidog.annotation.authority.CrossAuth;
import com.example.pidog.annotation.sql.CrossTenant;
import com.example.pidog.annotation.sql.MultiTenant;
import com.example.pidog.config.JwtConfig;
import com.example.pidog.threadLocal.TenantThreadLocal;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.SignatureException;
import javafx.util.Pair;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Objects;

@Component
public class TokenInterceptor implements HandlerInterceptor {

    @Resource
    private AuthUtil authUtil;

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws SignatureException {
        Method method = ((HandlerMethod) handler).getMethod();
        if (method.isAnnotationPresent(CrossAuth.class)) {
            return true;
        }
        Claims claims = authUtil.examineToken(request);
        if (method.isAnnotationPresent(CrossTenant.class)) { //如果存在越过租户注解
            return true; //直接越过
        }
        if (claims.get("tenantId") == null) { //如果租户id为空
            throw new SignatureException("参数非法,权限校验失败");//抛出异常
        }
        TenantThreadLocal.set(new Pair<>("tenantId", claims.get("tenantId").toString())); //向线程变量中添加租户信息
        return true;
    }
}

这是我的请求拦截器 核心在authUtil中

package com.example.pidog.annotation.authority;

import com.example.pidog.config.JwtConfig;
import com.example.pidog.domain.User;
import com.example.pidog.threadLocal.UserThreadLocal;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.SignatureException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

@Component
public class AuthUtil {
    @Resource
    private JwtConfig jwtConfig;


    /**
     * 验证token合法性
     *
     * @param request
     */
    public Claims examineToken(HttpServletRequest request) {
        String token = request.getHeader(jwtConfig.getHeader());
        if (StringUtils.isEmpty(token)) {
            token = request.getParameter(jwtConfig.getHeader());
        }
        if (StringUtils.isEmpty(token)) {
            throw new SignatureException(jwtConfig.getHeader() + "不能为空");
        }
        try {
            Claims claims = jwtConfig.getTokenClaim(token);
            if (claims == null || jwtConfig.isTokenExpired(claims.getExpiration())) {
                throw new SignatureException(jwtConfig.getHeader() + "失效,请重新登录。");
            }
            if (claims.get("userId") == null) {
                throw new SignatureException(jwtConfig.getHeader() + "失效,请重新登录。");
            }
            if (claims.get("nickName") == null) {
                throw new SignatureException(jwtConfig.getHeader() + "失效,请重新登录。");
            }
            User user = new User();
            user.setUserId(claims.get("userId", Integer.class).longValue());
            user.setNickName(claims.get("nickName", String.class));
            if(!jwtConfig.comparisonCache(user.getUserId(), token)){
                throw new SignatureException(jwtConfig.getHeader() + "失效,请重新登录。");
            };
            UserThreadLocal.set(user);
            return claims;
        } catch (Exception e) {
            e.printStackTrace();
            throw new SignatureException(jwtConfig.getHeader() + "失效,请重新登录。");
        }
    }
}

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值