大家好,我是霸戈!上次写完 “淘宝” 开放平台接口设计思路后,有不少粉丝就提到什么时候会出下一篇,所以今天打算写一篇开放平台SDK设计的一些思路,同时我也会做一些实践,一步步把我的设计思路进行落地。
在写这篇文章之前,我个人因为工作的原因使用过淘宝、京东开放平台的一些SDk,也学到不少设计思路,这次准备将我的思路落地成可以使用的SDK,大概的设计会分为三个模块,分别为:
- 数据传输模块:主要用于传输请求数据,本文使用http协议传输数据
- 序列化模块:用户序列化和反序列化数据,SDK是给客户使用应该尽可能的去适配客户的意愿,如客户首先的序列化方式为JSON那,作为使用方肯定是会希望SDK能支持JSON序列化和反序列化的
- 应用模块:主要是协调数据传输、序列化之间的工作,同时对数据做一些校验签名操作
数据传输
数据传输协议使用较为通用的http协议
,一开始是想是否做一个通用的设计来支持其他的传输方式,如支持socket
保持长链接,后来想想没必要过度设计,如果要做到支持不同的传输协议,那么就得做很大一部分适配的编码,实事上可能支持http后以后都会保持不变,没有这个必要!
目前有很多开源的http
工具,如httpclient
、okhttp
还有URLConnection
的方式等,选择哪一种呢? 好的方法是自己抽象出来一个接口,然后提供一个默认的实现类就好,这样就能保持一定的灵活性。
/**
* http协议接口
*/
public interface HttpClient {
/**
* 执行http post请求
* @param url
* @param data
* @param headers
* @return
*/
String post(String url, byte[] data, Map<String, List<String>> headers) throws HttpClientException;
}
复制代码
HttpClient
只定义了一个post
方法,满足需求即可(偷懒需求~),大部分开放平台的接口都是post
请求方式的,没必要把所有的http特性都给定义出来,一个post
就是干!
接下来就用HttpURLConnection
发起http请求吧:
package com.javaobj.sdk.http.impl;
public class DefaultHttpClient implements HttpClient {
public String post(String url, byte[] data, Map<String, List<String>> headers) throws HttpClientException {
URL targetUrl = null;
try {
targetUrl = new URL(url);
} catch (MalformedURLException e) {
throw new HttpClientException("访问的url链接不正确");
}
try {
HttpURLConnection connection = (HttpURLConnection) targetUrl.openConnection();
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setRequestMethod("POST");
//设置讲求头
if(headers != null){
Map<String, List<String>> tmp = new HashMap<String, List<String>>(headers);
for(Map.Entry<String, List<String>> entry : tmp.entrySet()){
for(String value: entry.getValue()){
connection.setRequestProperty(entry.getKey(), value);
}
}
}
connection.connect();
//发送请求数据
OutputStream out = connection.getOutputStream();
out.write(data);
out.close();
//读取请求响应
InputStream in = connection.getInputStream();
ByteOutputStream body = new ByteOutputStream();
int ch;
while ((ch = in.read()) != -1){
body.write(ch);
}
in.close();
String responseBody = new String(body.getBytes());
int statusCode = connection.getResponseCode();
boolean is2xxSuccessful = statusCode >= 200 && statusCode <= 299;
boolean is3xxSuccessful = statusCode >= 300 && statusCode <= 399;
//判断请求是否成功
if (!(is2xxSuccessful || is3xxSuccessful)) {
throw new HttpStatusClientException(statusCode, connection.getResponseMessage(), responseBody);
}
return responseBody;
} catch (IOException e) {
throw new HttpClientException(e);
}
}
}
复制代码
以上就实现了一个简单的http客户端了, 基本满足需求。
序列化和反序列化
序列化和反序列化就是将数据在Java实体之间转换,为了设计的灵活一点,此处面向接口编程,分别为序列化和反序列化定义一个接口:
- Serializer
- Deserializer
同时使用jackson
库做了一个简单的实现:
JacksonSerializer
public class JacksonSerializer implements Serializer {
private final ObjectMapper objectMapper = new ObjectMapper();
public JacksonSerializer() {
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
}
public byte[] serialize(Object obj) {
try {
return objectMapper.writeValueAsBytes(obj);
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
}
}
复制代码
JacksonDeserializer
public class JacksonDeserializer implements Deserializer {
private final ObjectMapper objectMapper = new ObjectMapper();
public JacksonDeserializer() {
objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
}
public <T> T deserialize(byte[] data, Class<T> clz) {
try {
return objectMapper.readValue(data, clz);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
复制代码
这样一个简单的序列化和反序列化工具就做好了,这样基本能满足需求,但是一般的开放平台都有format
参数,它的值可能json
、xml
等等,为了正好的整合序列化和反序列化, 我又创建了一个SerializationAdapter
接口,继承Serializer
和Deserializer
:
public interface SerializationAdapter extends Serializer, Deserializer{
/**
* 判断是否支持数据格式
* @return
*/
boolean support(String format);
}
复制代码
添加SerializationAdapter.support
方法用来判断它是否支持指定的数据格式,如适配json
数据格式,创建一个实现类JSONSerializationAdapter
:
JSONSerializationAdapter
public class JSONSerializationAdapter implements SerializationAdapter {
private final Serializer serializer;
private final Deserializer deserializer;
public JSONSerializationAdapter(Serializer serializer, Deserializer deserializer) {
this.serializer = serializer;
this.deserializer = deserializer;
}
public JSONSerializationAdapter() {
this(new JacksonSerializer(), new JacksonDeserializer());
}
@Override
public boolean support(String format) {
return format != null && Objects.equals(format.toLowerCase(), "json");
}
@Override
public <T> T deserialize(byte[] data, Class<T> clz) {
return deserializer.deserialize(data, clz);
}
@Override
public byte[] serialize(Object obj) {
return serializer.serialize(obj);
}
}
复制代码
这样就比较灵活了, 假如说老板在后面偷偷的来一句,这个要是能支持xml
就完美了,这个需求很简单! 假如我们使用JAXB
来实现xml
的序列化,那么先写好序列化和反序列化器:
JAXBSerializer
public class JAXBSerializer implements Serializer {
@Override
public byte[] serialize(Object obj) {
ByteOutputStream outputStream = new ByteOutputStream();
JAXB.marshal(obj, outputStream);
return outputStream.getBytes();
}
}
复制代码
JAXBDeserializer
public class JAXBDeserializer implements Deserializer {
@Override
public <T> T deserialize(byte[] data, Class<T> clz) {
//trim去掉不需要的换行
return JAXB.unmarshal(new StringReader(new String(data).trim()), clz);
}
}
复制代码
XMLSerializationAdapter
public class XMLSerializationAdapter implements SerializationAdapter {
private final Serializer serializer;
private final Deserializer deserializer;
public XMLSerializationAdapter(Serializer serializer, Deserializer deserializer) {
this.serializer = serializer;
this.deserializer = deserializer;
}
public XMLSerializationAdapter() {
this(new JAXBSerializer(), new JAXBDeserializer());
}
@Override
public boolean support(String format) {
return format != null && Objects.equals(format.toLowerCase(), "xml");
}
@Override
public <T> T deserialize(byte[] data, Class<T> clz) {
return deserializer.deserialize(data, clz);
}
@Override
public byte[] serialize(Object obj) {
return serializer.serialize(obj);
}
}
复制代码
同理其他数据格式也可以自定定义,就不多做阐述了。
如何使用?
使用也是非常简单的, 简单的写个测试方法测试下:
public class SerializationTest {
@Test
public void testSerialization(){
String format = "xml";
List<SerializationAdapter> adapters = new ArrayList<>();
adapters.add(new JSONSerializationAdapter());
adapters.add(new XMLSerializationAdapter());
Person person = new Person();
person.setName("霸戈");
person.setAge(18);
for(SerializationAdapter adapter : adapters){
if (adapter.support(format)) {
byte[] data = adapter.serialize(person);
System.out.println(format + "序列化后的数据:" + new String(data));
Person person2 = adapter.deserialize(data, Person.class);
System.out.println(format + "反序列化后的数据:" + person2);
}
}
}
}
复制代码
以上测试代码,只需要改变format
变量的值就能在json
和xml
格式之间转换。
JSON格式输出
json序列化后的数据:{"name":"霸戈","age":18}
json反序列化后的数据:Person{name='霸戈', age=18}
复制代码
XML格式输出
xml序列化后的数据:<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person>
<age>18</age>
<name>霸戈</name>
</person>
xml反序列化后的数据:Person{name='霸戈', age=18}
复制代码
API客户端
剩下的工具就是编写一个简单的API客户端,发起http请求、处理响应数据。
public interface SdkClient {
/**
* 执行API请求
* @param req
* @param <T>
* @return
*/
<T extends AbstractResponse> T execute(AbstractRequest<T> req);
}
复制代码
因为SDK都是给他人使用,尽可能的方便,入参(AbstractRequest),和出参(AbstractResponse)都用实体类表示。
AbstractRequest
public abstract class AbstractRequest<T extends AbstractResponse> {
/**
* 请求方法名称
* @return
*/
public abstract String getMethod();
/**
* 接口请求参数
* @return
*/
public abstract Map<String, Object> getApiParams();
/**
* 请求响应实体
* @return
*/
public abstract Class<T> getResponseClass();
}
复制代码
AbstractResponse
public class AbstractResponse {
private String code;
private String msg;
public boolean isSuccess(){
return Objects.equals(code, "0");
}
}
复制代码
DefaultClient
一个简单的API客户端
public class DefaultClient implements SdkClient {
private SerializationAdapter serializationAdapter = new JSONSerializationAdapter();
private HttpClient httpClient = new DefaultHttpClient();
/**
* 请求地址
*/
private final String endpoint;
/**
* 应用id
*/
private final String appid;
/**
* 应用密钥
*/
private final String appsecret;
public DefaultClient(String endpoint, String appid, String appsecret) {
this.endpoint = endpoint;
this.appid = appid;
this.appsecret = appsecret;
}
@Override
public <T extends AbstractResponse> T execute(AbstractRequest<T> req) {
Map<String, Object> params = req.getApiParams();
String timestamp = System.currentTimeMillis() + "";
//参数排序
StringBuilder paramsStr = new StringBuilder();
Map<String, Object> treeMap = new TreeMap<>(params);
for(Map.Entry<String, Object> entry : treeMap.entrySet()){
paramsStr.append(entry.getKey()).append(entry.getValue());
}
//计算签名
//应用密钥 + 应用id + 请求方法名称 + api版本号 + 请求参数 + 应用密钥
String sign = Md5Util.md5(appsecret + appid + req.getMethod() + req.getVersion() + paramsStr.toString() + appsecret );
String url = endpoint + "?"
+ "method=" + req.getMethod()
+ "&version=" + req.getVersion()
+ "×tamp" + timestamp
+ "&sign" + sign
+ "&appid" + appid;
Map<String, List<String>> headers = new HashMap<>();
headers.put("Content-type", Collections.singletonList("application/json"));
byte[] body = serializationAdapter.serialize(req.getApiParams());
String response = httpClient.post(url, body, headers);
return serializationAdapter.deserialize(response.getBytes(), req.getResponseClass());
}
}
复制代码
总结
至此一个简单的开放平台SDK就基本成型了,为了适配大多数客户的开放环境,应当注意以下几点:
- 尽量使用低版本jdk开发,如1.5、1.6,覆盖绝大多数Java运行/开发环境
- 减少第三方依赖,避免版本冲突
- 出参入参都使用java实体类,避免客户使用时参数名称、类型不正确
还有些注意点没想到的,大家阔以评论区留言,一起学习交流。