【设计模式】十二.结构型模式之过滤器模式

过滤器模式

一. 说明

过滤器模式顾名思义就是用来过滤的,是结构型模式的一种,也可以叫做Criteria Pattern。
过滤器模式通过一系列条件来筛选出符合的对象。它将过滤逻辑封装在过滤器对象中,并通过组合这些过滤器来实现复杂的过滤条件。当需要根据条件筛选出需要的对象时,我们就可以使用过滤器模式。

二.应用场景

  1. 日志框架中的filter模块,就是用于日志过滤的过滤器
  2. 我们在网关做权限校验时的逻辑,如果校验内容过多,可以抽象成过滤器链来做,将校验的职责分离。

三.代码示例

我们先以一般性例子来说明过滤器模式的写法,设计一个人群,人群中有男有女,有单身的有已婚的,我们根据性别或者是否单身或者多个条件组合对人群做过滤。
先创建人的对象

@Data
public class Person {
    private String name;
    private String gender;
    private boolean single;
    public Person(String name, String gender, boolean single) {
        this.name = name;
        this.gender = gender;
        this.single = single;
    }
}

再创建过滤器的标准接口,有男性过滤器,女性过滤器,单身过滤器,条件组合过滤器这个实现

public interface Criteria {
    List<Person> filter(List<Person> persons);
}

//男性过滤器
public class ManCriteria implements Criteria{
    @Override
    public List<Person> filter(List<Person> persons) {
        return persons.stream().filter(p -> p.getGender().equals("man")).collect(Collectors.toList());
    }
}
//女性过滤器
public class WomanCriteria implements Criteria{
    @Override
    public List<Person> filter(List<Person> persons) {
        return persons.stream().filter(p -> p.getGender().equals("woman")).collect(Collectors.toList());
    }
}
//单身过滤器
public class SingleCriteria implements Criteria{
    @Override
    public List<Person> filter(List<Person> persons) {
        return persons.stream().filter(p -> p.isSingle()).collect(Collectors.toList());
    }
}
//并条件过滤器
public class AndCriteria implements Criteria {
    private List<Criteria> criterias;
    public AndCriteria(Criteria... criterias) {
        this.criterias = Arrays.asList(criterias);
    }
    @Override
    public List<Person> filter(List<Person> persons) {
        for (Criteria criteria : criterias) {
            persons = criteria.filter(persons);
        }
        return persons;
    }
}
//或条件过滤器
public class OrCriteria implements Criteria {
    private List<Criteria> criterias;
    public OrCriteria(Criteria... criterias) {
        this.criterias = Arrays.asList(criterias);
    }
    @Override
    public List<Person> filter(List<Person> persons) {
        Set<Person> temp = new HashSet<>();
        for (Criteria criteria : criterias) {
            persons = criteria.filter(persons);
            if (persons.size() > 0) {
                temp.addAll(persons);
            }
        }
        return new ArrayList<>(temp);
    }
}

编写测试代码

public static void main(String[] args) {
        List<Person> persons = new ArrayList<>();
        persons.add(new Person("张三","man", true));
        persons.add(new Person("李四","woman", false));
        persons.add(new Person("王五","woman", false));
        persons.add(new Person("赵六","woman", true));
        persons.add(new Person("钱八","man", true));
        persons.add(new Person("孙九","man", true));

        Criteria man = new ManCriteria();
        Criteria woman = new WomanCriteria();
        Criteria single = new SingleCriteria();
        Criteria singleMan = new AndCriteria(single, man);
        Criteria singleOrWoman = new OrCriteria(single, woman);

        List<Person> mans = man.filter(persons);
        System.out.println("男性: "+ mans);
        List<Person> womans = woman.filter(persons);
        System.out.println("女性: "+ womans);
        List<Person> singles = single.filter(persons);
        System.out.println("单身: "+ singles);
        List<Person> singleMans = singleMan.filter(persons);
        System.out.println("单身男性: "+ singleMans);
        List<Person> singleWomans = singleOrWoman.filter(persons);
        System.out.println("单身或女性: "+ singleWomans);
    }

在这里插入图片描述

过滤器模式在开发中遇到的场景不多,我们之前使用了装饰者模式对权限拦截器做了改造,这里我们再用过滤器链对权限校验过滤器做改造。

没有改造前的伪代码是这样的

@Slf4j
@Component
public class AuthFilter implements WebFilter, Ordered {
   
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    	//参数获取
        //参数校验
        //token校验 或 验签校验
       	//权限值校验
       	....
        return chain.filter(exchange);
    }
}

如果将所有校验逻辑都放在这一个filter方法里,代码会显得非常臃肿,大长段的逻辑会导致代码难以阅读。我们可以使用过滤器链来改造这一块的代码,我们使用java简单的类来模拟网关的web环境,目前整个过滤逻辑可以拆分为以下几个过滤器和类

  • IAuthFilter:所有过滤器的接口
  • AuthParamsFilter:实现参数校验的逻辑,有些公共请求头或公共参数可以在此校验
  • AuthTokenFilter:实现token校验的逻辑,判断token是否在redis中过期或jwt形式的token是否过期
  • AuthSignFilter:实现签名校验的逻辑,如果参数中存在加签参数,我们不走token校验,而是走此加签校验的逻辑
  • AuthPermissionFilter:实现用户权限的校验逻辑
  • AuthFactory:所有过滤器的工厂类,做统一获取过滤器和统一过滤的逻辑
  • AuthDTO: 采集系统的所有公共参数,交给每个过滤器使用

我们开始编写代码

