一、背景
最近在做项目的时候,我碰到了一个问题,使用json传递报文的时候怎么保存报文的顺序?比如下面一段报文:
{
"ec": {
"request": {
"header": {
"actiontype": "00",
"currenttime": "1586003427105"
},
"body": {
"uuid": "dsasdadada",
"subject": "CN=AO",
"pubkey": "dsadsadsad",
"validaty": "",
"duration": "",
"certhash": ""
}
},
"signature": {
"alg": "sm3withSM2",
"pubkey": "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw==",
"sign": "MEUCIQD1NNi3enH4IuivzTMdGt3NYuuIbui/2/zBbk/rZ/BPIQIgbDqaiS34rkkaSc/QWLaH2kfjTIT3lzWJN4M1IzzJ+TA="
},
"eccert": {
"type": "00",
"value": "fsafasdsadasdas"
}
},
"signature": {
"alg": "sm3withSM2",
"pubkey": "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw==",
"sign": "MEYCIQDdwZByaHxSBYGZv2mD9VjKjatzkx8FkbWRjwd+aVJuyAIhALgERohg8KTaVqkun2kjcHHe+7HBgFEIZCbyZo8m0pA2"
}
}
这一段报文怎么创建呢?怎么确保每次创建的报文格式都是一致的呢?
二、解决方法
- 我的第一种解决方案(傻瓜式)
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.15</version>
</dependency>
使用fastjson的JSONObject反复创建添加,代码如下:
@Test
public void test(){
JSONObject jsonObject = new JSONObject(true);
JSONObject ec = new JSONObject(true);
JSONObject request = new JSONObject(true);
JSONObject header = new JSONObject(true);
header.put("actiontype","00");
header.put("currenttime","1586003427105");
JSONObject body = new JSONObject(true);
body.put("uuid","dsasdadada");
body.put("subject","CN=AO");
body.put("pubkey","dsadsadsad");
body.put("validaty","");
body.put("duration","");
body.put("certhash","");
request.put("header",header);
request.put("body",body);
JSONObject signature = new JSONObject();
signature.put("alg","sm3withSM2");
signature.put("pubkey", "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw==");
signature.put("sign","MEUCIQD1NNi3enH4IuivzTMdGt3NYuuIbui/2/zBbk/rZ/BPIQIgbDqaiS34rkkaSc/QWLaH2kfjTIT3lzWJN4M1IzzJ+TA=");
JSONObject eccert = new JSONObject();
eccert.put("type","00");
eccert.put("value","fsafasdsadasdas");
ec.put("request",request);
ec.put("signature",signature);
ec.put("eccert",eccert);
JSONObject signNature = new JSONObject();
signNature.put("alg","sm3withSM2");
signNature.put("pubkey", "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw==");
signNature.put("sign", "MEYCIQDdwZByaHxSBYGZv2mD9VjKjatzkx8FkbWRjwd+aVJuyAIhALgERohg8KTaVqkun2kjcHHe+7HBgFEIZCbyZo8m0pA2");
jsonObject.put("ec",ec);
jsonObject.put("signature",signNature);
System.out.println(JSONObject.toJSONString(jsonObject,true));
}
打印结果:
{
"ec":{
"request":{
"header":{
"actiontype":"00",
"currenttime":"1586003427105"
},
"body":{
"uuid":"dsasdadada",
"subject":"CN=AO",
"pubkey":"dsadsadsad",
"validaty":"",
"duration":"",
"certhash":""
}
},
"signature":{
"sign":"MEUCIQD1NNi3enH4IuivzTMdGt3NYuuIbui/2/zBbk/rZ/BPIQIgbDqaiS34rkkaSc/QWLaH2kfjTIT3lzWJN4M1IzzJ+TA=",
"alg":"sm3withSM2",
"pubkey":"MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw=="
},
"eccert":{
"type":"00",
"value":"fsafasdsadasdas"
}
},
"signature":{
"sign":"MEYCIQDdwZByaHxSBYGZv2mD9VjKjatzkx8FkbWRjwd+aVJuyAIhALgERohg8KTaVqkun2kjcHHe+7HBgFEIZCbyZo8m0pA2",
"alg":"sm3withSM2",
"pubkey":"MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw=="
}
}
这种方法特别的麻烦,每次向服务器发起请求时必须手动编写一次,这些代码存在高度的重复性,只是body不同,其它都是类似的,这样写代码复用性不高,存在代码臃肿,不利于阅读和项目拓展。
我的第二种解决方案(静态工厂模式和建造者模式)
- 将报文拆分成多个可重用组件(AtRequestBody、Ec、EcCert、Head、request和Signature)
- 使用静态工厂方法创建对应的对象
- 使用建造者模式创建AtRequestBody,实例化每个对象,组装对象
这种方法代码比较多,实例化起来也不是很简单,调用也不是很方便,而且组装的时候碰到一个问题,怎么保证AtRequestBody转化成json字符串按照,怎么保证转化是按照bean的字段顺序转化呢?也是本文的标题的关键字。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.8.1</version>
</dependency>
使用jackson的注解@JsonPropertyOrder({“ec”,“signature”}),这个会确保Java bean转化成jsonStr时按照指定顺序序列化。
package com.infosec.ra.common.build.outer.request;
import cn.com.infosec.hsm.crypto.HSM;
import cn.com.infosec.hsm.crypto.IHSM;
import cn.com.infosec.util.encoders.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.infosec.ra.common.RAConstant;
import com.infosec.ra.common.RAEnum;
import com.infosec.ra.common.build.Builder;
import com.infosec.ra.common.build.outer.Signature;
import com.infosec.ra.util.IHSMUtil;
import lombok.*;
import java.security.PublicKey;
/**
* @author Javacfox
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"ec","signature"})
public class AtRequestBody {
private Ec ec;
private Signature signature;
public void setSignature(String alg){
try {
IHSM sjj1507 = HSM.getInst("SOFT");
sjj1507.init();
IHSMUtil.setSjj1507(sjj1507);
byte[] sm3withSM2s = IHSMUtil.sign("1", 256, "12345678".toCharArray(), JSON.toJSONString(this.ec).getBytes(), "SM3withSM2", RAConstant.SM2ID);
PublicKey p1= IHSMUtil.exportPublicKey("SM2", IHSM.SIGN, "1", 256,"12345678".toCharArray());
byte[] encoded = p1.getEncoded();
this.signature = Builder.of(Signature::new)
.with(Signature::setAlg,alg)
.with(Signature::setPubkey, Base64.toBase64String(encoded))
.with(Signature::setSign,Base64.toBase64String(sm3withSM2s))
.build();
}catch (Exception e){
e.printStackTrace();
}
}
public static String getMsg(String actionType, String certType,String cert,JSONObject jsonObject) throws JsonProcessingException {
Ec ec = Builder.of(Ec::new)
.with(Ec::setRequest, actionType, System.currentTimeMillis() + "", jsonObject)
.with(Ec::setSignature, RAConstant.SIGN_ALG)
.with(Ec::setEcCert, certType, cert)
.build();
AtRequestBody build = Builder.of(AtRequestBody::new)
.with(AtRequestBody::setEc, ec)
.with(AtRequestBody::setSignature, RAConstant.SIGN_ALG)
.build();
ObjectMapper objectMapper = new ObjectMapper();
String s = objectMapper.writeValueAsString(build);
//JSONObject jsonObject1 = JSONObject.parseObject(JSONObject.toJSON(build).toString(), Feature.OrderedField);
return s;
}
}
代码比较多,就不一一贴了,不然篇幅太长。
我的第三种解决方案(java8 通用builder)
- 将报文拆分成多个可重用组件(AtRequestBody、Ec、EcCert、Head、request和Signature)
- 使用Supplier接口实例化对象
- 使用Consumer接口初始化对象
- 使用jackson确保java bean系列化时按照字段顺序
具体代码如下:
一、将报文拆分成可复用组件(java bean)
- Signature
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.*;
/**
* @author Javacfox
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"alg","pubkey","sign"})
public class Signature {
private String alg;
private String pubkey;
private String sign;
}
2.Request
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.infosec.ra.common.build.Builder;
import lombok.*;
/**
* @author Javacfox
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"header","body"})
public class Request {
private Head header;
private JSONObject body;
public void setHeader(String actiontype,String currenttime){
this.header = Builder.of(Head::new)
.with(Head::setActiontype,actiontype)
.with(Head::setCurrenttime,currenttime)
.build();
}
}
3.head
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.*;
/**
* @author Javacfox
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"actiontype","currenttime"})
public class Head {
private String actiontype;
private String currenttime;
}
4.EcCert
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.*;
/**
* @author Javacfox
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"type","value"})
public class EcCert {
private String type;
private String value;
}
5.ec
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.infosec.ra.common.build.Builder;
import lombok.*;
/**
* @author Javacfox
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"request","signature","eccert"})
public class Ec extends RequestBody {
private EcCert eccert;
public void setEcCert(String type,String value){
this.eccert = Builder.of(EcCert::new)
.with(EcCert::setType, type)
.with(EcCert::setValue, value)
.build();
}
}
6.AtRequestBody
import cn.com.infosec.hsm.crypto.HSM;
import cn.com.infosec.hsm.crypto.IHSM;
import cn.com.infosec.util.encoders.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.infosec.ra.common.RAConstant;
import com.infosec.ra.common.RAEnum;
import com.infosec.ra.common.build.Builder;
import com.infosec.ra.common.build.outer.Signature;
import com.infosec.ra.util.IHSMUtil;
import lombok.*;
import java.security.PublicKey;
/**
* @author Javacfox
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"ec","signature"})
public class AtRequestBody {
private Ec ec;
private Signature signature;
public void setSignature(String alg){
try {
IHSM sjj1507 = HSM.getInst("SOFT");
sjj1507.init();
IHSMUtil.setSjj1507(sjj1507);
byte[] sm3withSM2s = IHSMUtil.sign("1", 256, "12345678".toCharArray(), JSON.toJSONString(this.ec).getBytes(), "SM3withSM2", RAConstant.SM2ID);
PublicKey p1= IHSMUtil.exportPublicKey("SM2", IHSM.SIGN, "1", 256,"12345678".toCharArray());
byte[] encoded = p1.getEncoded();
this.signature = Builder.of(Signature::new)
.with(Signature::setAlg,alg)
.with(Signature::setPubkey, Base64.toBase64String(encoded))
.with(Signature::setSign,Base64.toBase64String(sm3withSM2s))
.build();
}catch (Exception e){
e.printStackTrace();
}
}
public static String getMsg(String actionType, String certType,String cert,JSONObject jsonObject) throws JsonProcessingException {
Ec ec = Builder.of(Ec::new)
.with(Ec::setRequest, actionType, System.currentTimeMillis() + "", jsonObject)
.with(Ec::setSignature, RAConstant.SIGN_ALG)
.with(Ec::setEcCert, certType, cert)
.build();
AtRequestBody build = Builder.of(AtRequestBody::new)
.with(AtRequestBody::setEc, ec)
.with(AtRequestBody::setSignature, RAConstant.SIGN_ALG)
.build();
ObjectMapper objectMapper = new ObjectMapper();
String s = objectMapper.writeValueAsString(build);
//JSONObject jsonObject1 = JSONObject.parseObject(JSONObject.toJSON(build).toString(), Feature.OrderedField);
return s;
}
}
二、创建建造器(Builder)
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* @author Javacfox
*/
public class Builder<T> {
private final Supplier<T> instantiator;
private List<Consumer<T>> modifiers = new ArrayList<>();
public Builder(Supplier<T> instant) {
this.instantiator = instant;
}
public static <T> Builder<T> of(Supplier<T> instant) {
return new Builder<>(instant);
}
public <P1> Builder<T> with(Consumer1<T, P1> consumer, P1 p1) {
Consumer<T> c = instance -> consumer.accept(instance, p1);
modifiers.add(c);
return this;
}
public <P1, P2> Builder<T> with(Consumer2<T, P1, P2> consumer, P1 p1, P2 p2) {
Consumer<T> c = instance -> consumer.accept(instance, p1, p2);
modifiers.add(c);
return this;
}
public <P1, P2, P3> Builder<T> with(Consumer3<T, P1, P2, P3> consumer, P1 p1, P2 p2, P3 p3) {
Consumer<T> c = instance -> consumer.accept(instance, p1, p2, p3);
modifiers.add(c);
return this;
}
public <P1, P2, P3,P4> Builder<T> with(Consumer4<T, P1, P2, P3,P4> consumer, P1 p1, P2 p2, P3 p3,P4 p4) {
Consumer<T> c = instance -> consumer.accept(instance, p1, p2, p3,p4);
modifiers.add(c);
return this;
}
public <P1, P2, P3,P4,P5> Builder<T> with(Consumer5<T, P1, P2, P3,P4,P5> consumer, P1 p1, P2 p2, P3 p3,P4 p4,P5 p5) {
Consumer<T> c = instance -> consumer.accept(instance, p1, p2, p3,p4,p5);
modifiers.add(c);
return this;
}
public T build() {
T value = instantiator.get();
modifiers.forEach(modifier -> modifier.accept(value));
modifiers.clear();
return value;
}
/**
* 1 参数 Consumer
*/
@FunctionalInterface
public interface Consumer1<T, P1> {
/**
* 接收参数方法
* @param t 对象
* @param p1 参数二
*/
void accept(T t, P1 p1);
}
/**
* 2 参数 Consumer
*/
@FunctionalInterface
public interface Consumer2<T, P1, P2> {
/**
* 接收参数方法
* @param t 对象
* @param p1 参数一
* @param p2 参数二
*/
void accept(T t, P1 p1, P2 p2);
}
/**
* 3 参数 Consumer
*/
@FunctionalInterface
public interface Consumer3<T, P1, P2, P3> {
/**
* 接收参数方法
* @param t 对象
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
*/
void accept(T t, P1 p1, P2 p2, P3 p3);
}
@FunctionalInterface
public interface Consumer4<T, P1, P2, P3, P4>{
/**
* 接收参数方法
* @param t 对象
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @param p4 参数四
*/
void accept(T t,P1 p1,P2 p2,P3 p3,P4 p4);
}
@FunctionalInterface
public interface Consumer5<T, P1, P2, P3, P4, P5>{
/**
* 接收参数方法
* @param t 对象
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @param p4 参数四
* @param p5 参数五
*/
void accept(T t,P1 p1,P2 p2,P3 p3,P4 p4,P5 p5);
}
}
- 调用方法
JSONObject jsonObject = new JSONObject(true);
jsonObject.put("uuid","dsasdadada");
jsonObject.put("subject","CN=AO");
jsonObject.put("pubkey","dsadsadsad");
jsonObject.put("validaty","");
jsonObject.put("duration","");
jsonObject.put("certhash","");
System.out.println(AtRequestBody.getMsg("00",RAEnum.CertType.EC.getValue(),"fsafasdsadasdas",jsonObject));
- 运行结果
{
"ec":{
"request":{
"header":{
"actiontype":"00",
"currenttime":"1586003427105"
},
"body":{
"uuid":"dsasdadada",
"subject":"CN=AO",
"pubkey":"dsadsadsad",
"validaty":"",
"duration":"",
"certhash":""
}
},
"signature":{
"sign":"MEUCIQD1NNi3enH4IuivzTMdGt3NYuuIbui/2/zBbk/rZ/BPIQIgbDqaiS34rkkaSc/QWLaH2kfjTIT3lzWJN4M1IzzJ+TA=",
"alg":"sm3withSM2",
"pubkey":"MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw=="
},
"eccert":{
"type":"00",
"value":"fsafasdsadasdas"
}
},
"signature":{
"sign":"MEYCIQDdwZByaHxSBYGZv2mD9VjKjatzkx8FkbWRjwd+aVJuyAIhALgERohg8KTaVqkun2kjcHHe+7HBgFEIZCbyZo8m0pA2",
"alg":"sm3withSM2",
"pubkey":"MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw=="
}
}
很明显,和第一次结果是一致的,但是比起第一次少了特别多代码,很简洁干净。
总结:
代码不可能一次就能既简洁又好用,复用性和拓展性高,需要反复的重构和优化,不断的提高自己的代码能力。