背景
周三,18:00。
小明扭了扭微微发酸的脖子,揉了揉盯着屏幕有些干涩的眼睛。
终于忙完了,邻近上班,整个人心也变得放松起来。
“对接方须要咱们提供新的服务,下周二上线,需要我发你了,很简略的。”
产品经理发过来一条音讯,突破了这份美妙。
“我可去他的吧,每次需要都是快下班就来了。”小明未免心里嘀咕了起来,不过手上可没停。
“好的,我先看下需要。”
回复完后,点开了需要文档,的确很简略。
为内部对接方提供一个新增商户的接口。放弃和外部控台新增商户统一。
的确不太难,小明想了想,外部控台新增商户尽管不是本人做的,然而接口应该能够间接复用,代码应该也能复用。
不过本人以前没做过内部对接,不晓得有没有其余的坑。
下周二上线,那么下周一就须要让提测,让测试染指进来。
小明点开了日历,明天周三,本人接口文档编写,具体设计,编码和自测的工夫只剩两天。
原本应该是短缺的,不过平时还会有各种工作琐事须要解决,会升高整体的工作效率。
还是先问共事要下以前的代码和文档吧。
看着工夫曾经超过了下班时间,小明叹了一口气。
接口文档
文档编写
周四,9:30。
小明来到公司就开始着手解决接口文档的编写,有以前的文档根底,写起来还是很快的。
不过对外的接口还是有些不同,小明依照本人的了解加上了 traceId,requestTime 等字段,便于问题的排查和定位。
于是根本的接口就写好了:
申请参数:
序号 | 参数 | 形容 | 是否必填 | 阐明 |
---|---|---|---|---|
1 | traceId | 惟一标识 | 是 | 用于惟一定位一笔申请,32 位字符串 |
2 | requestTime | 申请参数 | 是 | 申请工夫,yyyyMMddHHmmssSSS 17 位工夫戳 |
3 | username | 用户名 | 是 | 最长 64 位字符串 |
4 | password | 明码 | 是 | 最长 128 位字符串, md5 加密 |
5 | address | 地址 | 否 | 最长 128 位字符串 |
响应参数:
序号 | 参数 | 形容 | 是否必填 | 阐明 |
---|---|---|---|---|
1 | respCode | 响应编码 | 是 | 000000 示意胜利,其余见响应编码枚举 |
2 | respDesc | 响应形容 | 是 | 申请工夫,yyyyMMddHHmmssSSS 17 位工夫戳 |
3 | userId | 用户标识 | 是 | 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
小结
签名在对外接口的通信中,能够保障信息不被篡改。
心愿这个工具能够帮到你,让你按时上班。
我是老马,期待与你的下次重逢。