//AuthDTO参数类
@Data
public class AuthDTO implements Serializable {
    /**
     * 请求uri
     */
    private String uri;
    /**
     * 请求参数
     */
    private Map<String, String> parameters = new HashMap<>();
    /**
     * 请求头
     */
    private Map<String, String> headers = new HashMap<>();
    /**
     *body
     */
    private String body;

}
public interface IAuthFilter {
	//过滤器执行顺序
    int order();
    //过滤方法 
    boolean filter(AuthDTO authDTO);
}

//参数校验过滤器
@Slf4j
public class AuthParamsFilter implements IAuthFilter {
    @Override
    public int order() {
        return 0;
    }
    @Override
    public boolean filter(AuthDTO authDTO) {
        if (authDTO == null) {
            log.warn("参数为空");
            return false;
        }
        //校验公共参数
        String token = authDTO.getHeaders().get("token");
        String sign = authDTO.getParameters().get("sign");
        if (ObjectUtils.isEmpty(token) && ObjectUtils.isEmpty(sign)) {
            log.warn("权限参数缺失");
            return false;
        }
        return true;
    }
}

//token校验过滤器
@Slf4j
public class AuthTokenFilter implements IAuthFilter {
	//使用本地map模拟redis缓存权限值
    private static Map<String, String> redisCache = new HashMap<>();
    static {
        redisCache.put("abcd", "10001");
        redisCache.put("efgh", "10002");
    }

    @Override
    public int order() {
        return 1;
    }

    @Override
    public boolean filter(AuthDTO authDTO) {
        String token = authDTO.getHeaders().get("token");
        if (!StringUtils.isEmpty(token)) {
            String uid = redisCache.get(token);
            if (StringUtils.isEmpty(uid)) {
                log.warn("登录已过期");
                return false;
            }
            authDTO.getHeaders().put("uid", uid);
        }
        return true;
    }
}

//加签参数校验器
@Slf4j
public class AuthSignFilter implements IAuthFilter {
    private static final String SECRET = "123456";

    @Override
    public int order() {
        return 2;
    }

    @Override
    public boolean filter(AuthDTO authDTO) {
        String sign = authDTO.getParameters().get("sign");
        if (!StringUtils.isEmpty(sign)) {
            String uid = authDTO.getParameters().get("uid");
            String orderId = authDTO.getParameters().get("orderId");
            String digist = MD5Util.digist(uid + orderId + SECRET);
            if (!sign.equals(digist)) {
                log.warn("加签失败");
                return false;
            }
            authDTO.getHeaders().put("uid", uid);
        }
        return true;
    }
}

//权限值校验器
@Slf4j
public class AuthPermissionFilter implements IAuthFilter {
    //使用本地map模拟redis缓存权限值
    private static Map<String, String[]> redisCache = new HashMap<>();

    static {
        redisCache.put("10001", new String[]{"/user", "/order"});
        redisCache.put("10002", new String[]{"/user"});
    }

    @Override
    public int order() {
        return 3;
    }

    @Override
    public boolean filter(AuthDTO authDTO) {
        String uri = authDTO.getUri();
        String uid = authDTO.getHeaders().get("uid");
        String[] permissions = redisCache.get(uid);
        List<String> list = Arrays.asList(permissions);
        if (!list.contains(uri)) {
            log.warn("用户:{} 没有访问{}的权限", uid, uri);
            return false;
        }
        return true;
    }
}

我们还需要编写一个工厂,获取所有的过滤器组成链,提供一个总的过滤方法

@Slf4j
public class AuthFactory {
	//这里使用本地静态list转载所有过滤器,在Spring环境中可以直接注入
	/**
	@Resource
	private List authFilterSet;
	**/
    private static final List<IAuthFilter> filters = new ArrayList<>();
    static {
        filters.add(new AuthParamsFilter());
        filters.add(new AuthTokenFilter());
        filters.add(new AuthSignFilter());
        filters.add(new AuthPermissionFilter());
    }
    //过滤入口方法
    public boolean filter(AuthDTO authDTO) {
        filters.sort(Comparator.comparing(IAuthFilter::order));
        for (IAuthFilter filter : filters) {
            if (!filter.filter(authDTO)) {
                return false;
            }
        }
        log.info("校验通过");
        return true;
    }
}

编写测试代码

 public static void main(String[] args) {
        AuthFactory factory = new AuthFactory();
        log.info("没有任何参数时");
        factory.filter(null);
        log.info("---------------");

        AuthDTO authDTO = new AuthDTO();
        log.info("有token没有访问权限时");
        authDTO.setUri("/order");
        authDTO.getHeaders().put("token", "efgh");
        factory.filter(authDTO);
        log.info("---------------");

        log.info("有token有访问权限时");
        AuthDTO authDTO2 = new AuthDTO();
        authDTO2.setUri("/order");
        authDTO2.getHeaders().put("token", "abcd");
        factory.filter(authDTO2);
        log.info("---------------");

        log.info("没有token有验签正确时");
        AuthDTO authDTO3 = new AuthDTO();
        authDTO3.setUri("/order");
        authDTO3.getParameters().put("uid", "10001");
        authDTO3.getParameters().put("orderId","22222");
        authDTO3.getParameters().put("sign", "05f62fe69de77fbbcb107943c6e1501b");
        factory.filter(authDTO3);
    }

结果输出正确
【设计模式】十二.结构型模式之过滤器模式_第1张图片

四. 总结

过滤器模式满足单一职责原则,可以将一大段的逻辑判断代码根据职责分离成多个过滤器组成过滤器链,上述的例子其实和责任链模式有点相似,和责任链的区别是,责任链模式需要在当前执行器传入下个执行器,而过滤器链只是简单的按序执行,可以说过滤器链是责任链模式的简单版本。

你可能感兴趣的:(java设计模式,设计模式,java)