背景

周三,18:00。

小明扭了扭微微发酸的脖子,揉了揉盯着屏幕有些干涩的眼睛。

终于忙完了,邻近上班,整个人心也变得放松起来。

“对接方须要咱们提供新的服务,下周二上线,需要我发你了,很简略的。”

产品经理发过来一条音讯,突破了这份美妙。

“我可去他的吧,每次需要都是快下班就来了。”小明未免心里嘀咕了起来,不过手上可没停。

“好的,我先看下需要。”

回复完后,点开了需要文档,的确很简略。

为内部对接方提供一个新增商户的接口。放弃和外部控台新增商户统一。

的确不太难,小明想了想,外部控台新增商户尽管不是本人做的,然而接口应该能够间接复用,代码应该也能复用。

不过本人以前没做过内部对接,不晓得有没有其余的坑。

下周二上线,那么下周一就须要让提测,让测试染指进来。

小明点开了日历,明天周三,本人接口文档编写,具体设计,编码和自测的工夫只剩两天。

原本应该是短缺的,不过平时还会有各种工作琐事须要解决,会升高整体的工作效率。

还是先问共事要下以前的代码和文档吧。

看着工夫曾经超过了下班时间,小明叹了一口气。

接口文档

文档编写

周四,9:30。

小明来到公司就开始着手解决接口文档的编写,有以前的文档根底,写起来还是很快的。

不过对外的接口还是有些不同,小明依照本人的了解加上了 traceId,requestTime 等字段,便于问题的排查和定位。

于是根本的接口就写好了:

申请参数:

序号参数形容是否必填阐明
1traceId惟一标识用于惟一定位一笔申请,32 位字符串
2requestTime申请参数申请工夫,yyyyMMddHHmmssSSS 17 位工夫戳
3username用户名最长 64 位字符串
4password明码最长 128 位字符串, md5 加密
5address地址最长 128 位字符串

响应参数:

序号参数形容是否必填阐明
1respCode响应编码000000 示意胜利,其余见响应编码枚举
2respDesc响应形容申请工夫,yyyyMMddHHmmssSSS 17 位工夫戳
3userId用户标识32 位字符串,胜利创立后用户的惟一标识

小明具体的写下了整个接口的申请形式,注意事项,以及对应的各种枚举值等。

并且把根本的具体设计文档也整顿了一下。

1762 个字,小明看了看总字数,香甜的笑了笑。

这份文档,显然没有需要文档那么简洁。

一低头,曾经 11:30 了,好家伙,工夫过得真快。

于是预订了下午 14:00 的会议室,筹备和产品经理,测试,项目经理过一下文档。

文档评审

会议室 14:00。

小明按时来到会议室,提前插好投影仪,等着大家的到来。

“我昨天提的需要简略吧。”,未见其人先问其声,产品经理刚到门口就笑着走了进来。

“是的,还好。”

接着,项目经理和测试也一起走了进来。

“快点过需要文档吧”,项目经理说道。

小明清了清嗓子,讲了下整体的我的项目背景。并且把本人的具体设计和接口文档过了一遍。

过的时候,产品经理抬头在做其余的事件,这些细节并不需要关怀。

测试听的比拟认真,不停的提出本人的疑难,前面须要本人进行验证。

“还有其余问题吗?”,小明本人一个人不停说了半个多小时,感觉有些干燥。

“我没什么问题了”,测试说,“我最关怀的就是什么时候能够提测?”

“下周一吧”,小明顿了顿,“我预计要会后能力开始编码。”

“那还好”,测试回道,并示意本人没有其余疑难。

“你这个文档写的挺具体的”,项目经理略带称许的眼光看了下小明,“不过有一个问题,你这个接口连签名都没有。”

“签名,什么签名?”,小明有点懵。

“你连对外接口签名都不会晓得?有工夫还是要学习学习。”,项目经理显然有些悲观。

“好了,不说这些了。”,产品经理这时退出了谈话,“这么简略的需要下周二上没问题吧?”

“应该没问题,只有按时提测就行”,测试看向了小明。

“应该没问题”,小明脑子里还在想接口签名的事件,“我回去看下接口签名,调整下接口。”

接口签名

签名作用

小明去查了查,发现对外的接口,安全性必定是须要思考的。

为了保证数据的安全性,避免信息被篡改,签名是比拟常见的一种形式。

签名实现

实现的形式有很多种,比拟罕用的形式:

(1)将所有参数,除去 checkValue 自身,按参数名字母升序排序。

(2)排序后的参数,依照参数和值的形式拼接为字符串

(3)对拼接完的字符串,应用单方约定好的 key 进行 md5 加密,失去 checkValue 值

(4)将对应的值设置到 checkValue 属性上

当然,在签名的实现上可能会有差别,然而单方保持一致即可。

签名校验

晓得如何进行加签,校验也是相似的。

反复下面的 (1)(2)(3)步,失去对应的 checkValue。

而后和对方传递的值进行比照,如果统一,阐明签名验证通过。

接口的调整

在理解了签名的必要性之后,小明在接口文档中新增了 checkValue 这个属性值。

并且和测试进行了私下的沟通,到这里,文档才算初步完结。

看着工夫曾经超过了下班时间,小明叹了一口气。

代码实现

v1 版本

周五,10:00。

小明来到公司就开始进行编码,其余的货色解决的差不多之后,就剩下一个签名的实现问题。

一开始也没多想,间接实现如下:

