设计模式之美学习-如何进行面向对象设计(三)

需求

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

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.");
    }
  }
}
复制代码

实战方法

------------------b端基础配置---------------------
管理对检查表进行新增
   -检查表配置信息新增修改
   -①新增检查项分组
   -②基于检查项分组新增检查项
     1)评分配置
管理对检查表进行修改
    -检查表配置信息修改
    -检查项新增/修改/删除/排序
     1)评分配置
    -检查分组新增/修改/删除/排序
管理员对检查表进行授权
管理员对检查表进行编辑
管理员对检查表进行禁用启用

-----------------------C端巡店----------------------
巡店人员巡店前签到
巡店人员根据检查表填写检查结果
巡店人员提交检查结果,根据检查结果生成整改单
巡店人员完成巡店进行签退
巡店人员下发整改单
巡店人员根据检查结果开具奖励单
巡店人员根据检查结果开具奖惩单
巡店人员作废整改单
巡店人员对整改单整改项进行驳回
巡店人员根据巡检结果开具奖励单
巡店人员根据巡检结果开具处罚单
--------------------C端门店整改-----------------------
门店人员根据整改单-不合格的整改项进行整改
门店人员根据驳回的整改项进行整改

---------------------领域建模-------------------------------  
--名词整理

检查表、检查项目分组、检查项目、检查结果、整改单、整改项、奖励单、奖惩单

--行为聚合划分
检查表聚合根
   ①检查表配置新增、编辑、删除、授权、禁用、启用
   ②检查项目分组Entinty(依附于检查表不能独立存在、且有自己生命周期)
    1)新增、修改、删除、排序
   ②检查项目Entity(依附于检查表和检查分组不能独立存在、且有自己生命周期)
    1)新增、修改、删除、排序
   ③检查项目评分方式valueObject  
检查结果聚合根
   ①巡店人员创建检查结果
   ②巡店人员根据检查项选择检查通过or不通过
整改单聚合根
   ①系统自动根据检查结果生成整改单
   ②巡店人员根据检查结果下发生成整改单
   ④巡店人员作废整改单
   ③门店人员根据检查结果进行整改
   ⑤巡店人员对整改项进行驳回
   ⑥门店人员对驳回整改项进行修正

-----------------核心技术问题-------------------------------  
1.审核记录
2.数据量预估
-----------------ddl--------------------------------------
store_check_sheet(检查表) 不要强制命名store,是否应该设计更宽,以后可以不止支撑门店检查,别的业务类型的检查?
store_scope (检查范围):org_type 更宽泛一点

min_check_time(最低检查时长) 我们存储最小单位就行了。能够满足各种场景 比如miao。ui如何展示小时 分钟是ui的事情
pass_score (合格分数)是不是也是整数
rectification_day(整改时效)不根据当前业务存储,存储最小单位?如果需求整个时效要到小时能不破坏结构和刷数的情况兼容吗?
检查表体现多版本
检查结果关联签到记录
检查结果关联奖励单 惩罚单
   
     


posted @   意犹未尽  阅读(362)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示