跳至主要內容

全局拦截器

xuejmnet大约 8 分钟

全局拦截器

easy-qeury默认提供了拦截器,支持项目在工程化的时候可以批量拦截sql表达式

  • 自动填充id,自动填充创建人,自动填充创建时间,自动填充修改人,自动填充修改时间等
  • 请求租户id自动条件过滤
  • 用户权限比如领导可以看看和操作本部门和下属部门,部门只能看看和操作本部门,用户只能看和操作自己的数据
  • 比如/company/**的web请求路径将当前企业id:companyId作为条件限制只允许操作当前企业

如果不知道如何使用可以查看 实战对象设计

名称描述
EntityInterceptor对象拦截器用于对象插入前和修改前进行对象拦截
PredicateFilterInterceptor条件拦截器用户在查询,修改,删除的时候可以通过条件拦截来动态构建添加条件如:租户id
UpdateSetInterceptor更新列拦截器用户在更新update表达式的时候可以通过当前拦截器自动追加set列操作
UpdateEntityColumnInterceptor对象列的更新拦截器EntityInterceptor主要作用是给对象赋值,但是给对象赋值的的时候比如updateTime在更新的时候不一定会更新这个字段,这个时候这个拦截器就会在最后可以进行处理添加对updateTime进行设置

Interceptor Api

方法默认值/实现描述
order100用于对拦截器进行顺序排序执行 越小越先执行
enabletrue是否默认添加到表达式中前提是apply为true,true:默认添加,false:不添加可以通过ThreadLocal动态设置,或者调用useInterceptor(name)来显式使用
name拦截器名称需要自己实现,默认可以使用类名
apply哪些对象允许采用当前拦截器默认可以才用是否为某个接口的实现Interface.class.isAssignableFrom(entityClass);

说明!!!

创建完拦截器后需要配置到QueryConfiguration,如果你是springboot并且是默认easy-query只需要添加@Component如果是solon那么可以查看配置或配置到所有数据源
如果您是自行构建的easy-query需要自行添加拦截器

QueryRuntimeContext runtimeContext = easyQuery.getRuntimeContext();
QueryConfiguration configuration = runtimeContext.getQueryConfiguration();
configuration.applyInterceptor(new MyEntityInterceptor());

demo数据

@Data
@Table("t_topic_interceptor")
public class TopicInterceptor {
    @Column(primaryKey = true)
    private String id;
    private Integer stars;
    private String title;
    @UpdateIgnore
    private LocalDateTime createTime;
    @UpdateIgnore
    private String createBy;
    private LocalDateTime updateTime;
    private String updateBy;
    @UpdateIgnore
    private String tenantId;
}

EntityInterceptor

Api

方法默认实现描述
configureInsert配置自动插入时的值:创建时间,创建人
configureUpdate配置更新是需要修改的值:修改时间,修改人

模拟当前用户租户对象

public class CurrentUserHelper {
    private static String userId;
    public static void setUserId(String userId){
        CurrentUserHelper.userId=userId;
    }
    public static String getUserId(){
        return userId;
    }
    private static String tenantId;
    public static void setTenantId(String tenantId){
        CurrentUserHelper.tenantId=tenantId;
    }
    public static String getTenantId(){
        return tenantId;
    }
}

拦截器


/**
 * create time 2023/4/3 21:13
 * 如果是spring项目添加@Component,如果是非spring项目直接添加到QueryConfiguration.applyInterceptor
 *
 * @author xuejiaming
 */
public class MyEntityInterceptor implements EntityInterceptor {
    @Override
    public void configureInsert(Class<?> entityClass, EntityInsertExpressionBuilder entityInsertExpressionBuilder, Object entity) {
        TopicInterceptor topicInterceptor = (TopicInterceptor) entity;
        if (topicInterceptor.getCreateTime() == null) {
            topicInterceptor.setCreateTime(LocalDateTime.now());
        }
        if (topicInterceptor.getCreateBy() == null) {
            topicInterceptor.setCreateBy(CurrentUserHelper.getUserId());
        }
        if (topicInterceptor.getUpdateTime() == null) {
            topicInterceptor.setUpdateTime(LocalDateTime.now());
        }
        if (topicInterceptor.getUpdateBy() == null) {
            topicInterceptor.setUpdateBy(CurrentUserHelper.getUserId());
        }
    }

    @Override
    public void configureUpdate(Class<?> entityClass, EntityUpdateExpressionBuilder entityUpdateExpressionBuilder, Object entity) {

        TopicInterceptor topicInterceptor = (TopicInterceptor) entity;
        topicInterceptor.setUpdateTime(LocalDateTime.now());
        topicInterceptor.setUpdateBy(CurrentUserHelper.getUserId());
    }

