zoukankan      html  css  js  c++  java
  • 设计模式之美学习-如何进行面向对象设计(三)

    需求

    实现一个接口鉴权的功能,实现思路

    1.调用端下发一个appId和秘钥

    2.调用端将每次传过来的参数url后面都要带上appId+时间戳同时根据url+appId+时间戳+参数+秘钥进行MD5加密后通过token参数传过来

    3.获取时间戳判断是否过期(假定1分钟) 如果过期鉴权失败

    4.服务端解析出url+appId+时间戳+参数+秘钥进行md5加密

    5.根据token匹配 如果不匹配 则认为参数被篡改 则鉴权失败

    方式一

    把需求描述中的名词罗列出来,作为可能的候选类,然后再进行筛选。对于没有经验的初学者来说,这个方法比较简单、明确,可以直接照着做。

    我想到的几个名词 囧

    鉴权

    解析

    加密

    方式二

    类的大致拆解

    将上面的需求拆分成一个一个尽可能小的功能点"单一职责"

    1.解析url获取参数、token、时间戳

    2.从存储中根据appId获取秘钥

    3.将url+appId+时间戳+参数+秘钥+拼成一个串

    4.对字符串进行加密生成一个token

    5.根据时间戳判断是否token过期(防止接口重放)

    6.对加密后的token和调用方穿过来的token判断是否一致(防止参数篡改)

    3 4  5 6都是跟token相关 负责token的生成验证,1负责解析url,2.操作appid和密码

    所以,我们可以粗略地得到三个核心的类:AuthToken、Url、CredentialStorage。AuthToken 负责实现 3 4  5 6这四个操作;Url 负责 1两个操作;CredentialStorage 负责 2 这个操作

    类的定义

    AuthToken

    3.将url+appId+时间戳+参数+秘钥+拼成一个串

    4.对字符串进行加密生成一个token

    5.根据时间戳判断是否token过期

    6.对加密后的token和调用方穿过来的token判断是否一致

    对于方法的识别,很多面向对象相关的书籍,一般都是这么讲的,识别出需求描述中的动词,作为候选的方法,再进一步过滤筛选。类比一下方法的识别,我们可以把功能点中涉及的名词,作为候选属性,然后同样进行过滤筛选

    动词: 1.拼接串 2.判断是否过期 3.生成token 4.是否匹配    但是拼接串和生成token其实都是为生成token服务所以实现了2个方法1.isExpired判断是否过期  2.match是否匹配

    注意点:

    1. 从业务模型上来说,不应该属于这个类的属性和方法,不应该被放到这个类里。比如 URL、AppID 这些信息,从业务模型上来说,不应该属于 AuthToken,所以我们不应该放到这个类中。
    2. 在设计类具有哪些属性和方法的时候,不能单纯地依赖当下的需求,还要分析这个类从业务模型上来讲,理应具有哪些属性和方法。这样可以一方面保证类定义的完整性,另一方面不仅为当下的需求还为未来的需求做些准备。

    Url

    1.解析url获取参数、token、时间戳

    虽然需求描述中,我们都是以 URL 来代指接口请求,但是,接口请求并不一定是以 URL 的形式来表达,还有可能是 dubbo RPC 等其他形式。为了让这个类更加通用,命名更加贴切,我们接下来把它命名为 ApiRequest。下面是我根据功能点描述设计的 ApiRequest 类。

    CredentialStorage

    2.从存储中根据appId获取秘钥

    定义类与类之间的交互关系

    UML 统一建模语言中的关系 

    泛化

    泛化(Generalization)可以简单理解为继承关系。

    public class A { ... }
    public class B extends A { ... }

    实现

    实现(Realization)一般是指接口和实现类之间的关系。

    public interface A {...}
    public class B implements A { ... }

    聚合

    聚合(Aggregation)是一种包含关系,A 类对象包含 B 类对象,B 类对象的生命周期可以不依赖 A 类对象的生命周期,也就是说可以单独销毁 A 类对象而不影响 B 对象,比如课程与学生之间的关系

    public class A {
      private B b;
      public A(B b) {
        this.b = b;
      }
    }

    组合

    组合(Composition)也是一种包含关系。A 类对象包含 B 类对象,B 类对象的生命周期跟依赖 A 类对象的生命周期,B 类对象不可单独存在,比如鸟与翅膀之间的关系。

    public class A {
      private B b;
      public A() {
        this.b = new B();
      }
    }

    关联

    关联(Association)是一种非常弱的关系,包含聚合、组合两种关系。具体到代码层面,如果 B 类对象是 A 类的成员变量,那 B 类和 A 类就是关联关系。

    public class A {
      private B b;
      public A(B b) {
        this.b = b;
      }
    }
    或者
    public class A {
      private B b;
      public A() {
        this.b = new B();
      }
    }

    依赖

    public class A {
      private B b;
      public A(B b) {
        this.b = b;
      }
    }
    或者
    public class A {
      private B b;
      public A() {
        this.b = new B();
      }
    }
    或者
    public class A {
      public void func(B b) { ... }
    }

    关系实现

    CredentialStorage为接口 可以设计MysqlCredentialStorage或者RedisCredentialStorage

    ApiRequest也是接口可以为HttpApiRequest或者DubboApiRequest

    所以这里只用到了实现关系

    对外提供的入口方法可以通过main方法吧整个流程跑完 如mvc的拦截器的执行方法实现

    public interface ApiAuthenticator {
      void auth(String url);
      void auth(ApiRequest apiRequest);
    }
    
    public class DefaultApiAuthenticatorImpl implements ApiAuthencator {
      private CredentialStorage credentialStorage;
      
      public DefaultApiAuthenticator() {
        this.credentialStorage = new MysqlCredentialStorage();
      }
      
      public DefaultApiAuthenticator(CredentialStorage credentialStorage) {
        this.credentialStorage = credentialStorage;
      }
    
      @Override
      public void auth(String url) {
        ApiRequest apiRequest = ApiRequest.buildFromUrl(url);
        auth(apiRequest);
      }
    
      @Override
      public void auth(ApiRequest apiRequest) {
        String appId = apiRequest.getAppId();
        String token = apiRequest.getToken();
        long timestamp = apiRequest.getTimestamp();
        String originalUrl = apiRequest.getOriginalUrl();
    
        AuthToken clientAuthToken = new AuthToken(token, timestamp);
        if (clientAuthToken.isExpired()) {
          throw new RuntimeException("Token is expired.");
        }
    
        String password = credentialStorage.getPasswordByAppId(appId);
        AuthToken serverAuthToken = AuthToken.generate(originalUrl, appId, password, timestamp);
        if (!serverAuthToken.match(clientAuthToken)) {
          throw new RuntimeException("Token verfication failed.");
        }
      }
    }
  • 相关阅读:
    python读写操作excel数据
    python读写操作excel数据小应用
    操作系统相关知识
    网络编程课堂笔记
    UDP简单初识+socketserver 模块实现服务端并发
    链接循环+模拟远程执行命令+实现大文件上传
    循环通信
    luogu P2761 软件补丁问题
    luogu P4016 负载平衡问题
    P3381 【模板】最小费用最大流(spfa板子)
  • 原文地址:https://www.cnblogs.com/LQBlog/p/12118125.html
Copyright © 2011-2022 走看看