0. 前言
数据对接在开发过程中非常常见,与平台合作开发或者使用其他平台的接口是家常便饭,虽然对接的平台各有不同,但是对接方式基本大同小异,下面整理了一些自己在开发过程中对接时遇到的一些方式或事项。
注:所列代码中较多使用Hutool工具类,需要在maven中导入,Hutool是一款非常便捷的工具包,感兴趣可以参看官方文档:Hutool — 🍬A set of tools that keep Java sweet.
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.1</version>
<scope>compile</scope>
</dependency>
1. 签名方式
1.1 参数排序拼接
对请求参数按照字母顺序升序排列,一般采用 head + key1 + value1 + key2 + value2 + ... + tail 的模式,head一般为密钥等,tail常见密钥或者请求的json字符串,拼接后进行MD5加密,下面的代码为参数增无符号拼接,使用了Hutool的MD5加密工具,可以按需进行调整
// 非空参数名按字母排序,键值拼接,头/尾添加参数,MD5加密
public static String getMd5SignContent(Map<String, Object> sortedParams, String appCode, String body) {
// 1. 根据key排序
TreeMap<String, Object> map = new TreeMap<>(sortedParams);
// 2. 拼接参数名和参数值
StringBuilder content = new StringBuilder();
map.forEach((key, value) -> {
if (null != key && null != value) {
content.append(key).append(value);
}
});
String sign = appCode + content + body;
// 3. 生成32位大写MD5值
return DigestUtils.md5Hex(sign).toUpperCase();
}
1.2 某个字段加密
直接对比如密钥等特殊字段进行加密,可以直接使用上面的最后一行代码
2. 请求方式
借助Hutool的http工具进行访问,url为平台提供的接口地址,post为请求方式,可以换成get等,header中包含请求头信息,可以放置token/content-type等信息,body用于存放请求体信息,execute()执行返回请求结果
HttpResponse response = HttpRequest.post(url).header("Content-Type", CONTENT_TYPE).body(body).execute();
2.1 表单数据
private static final String CONTENT_TYPE = "application/x-www-form-urlencoded"
2.2 字节数据
private static final String CONTENT_TYPE = "application/octet-stream;charset=UTF-8";
2.3 JSON数据
private static final String CONTENT_TYPE = "application/json"
3. 数据传输方式
3.1 加密传输
为了数据传输的安全性考虑,对传输数据进行加密处理,目前仅遇到过AES128加密,代码如下,使用了Hutool的加密对象
public class CryptoUtils {
/**
* 数据传输的加解密使用对称加解密算法 AES 128 位加解密,加解密模式采用 CBC,填充模式采用 PKCS5Padding 方式
*/
private static final String MODE = "AES/CBC/PKCS5Padding";
/**
* AES128_CBC_PKCS5Padding 加密
*/
public static String encodeByAes128(String data, String key, String vector) {
SymmetricCrypto crypto = getSymmetricCrypto(key, vector);
byte[] encrypt = crypto.encrypt(data.getBytes());
return Base64.encode(encrypt);
}
/**
* AES128_CBC_PKCS5Padding 解密
*/
public static String decodeByAes128(String data, String key, String vector) {
SymmetricCrypto crypto = getSymmetricCrypto(key, vector);
// 先进行Base64解码
byte[] decodedData = Base64.decode(data);
byte[] decrypt = crypto.decrypt(decodedData);
return new String(decrypt, UTF_8);
}
/**
* 获取加解密对象
*/
private static SymmetricCrypto getSymmetricCrypto(String key, String vector) {
IvParameterSpec iv = new IvParameterSpec(vector.getBytes(UTF_8));
SecretKeySpec spec = new SecretKeySpec(key.getBytes(UTF_8), "AES");
return new SymmetricCrypto(MODE, spec, iv);
}
}
3.2 压缩传输
将数据进行压缩处理,以字节流形式传输,可以传输大量数据,下面的代码使用了Hutool的ZipUtil工具类,参数为json字符串
public class ZipUtils {
/**
* 压缩数据
*
* @param str 待压缩数据
* @return 压缩后数据字节
*/
public static byte[] zip(String str) {
byte[] gz;
try {
// 压缩数据
gz = ZipUtil.gzip(str.getBytes("UTF-8"));
} catch (Exception e) {
throw new BizException("数据压缩失败");
}
return Base64.encodeBase64(gz);
}
/**
* 解压数据
*
* @param str 待解压数据
* @return 解压后数据
*/
public static String unzip(String str) {
byte[] bt;
try {
// 解压数据
bt = ZipUtil.unGzip(Base64.decodeBase64(str));
} catch (Exception e) {
throw new BizException("数据解压失败");
}
String sbody = null;
try {
sbody = new String(bt, "UTF-8");
} catch (Exception e) {
throw new BizException("不支持的编码格式");
}
return sbody;
}
}
4. 其他事项
4.1 自增序列
常用于签名参数中,用来判断同一请求或频繁请求,下面的代码是一秒内自增的4位自增序列,可以按需调整。
public class SeqUtils {
private static final AtomicInteger counter = new AtomicInteger(0);
private static volatile long lastUnixTime;
/**
* 4 位自增序列取自时间戳,同一秒内按序列自增长,新秒重计,如0001
*/
public static synchronized String next(long unixTime){
if (unixTime != lastUnixTime) {
lastUnixTime = unixTime;
counter.set(0);
}
return String.format("%04d", counter.incrementAndGet() % 9999);
}
}
4.2 重定向
常见于对接数据中包含图片的时候,当我们想去下载该图片发现图片大小为0,而直接点击链接却可以访问图片,是因为图片地址进行了重定向,可以通过访问原地址的返回状态码来判断,并获取重定向的地址。
private String getReDirectionUrl(String source) {
// 请求目标地址
HttpResponse response = HttpRequest.get(source).execute();
// 获取相应状态码
int status = response.getStatus();
String location;
// 302:重定向
if (status == 302) {
// 获取重定向地址
location = response.header("Location");
System.out.println("重定向地址" + location);
} else {
// 正常返回
location = source;
}
return location;
}