设计模式与权限系统:责任链、策略、代理三剑客

权限系统,说白了就是「谁能不能干什么」。我做了十几年系统,见过太多权限代码写得像一团乱麻。今天咱们聊聊怎么用三个设计模式,把权限系统理得清清楚楚。

我个人习惯把权限系统拆成三个层次:权限校验流程权限策略选择访问控制拦截。正好对应责任链、策略、代理三个模式。你想想看,是不是很自然?

一、责任链模式:让权限校验像流水线

先说说责任链模式。我在项目中遇到过这样一个场景:一个请求过来,要经过登录校验、角色校验、资源权限校验、数据权限校验……如果全写在一个方法里,那代码简直没法看。

责任链模式就是把这些校验器串成一条链。每个节点只做一件事,做完就传给下一个。这样既清晰又灵活。

核心思想:每个处理器只关注自己的职责,处理完就交给下一个。你可以动态增删处理器,而不影响其他环节。

// 抽象处理器
public abstract class PermissionHandler {
    protected PermissionHandler next;
    
    public void setNext(PermissionHandler next) {
        this.next = next;
    }
    
    public abstract boolean handle(Request request);
    
    protected boolean handleNext(Request request) {
        if (next == null) return true;
        return next.handle(request);
    }
}

// 登录校验
public class LoginHandler extends PermissionHandler {
    @Override
    public boolean handle(Request request) {
        if (request.getUser() == null) {
            System.out.println("用户未登录,拒绝访问");
            return false;
        }
        System.out.println("登录校验通过");
        return handleNext(request);
    }
}

// 角色校验
public class RoleHandler extends PermissionHandler {
    @Override
    public boolean handle(Request request) {
        if (!"admin".equals(request.getUser().getRole())) {
            System.out.println("角色权限不足");
            return false;
        }
        System.out.println("角色校验通过");
        return handleNext(request);
    }
}

// 资源权限校验
public class ResourceHandler extends PermissionHandler {
    @Override
    public boolean handle(Request request) {
        if (!request.getUser().hasPermission(request.getResource())) {
            System.out.println("无资源访问权限");
            return false;
        }
        System.out.println("资源权限校验通过");
        return handleNext(request);
    }
}

我的经验:责任链的顺序很重要。我一般把「成本低、拒绝快」的校验放前面。比如登录校验,用户没登录就直接返回,不用再去查数据库里的资源权限了。这样可以大幅提升性能。

二、策略模式:权限策略灵活切换

权限校验通过了,但不同资源、不同场景下的权限策略可能完全不同。比如:

  • 普通文档:只要登录就能看
  • 机密文档:需要管理员审批
  • 个人资料:只能自己看

这时候策略模式就派上用场了。每种权限策略封装成一个类,运行时动态选择。

// 策略接口
public interface PermissionStrategy {
    boolean checkPermission(User user, Resource resource);
}

// 公开策略:登录即可
public class PublicStrategy implements PermissionStrategy {
    @Override
    public boolean checkPermission(User user, Resource resource) {
        return user != null;
    }
}

// 机密策略:需要管理员
public class ConfidentialStrategy implements PermissionStrategy {
    @Override
    public boolean checkPermission(User user, Resource resource) {
        return user != null && "admin".equals(user.getRole());
    }
}

// 私有策略:只能自己
public class PrivateStrategy implements PermissionStrategy {
    @Override
    public boolean checkPermission(User user, Resource resource) {
        return user != null && user.getId().equals(resource.getOwnerId());
    }
}

// 策略上下文
public class PermissionContext {
    private PermissionStrategy strategy;
    
    public void setStrategy(PermissionStrategy strategy) {
        this.strategy = strategy;
    }
    
    public boolean executeStrategy(User user, Resource resource) {
        return strategy.checkPermission(user, resource);
    }
}

避坑指南:我曾经把策略选择逻辑写死在if-else里,结果每次新增策略都要改代码。后来改成用配置文件+反射加载,新增策略只需要加一个类和一个配置项,完全符合开闭原则。

三、代理模式:优雅的访问控制

最后说说代理模式。权限校验和策略选择都做好了,但怎么把这些逻辑「无侵入」地加到业务代码里?

代理模式就是最好的答案。它像一个门卫,在真正访问目标对象之前,先执行权限检查。

// 业务接口
public interface DocumentService {
    void viewDocument(String docId);
    void editDocument(String docId);
}

// 真实业务类
public class DocumentServiceImpl implements DocumentService {
    @Override
    public void viewDocument(String docId) {
        System.out.println("查看文档:" + docId);
    }
    
    @Override
    public void editDocument(String docId) {
        System.out.println("编辑文档:" + docId);
    }
}

// 代理类
public class DocumentServiceProxy implements DocumentService {
    private DocumentService realService;
    private PermissionHandler handlerChain;
    private PermissionContext context;
    
    public DocumentServiceProxy(DocumentService realService) {
        this.realService = realService;
        // 初始化责任链
        this.handlerChain = new LoginHandler();
        handlerChain.setNext(new RoleHandler());
        handlerChain.setNext(new ResourceHandler());
        // 初始化策略上下文
        this.context = new PermissionContext();
    }
    
    @Override
    public void viewDocument(String docId) {
        Request request = buildRequest(docId, "view");
        // 先走责任链校验
        if (!handlerChain.handle(request)) {
            throw new SecurityException("权限不足");
        }
        // 再走策略校验
        if (!context.executeStrategy(request.getUser(), request.getResource())) {
            throw new SecurityException("策略不允许");
        }
        // 通过后调用真实方法
        realService.viewDocument(docId);
    }
    
    @Override
    public void editDocument(String docId) {
        // 类似实现...
    }
}

实际应用:在Spring框架中,AOP就是代理模式的典型应用。你可以用@Around注解,在方法执行前后插入权限校验逻辑。但底层原理和上面写的代理类是一样的。

四、三模式协同:一张图看懂

这三个模式不是孤立的,它们协同工作才能构建一个完整的权限系统。我画了一张图,帮你理清它们的关系:

权限系统三模式协同架构 客户端请求 代理模式:访问控制 DocumentServiceProxy 责任链模式:权限校验流程 登录校验 角色校验 资源校验 数据校验 策略模式:权限策略选择 公开策略 | 机密策略 | 私有策略 | 自定义策略

从图上可以看得很清楚:代理模式在最外层,拦截所有请求;责任链模式在中间层,做流程化的校验;策略模式在最底层,根据资源类型选择具体的权限规则。三层各司其职,互不干扰。

五、实际项目中的组合用法

我在一个企业级文档管理系统中,就是这样设计的。具体做法是:

  1. 代理层:用Spring AOP的@Around注解,拦截所有Controller方法
  2. 责任链层:每个校验器实现Handler接口,通过配置决定顺序
  3. 策略层:每种文档类型对应一个策略实现,从数据库读取配置

这样做的好处是:

模式 职责 变更影响范围
代理模式 拦截请求,统一入口 新增接口时,只需加切点表达式
责任链模式 流程化校验,可动态增删 新增校验器,不影响已有逻辑
策略模式 具体权限规则,按需切换 新增策略,只需加类+配置

总结一下:这三个模式组合起来,权限系统就变得非常灵活。你想想看,如果哪天业务方说「我们要加一个IP白名单校验」,你只需要在责任链里加一个处理器;如果又说「机密文档要改成部门经理审批」,你只需要改一下策略实现。完全不用动业务代码。

嗯,这就是我多年实战中沉淀下来的权限系统设计思路。代码写得再漂亮,不如架构设计得合理。希望对你有所帮助。

公众号:蓝海资料掘金营,微信deep3321