    @Override
    public String name() {
        return "MyEntityInterceptor";
    }

    @Override
    public boolean apply(Class<?> entityClass) {
        return TopicInterceptor.class.isAssignableFrom(entityClass);
    }
}
//租户拦截器
public class MyTenantInterceptor implements EntityInterceptor,PredicateFilterInterceptor {
    @Override
    public String name() {
        return "MyTenantInterceptor";
    }

    @Override
    public boolean apply(Class<?> entityClass) {
        return TopicInterceptor.class.isAssignableFrom(entityClass);
    }

    @Override
    public void configure(Class<?> entityClass, LambdaEntityExpressionBuilder lambdaEntityExpressionBuilder, WherePredicate<Object> wherePredicate) {
        if(CurrentUserHelper.getUserId()!=null){
            wherePredicate.eq("tenantId", CurrentUserHelper.getTenantId());
        }
    }

    @Override
    public void configureInsert(Class<?> entityClass, EntityInsertExpressionBuilder entityInsertExpressionBuilder, Object entity) {

        TopicInterceptor topicInterceptor = (TopicInterceptor) entity;
        if (topicInterceptor.getTenantId() == null) {
            topicInterceptor.setTenantId(CurrentUserHelper.getTenantId());
        }
    }
}

测试代码

//设置当前租户和当前用户
CurrentUserHelper.setUserId("xiaoming");
CurrentUserHelper.setTenantId("abc");
TopicInterceptor topicInterceptor = new TopicInterceptor();
topicInterceptor.setId("123");
topicInterceptor.setTitle("123");
topicInterceptor.setStars(123);
long l = easyQuery.insertable(topicInterceptor).executeRows();

==> Preparing: INSERT INTO t_topic_interceptor (`id`,`stars`,`title`,`create_time`,`create_by`,`update_time`,`update_by`,`tenant_id`) VALUES (?,?,?,?,?,?,?,?) 
==> Parameters: 123(String),123(Integer),123(String),2023-04-03T21:28:32.643(LocalDateTime),xiaoming(String),2023-04-03T21:28:32.643(LocalDateTime),xiaoming(String),abc(String)
<== Total: 1

TopicInterceptor topicInterceptor1 = easyQuery.queryable(TopicInterceptor.class).whereId("123").firstOrNull();

