距离上一次的文章Mitt 一个小而全的发布订阅库,麻雀虽小五脏俱全过去了也差不多一个月了,记录工作中的经验或解决问题的思路 咱们还是继续保持,也或许这能些成为你下次面试的底气吧!
我们还是基于工作中的真实需求进行记录;
背景提要
因为我们是做医院信息化整体解决的公司,CA签名就是一个比较重要功能,细心的姐妹们平时去医院做了检查,再检查单上就能看到CA认证的签名。
CA签名,通俗点讲就是数字认证,CA的厂家也是很多,什么北京CA,四川CA,湖北CA。其实就是一个类似U盘(UK)的东西。插在电脑上使用,当然现在也有APP客户端直接扫码就能替换硬件UK。这样就可以不用带UK了,直接通过APP扫码登陆就能继续后续流程了。
经过CA签名的数据,是具有法律效力的,在医院的医疗过程中产生的数据(比如处方单,检查报告,电子病历,病案首页等等这些文书)都是存放在医院的数据库。 当出现医疗纠纷的时候,是可以修改这些电子文书的(曾经也有医院私自改了这些电子文书),因为这些都是处理医疗纠纷的最直接的证据。
基于这种背景下产生的数据的快照就显得非常重要,也是医院信息系统中信息安全中比较重要的一环。
作为系统提供方我们的业务系统就必须支持使用CA签名了。
最开始只有一家CA厂家需要对接;
需求分析
分析了整个业务流程基本上确定这个业务模式:
step1: 把用户的UK 和 现在用户进行绑定,这样就既可以使用 用户名/密码 也可以使用UK登入系统
step2: CA信息存放在sessionStorage中并记录登录方式标识,在后续需要手动或自动签名的地方 可以判断是否使用的CA登录;
step3: 具体业务使用签名服务,完成整个签名的流程;
为了支持更多的CA厂家,我们必须做提前设计;
往往第一版都只是满足基本需求快速交付即可;
所以第一版的CA功能自然很快就按照CA的开发文档进行了对接开发交付,并提交了现场uat境进行了流程及功能的验证;
除了一些细微的调整,整个业务模式没什么问题;这需求就基本结束了。
很快 第二家医院 就有了同样的需求,但是他们使用的CA不再是北京CA了,而是湖北CA,
这就意味着,我们需要不断的去兼容支持不同厂家的CA了。
所要面临的是不同的厂家不同的业务模式以及不同的开发对接文档;
这时候就需要能够根据使用系统配置使用某一种CA了;
解决问题
定义好 系统需要使用那种CA的参数后,我们就需要梳理各种CA 每一步操作的差异,然后封装成统一的组件,给各个业务系统或者统一的passport页面进行集成。
这个时候就需要使用策略模式了,抽象出每个业务节点,然后每个节点自己根据CA的类型去实现具体业务。
什么是策略模式
下面是 微软 learn.microsoft.com 上的一段解释,使用国际象棋的例子讲解了什么是策略模式; 策略模式
策略模式使用相同的控制操作在不同的场景中激活不同的技能。
功能分解范式将不同的操作划分为不同的技能。 策略分解范式对所有技能使用相同的操作,并根据每种策略可发挥最大作用的方案来划分技能。
考虑国际象棋的类比。 在国际象棋比赛的开局阶段,目标是生存下来并进入下一阶段。 在中盘阶段,目标是获得对对手的优势。 终盘阶段的目标是将死对手,即困住或制服对手的国王。
有兴趣也可以看看 知乎上面的 # 策略模式详解 他列举了一个 网购中的购物付款界面选择支付方式的例子给介绍了策略模式的场景及应用;
我们怎么处理的?
首先定义了 CA_TYPE:
其次抽象了业务操作:分别为 登录,签名,验签 这几个业务操作;
因为这几步操作都需要初始化CA:
所以我们定义好了对于的操作的接口:
init()
login()
sign()
virifySign()
getCaInfo()
有了这几个基础接口之后 我们只需要针对不同的CA厂商进行处理对应的操作接口
一个简单的例子:
定义了一个简单的对象(或者Map),以UK 的名称作为Key ,分别放入 接口以及接口的实现,统一了接口的参数以及接口的返回;
如下:
const functions = {
BJCA_UK:{
async init(params){},
async sign(params){},
async verify(params){}
},
BJCA_QR:{
async init(params){},
async sign(params){},
async verify(params){}
},
HBCA_UK:{
async init(params){},
async sign(params){},
async verify(params){}
},
SCCA_UK:{
async init(params){},
async sign(params){},
async verify(params){}
}
// ...... other CA support .
}
//应用
const handleGetCatype = ()=>{
// TODO
return 'XX_CA'
}
const hadnleDoSign = async (params)=>{
const caType = handleGetCatype()
const isInit = await functions[Catype].init()
return functions[Catype].sign(params)
}
有了上面的基础结构,我们再需要添加支持的CA类型,我们只需要扩展 functions 对应的CA类型的接口实现即可,不影响其他CA的流程;
每一次的修改都不会或者很少触碰到其他的部分,这样就对此进行了简单的隔离; 减少了更改范围 既然就降低了测试范围。
最后的最后
下面请上我们今天的主角:有请小趴菜