/** * 手动构建验签后果 * @return 后果 * @since 0.0.2 */public String buildCheckValue() {    StringBuilder stringBuilder = new StringBuilder();    stringBuilder.append(name);    stringBuilder.append(password);    // 其余一堆属性    return Md5Util.md5(stringBuilder.toString());}

当然,作为一个拿来主义者,小明意识到一个问题。

其余我的项目中必定有相似的工具类,本人不应该反复造轮子。

v2 版本

从其余利用拷贝了一份工具类过去,大略实现如下:

import com.github.houbb.heaven.util.lang.StringUtil;import com.github.houbb.heaven.util.lang.reflect.ClassUtil;import com.github.houbb.heaven.util.lang.reflect.ReflectFieldUtil;import com.github.houbb.heaven.util.secrect.Md5Util;import java.lang.reflect.Field;import java.util.*;/** * @author binbin.hou * @since 1.0.0 */public class CheckValueUtils {    private CheckValueUtils(){}    public static String buildCheckValue(Object object) {        Class<?> clazz = object.getClass();        // 获取所有字段的 fieldMap        Map<String, Field> fieldMap = ClassUtil.getAllFieldMap(clazz);        // 移除 checkValue 名称的        fieldMap.remove("checkValue");        // 对字段按名称排序        Set<String> fieldNameSet = fieldMap.keySet();        List<String> fieldNameList = new ArrayList<>(fieldNameSet);        Collections.sort(fieldNameList);        // 反射获取所有字符串的值        StringBuilder stringBuilder = new StringBuilder();        for(String fieldName : fieldNameList) {            Object value = ReflectFieldUtil.getValue(fieldName, object);            // 反射获取值            String valueStr = StringUtil.objectToString(value, "");            // 拼接            stringBuilder.append(fieldName).append("=").append(valueStr);        }        //md5 加签        return Md5Util.md5(stringBuilder.toString());    }}

总的来说还是很好用的,而且本人的工夫也不多,就间接拿来应用就好。

全副搞定之后,就是自测工作,通过了一次踩坑之后,小明算是把整个接口自测通过了。

“这样,提测工夫也来得及了。”

看着工夫曾经超过了下班时间,小明叹了一口气。

更优雅的加签实现

一般来说,故事到这里就完结了。

不过小明的一个想法,让这个故事持续走了上来。

工具办法的有余

原有的办法根本能够满足大部分的需要,不过想要做调整就会变得比拟麻烦。

比方,想某些十分大的字段不加入加签,加签字段的名字不叫 checkValue,而是改成 sign,调整一下字段排序的形式等等。

这些都会导致原有的工具办法不可用,须要从新复制,批改。

那能不能实现一个更加灵便的加签工具呢?

答案是必定的,小明周末花了 2 天的工夫,实现了一个加签工具。

疾速开始

maven 引入

<plugin>    <groupId>com.github.houbb</groupId>    <artifactId>checksum</artifactId>    <version>0.0.6</version></plugin>

pojo 对象

  • User.java
public class User {    @CheckField    private String name;    private String password;    @CheckField(required = false)    private String address;    @CheckValue    private String checksum;    //Getter & Setter    //toString()}

其中波及到两个外围的注解:

@CheckField 示意参加加签的字段信息,默认都是参加加签的。指定 required=false 跳过加签。

@CheckValue 示意加签后果寄存的字段,该字段类型须要为 String 类型。

前期将会增加一个 String 与不同类型的转换实现,拓展利用场景。

获取签名

所有的工具类办法见 ChecksumHelper,且上面的几个办法都反对指定秘钥。

User user = User.buildUser();final String checksum = ChecksumHelper.checkValue(user);

该办法会把 User 对象中指定 @CheckField 的字段全副进行解决,

通过指定排序后进行拼接,而后联合指定加密策略构建最初的验签后果。

填充签名

User user = User.buildUser();ChecksumHelper.fill(user);

能够把对应的 checkValue 值默认填充到 @CheckValue 指定的字段上。

验证签名

User user = User.buildUser();boolean isValid = ChecksumHelper.isValid(user);

会对以后的 user 对象进行加签运算,并且将加签的后果和 user 自身的签名进行比照。

疏导类

ChecksumBs 疏导类

为了满足更加灵便的场景,咱们引入了基于 fluent-api 的 ChecksumBs 疏导类。

下面的配置默认等价于:

final String checksum = ChecksumBs        .newInstance()        .target(user)        .charset("UTF-8")        .checkSum(new DefaultChecksum())        .sort(Sorts.quick())        .hash(Hashes.md5())        .times(1)        .salt(null)        .checkFieldListCache(new CheckFieldListCache())        .checkValueCache(new CheckValueCache())        .checkValue();

配置阐明

下面所有的配置都是能够灵便替换的,所有的实现都反对用户自定义。

属性阐明
target待加签对象
charset编码
checkSum具体加签实现
sort字段排序策略
hash字符串加密 HASH 策略
salt加密对应的盐值
times加密的次数
checkFieldListCache待加签字段的缓存实现
checkValueCache签名字段的缓存实现

性能

背景

每次咱们说到反射第一反馈是不便,第二反馈就是性能。

有时候往往因为关怀性能,而抉择手动一次次的复制,黏贴。

性能

详情见 BenchmarkTest.java

本次进行 100w 次测试验证,耗时如下。

手动解决耗时:2505ms

注解解决耗时:2927ms

小结

签名在对外接口的通信中,能够保障信息不被篡改。

心愿这个工具能够帮到你,让你按时上班。

我是老马,期待与你的下次重逢。