==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time`,t.`create_by`,t.`update_time`,t.`update_by`,t.`tenant_id` FROM t_topic_interceptor t WHERE t.`id` = ? LIMIT 1
==> Parameters: 123(String)
<== Total: 1


CurrentUserHelper.setUserId("xiaoming1");
long l1 = easyQuery.updatable(topicInterceptor1).executeRows();

==> Preparing: UPDATE t_topic_interceptor SET `stars` = ?,`title` = ?,`update_time` = ?,`update_by` = ? WHERE `id` = ?
==> Parameters: 123(Integer),123(String),2023-04-03T21:28:32.670(LocalDateTime),xiaoming1(String),123(String)
<== Total: 1

插入的时候确定了自动填充,实际项目中可以通过接口来限制,更新的时候也会自动更新时间,但是如果是表达式更新那么还是原先的操作并不会更新

 long l2 = easyQuery.updatable(TopicInterceptor.class).set(TopicInterceptor::getTitle, topicInterceptor2.getTitle())
                .whereId(topicInterceptor2.getId()).executeRows();

==> Preparing: UPDATE t_topic_interceptor SET `title` = ? WHERE `id` = ?
==> Parameters: 123(String),123(String)
<== Total: 1

这种情况下updateByupdateTime并不会自动添加到生成的sql里面,这个时候我们的UpdateSetInterceptor拦截就起作用了

UpdateSetInterceptor

我们在原先的拦截器上再次实现UpdateSetInterceptor让原先的拦截器支持表达式set,当然你也可以单独创建一个拦截器,如果单独创建那么可以单独对其进行选择性启用或者禁用

Api

方法默认实现描述
configure配置表达式更新set列自动填充

/**
 * create time 2023/4/3 21:13
 * 如果是spring项目添加@Component,如果是非spring项目直接添加到EasQueryConfiguration.applyInterceptor
 *
 * @author xuejiaming
 */
public class MyEntityInterceptor implements EntityInterceptor, UpdateSetInterceptor {
    @Override
    public void configureInsert(Class<?> entityClass, EntityInsertExpressionBuilder entityInsertExpressionBuilder, Object entity) {
        TopicInterceptor topicInterceptor = (TopicInterceptor) entity;
        if (topicInterceptor.getCreateTime() == null) {
            topicInterceptor.setCreateTime(LocalDateTime.now());
        }
        if (topicInterceptor.getCreateBy() == null) {
            topicInterceptor.setCreateBy(CurrentUserHelper.getUserId());
        }
        if (topicInterceptor.getUpdateTime() == null) {
            topicInterceptor.setUpdateTime(LocalDateTime.now());
        }
        if (topicInterceptor.getUpdateBy() == null) {
            topicInterceptor.setUpdateBy(CurrentUserHelper.getUserId());
        }
    }

    @Override
    public void configureUpdate(Class<?> entityClass, EntityUpdateExpressionBuilder entityUpdateExpressionBuilder, Object entity) {

        TopicInterceptor topicInterceptor = (TopicInterceptor) entity;
        topicInterceptor.setUpdateTime(LocalDateTime.now());
        topicInterceptor.setUpdateBy(CurrentUserHelper.getUserId());
    }

    @Override
    public String name() {
        return "MyEntityInterceptor";
    }

    @Override
    public boolean apply(Class<?> entityClass) {
        return TopicInterceptor.class.isAssignableFrom(entityClass);
    }

    @Override
    public void configure(Class<?> entityClass, EntityUpdateExpressionBuilder entityUpdateExpressionBuilder, ColumnSetter<Object> columnSetter) {
        //创建两个属性比较器
        EntitySegmentComparer updateTime = new EntitySegmentComparer(entityClass, "updateTime");
        EntitySegmentComparer updateBy = new EntitySegmentComparer(entityClass, "updateBy");
        columnSetter.getSQLBuilderSegment().forEach(k -> {
            updateTime.visit(k);
            updateBy.visit(k);
            return updateTime.isInSegment() && updateBy.isInSegment();
        });
        //是否已经set了
        if (!updateBy.isInSegment()) {
            String userId = StringUtils.defaultString(CurrentUserHelper.getUserId());
            columnSetter.set( "updateBy", userId);
        }
        if (!updateTime.isInSegment()) {
            columnSetter.set("updateTime", LocalDateTime.now());
        }
    }
}

测试

long l2 = easyQuery.updatable(TopicInterceptor.class)
        //虽然我们没有在表达式中设置需要set的属性,但是因为拦截器得原因easy-qeury帮我们自动的进行了处理
        .set(TopicInterceptor::getTitle, topicInterceptor2.getTitle())
        .whereId(topicInterceptor2.getId()).executeRows();

==> Preparing: UPDATE t_topic_interceptor SET `title` = ?,`update_by` = ?,`update_time` = ? WHERE `id` = ?
==> Parameters: 123(String),xiaoming1(String),2023-04-03T21:56:39.426(LocalDateTime),123(String)
<== Total: 1

到目前为止基本上大部分的业务需求已经可以实现了,但是如果你是有租户的或者你是需要对当前请求查询条件进行额外条件过滤添加的,那么PredicateFilterInterceptor可以帮你满足这个条件

PredicateFilterInterceptor

Api

方法默认实现描述
configure配置表达式where条件,查询,修改(对象/表达式),删除(对象/表达式)

租户模式实现

这边我们新建一个租户拦截器,把原先拦截器里面的自动填充租户id移动到租户拦截器里面


public class MyTenantInterceptor implements EntityInterceptor,PredicateFilterInterceptor {
    @Override
    public String name() {
        return "MyTenantInterceptor";
    }

    @Override
    public boolean apply(Class<?> entityClass) {
        return TopicInterceptor.class.isAssignableFrom(entityClass);
    }

    @Override
    public void configure(Class<?> entityClass, LambdaEntityExpressionBuilder lambdaEntityExpressionBuilder, WherePredicate<Object> sqlWherePredicate) {
        if(CurrentUserHelper.getUserId()!=null){x
            sqlWherePredicate.eq("tenantId", CurrentUserHelper.getTenantId());
        }
    }

    @Override
    public void configureInsert(Class<?> entityClass, EntityInsertExpressionBuilder entityInsertExpressionBuilder, Object entity) {

        TopicInterceptor topicInterceptor = (TopicInterceptor) entity;
        if (topicInterceptor.getTenantId() == null) {
            topicInterceptor.setTenantId(CurrentUserHelper.getTenantId());
        }
    }
}

测试


//查询
TopicInterceptor topicInterceptor1 = easyQuery.queryable(TopicInterceptor.class).whereId("12345").firstOrNull();

==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time`,t.`create_by`,t.`update_time`,t.`update_by`,t.`tenant_id` FROM t_topic_interceptor t WHERE t.`tenant_id` = ? AND t.`id` = ? LIMIT 1
==> Parameters: abc(String),12345(String)
<== Total: 1

//实体对象更新

CurrentUserHelper.setUserId("xiaoming1");
long l1 = easyQuery.updatable(topicInterceptor1).executeRows();

==> Preparing: UPDATE t_topic_interceptor SET `stars` = ?,`title` = ?,`update_time` = ?,`update_by` = ? WHERE `tenant_id` = ? AND `id` = ?
==> Parameters: 12345(Integer),12345(String),2023-04-03T22:20:27.756(LocalDateTime),xiaoming1(String),abc(String),12345(String)
<== Total: 1


//表达式更新
long l2 = easyQuery.updatable(TopicInterceptor.class).set(TopicInterceptor::getTitle, topicInterceptor2.getTitle())
            .whereId(topicInterceptor2.getId()).executeRows();


==> Preparing: UPDATE t_topic_interceptor SET `title` = ?,`update_by` = ?,`update_time` = ? WHERE `tenant_id` = ? AND `id` = ?
==> Parameters: 12345(String),xiaoming1(String),2023-04-03T22:20:27.773(LocalDateTime),abc(String),12345(String)
<== Total: 1


//表达式删除
long l3 = easyQuery.deletable(TopicInterceptor.class)
                .whereById(topicInterceptor2.getId()).executeRows();

==> Preparing: DELETE FROM t_topic_interceptor WHERE `tenant_id` = ? AND `id` = ?
==> Parameters: abc(String),12345(String)
<== Total: 1

//对象删除
long l4 = easyQuery.deletable(topicInterceptor2).executeRows();

==> Preparing: DELETE FROM t_topic_interceptor WHERE `tenant_id` = ? AND `id` = ?
==> Parameters: abc(String),12345(String)
<== Total: 0

所有的增删改都会添加对应的条件表达式值,可以做到表结构完美隔离租户之间的数据,并且用户使用全程无感

UpdateEntityColumnInterceptor

Api

方法默认实现描述
configure配置表达式更新选择需要set列自动填充

/**
 * create time 2023/4/3 21:13
 * 如果是spring项目添加@Component,如果是非spring项目直接添加到EasQueryConfiguration.applyInterceptor
 *
 * @author xuejiaming
 */
public class MyEntityInterceptor implements EntityInterceptor, UpdateEntityColumnInterceptor  {
    @Override
    public void configureInsert(Class<?> entityClass, EntityInsertExpressionBuilder entityInsertExpressionBuilder, Object entity) {
        TopicInterceptor topicInterceptor = (TopicInterceptor) entity;
        if (topicInterceptor.getCreateTime() == null) {
            topicInterceptor.setCreateTime(LocalDateTime.now());
        }
        if (topicInterceptor.getCreateBy() == null) {
            topicInterceptor.setCreateBy(CurrentUserHelper.getUserId());
        }
        if (topicInterceptor.getUpdateTime() == null) {
            topicInterceptor.setUpdateTime(LocalDateTime.now());
        }
        if (topicInterceptor.getUpdateBy() == null) {
            topicInterceptor.setUpdateBy(CurrentUserHelper.getUserId());
        }
    }

    @Override
    public void configureUpdate(Class<?> entityClass, EntityUpdateExpressionBuilder entityUpdateExpressionBuilder, Object entity) {

        TopicInterceptor topicInterceptor = (TopicInterceptor) entity;
        topicInterceptor.setUpdateTime(LocalDateTime.now());
        topicInterceptor.setUpdateBy(CurrentUserHelper.getUserId());
    }

    @Override
    public String name() {
        return "MyEntityInterceptor";
    }

    @Override
    public boolean apply(Class<?> entityClass) {
        return TopicInterceptor.class.isAssignableFrom(entityClass);
    }
    @Override
    public void configure(Class<?> entityClass, EntityUpdateExpressionBuilder entityUpdateExpressionBuilder, ColumnOnlySelector<Object> columnSelector, Object entity) {
        //创建两个属性比较器
        EntitySegmentComparer updateTime = new EntitySegmentComparer(entityClass, "updateTime");
        EntitySegmentComparer updateBy = new EntitySegmentComparer(entityClass, "updateBy");
        columnSelector.getSQLSegmentBuilder().forEach(k -> {
            updateTime.visit(k);
            updateBy.visit(k);
            return updateTime.isInSegment() && updateBy.isInSegment();
        });
        //是否已经set了
        if (!updateTime.isInSegment()) {
            columnSelector.column("updateTime");
        }
        if (!updateBy.isInSegment()) {
            columnSelector.column( "updateBy");
        }
    }
}

按需拦截

比如我们现在有这么一个需求因为部分接口需要针对测试数据进行移除,不希望统计到程序里面所以可以针对部分情况进行按需拦截
可以新建一个表达式拦截器PredicateFilterInterceptor,然后默认将enable改成false需要时自行添加条件通过useInteceptor(name),或者可以通过判断当前是否是某个api接口比如startWith("/api/test"),可以通过ThreadLocal来表示当前启用值

上次编辑于:
贡献者: Hoysing