使用java手写一个sdk

使用java手写一个sdk

java实现sdk详细步骤

背景

最近接了一个私活,虚拟货币相关的app。涉及到了和Wallet Service对接api,可是这种私营服务平台并没有提供sdk,只给了我一个word文档,里面记录了各种api地址,参数,response等等(吐槽下,文档多半是初中生写的,杂乱不堪,看了一会我就成了大头儿子.......)。由于之前有做过JD的第三方服务开发,JD有提供一套sdk库。所以,本文将借鉴京东sdk的一些设计模式,给Wallet Service编写了一个sdk以供自己使用

大致分为以下几个结构

sdk

- Client: 客户端模块。负责请求处理,参数构造,响应结果解析

- response: 响应值。针对不同api接口,制定不同的响应Model

- request: 请求模块。指定api地址,参数,请求方式(POST/GET),参数传输协议等等

- http/https: 请求发送模块

- jsonParser: json解析模块

1 Client.java

public interface Client {

    <T extends AbstractResponse> T execute(Request<T> request) throws Exception;

}

提供执行请求发送的接口。

2 Request.java

public interface Request<T extends AbstractResponse> {
    /**
     * 获取请求地址
     */
    String getApiUrl();

    /**
     * 获取响应值实例类构造器
     */
    Class<T> getResponseClass();

    /**
     * 获取请求参数
     */
    Object getUrlParams();

    /**
     * 获取请求方式 POST/GET....
     */
    String getMethod();

    /**
     * 获取参数传输方式 Application_Json/Application_from_urlencoded
     */
    ContentType getContentType();
}

制定请求地址,响应模型类构造器,请求参数,请求方式等接口

3 AbstractRequest.java

// lombok模块 
@Setter
@Getter
public abstract class AbstractRequest {

    
    private final DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    protected String url;

    protected String timestamp;

    protected String version;

    public String j_code;

    protected ContentType contentType = ContentType.create(ContentType.APPLICATION_FORM_URLENCODED.getMimeType(),"UTF-8");

    /**
     * 请求的服务器地址 可以自己适配到配置文件中
     */
    protected String baseUrl = "http://250.250.250.250:8000";
    /**
     * 请求参数Map
     */
    protected Map<String,Object> urlParamsMap = new HashMap<>();

    //默认为POST方式
    protected String method = RequestMethod.POST.toString();

    public AbstractRequest() {
        this.timestamp = this.sdf.format(new Date());
        this.version = "1.0";
    }

    /**
     * @param method 调用方式 暂时只支持POST和GET 默认POST  如果需要是GET 请调用此构造函数指定
     */
    public AbstractRequest(String method){
        this.timestamp = this.sdf.format(new Date());
        this.version = "1.0";
        this.method = method;
    }

    /**
     * 
     * @param method       调用方式 暂时只支持POST和GET 默认POST
     * @param contentType  参数传输方式
     */
    public AbstractRequest(String method,ContentType contentType){
        this.timestamp = this.sdf.format(new Date());
        this.version = "1.0";
        this.method = method;
        this.contentType = contentType;
    }

    protected abstract void setUrlParamsMap(Map<String,Object> map);

如果@Getter @Setter报错,请导入lombok模块。或者自己替换getter/setter方法

4 AbstractResponse.java

@Setter
@Getter
@NoArgsConstructor
public abstract class AbstractResponse<T extends AbstractResult> implements Serializable {

    private static final long serialVersionUID = -1029647126543106295L;
    //服务器响应状态值
    private String code;
    //请求地址
    private String url;
    //是否成功
    private boolean success;

}

5 MyFirstResponse.java

public class MyFirstResponse extends AbstractResponse<MyFirstResponse.ResponseMsgResult> {

    /**
     * 响应结构 对象
     */
    private MyFirstResponse.ResponseMsgResult responseMsg;

    public MyFirstResponse.ResponseMsgResult getResponseMsg() {
        return responseMsg;
    }

    public void setResponseMsg(MyFirstResponse.ResponseMsgResult responseMsg) {
        this.responseMsg = responseMsg;
    }

    /**
     * 响应参数
     */
    public static class ResponseMsgResult extends AbstractResult {

        private String userName;

        private String age;

        public String getUserName() {
            return userName;
        }

        public void setUserName(String userName) {
            this.userName = userName;
        }

        public String getAge() {
            return age;
        }

        public void setAge(String age) {
            this.age = age;
        }
    }

}

指定参数返回结构

MyFirstRequest.java

@Getter
@Setter
public class MyFirstRequest extends AbstractRequest implements Request<MyFirstResponse> {

    /**
     * 参数 userId
     */
    private String userId;
    /**
     * 参数 userName
     */
    private String userName;

    @Override
    protected void setUrlParamsMap(Map<String, Object> map) {
        //设置参数
        map.put("userId",this.userId);
        map.put("userName",this.userName);
    }

    @Override
    public String getApiUrl() {
        //设置请求地址
        return getBaseUrl() + "/testSdk.do";
    }

    @Override
    public Class<MyFirstResponse> getResponseClass() {
        return MyFirstResponse.class;
    }

    @Override
    public Object getUrlParams() {
        setUrlParamsMap(super.urlParamsMap);
        return super.urlParamsMap;
    }
}

SSLClient.java

public class SSLClient extends DefaultHttpClient {

    public SSLClient() throws Exception{
        super();
        SSLContext ctx = SSLContext.getInstance("TLS");
        X509TrustManager tm = new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain,
                                           String authType) throws CertificateException {
            }
            @Override
            public void checkServerTrusted(X509Certificate[] chain,
                                           String authType) throws CertificateException {
            }
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        ctx.init(null, new TrustManager[]{tm}, null);
        SSLSocketFactory ssf = new SSLSocketFactory(ctx,SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        ClientConnectionManager ccm = this.getConnectionManager();
        SchemeRegistry sr = ccm.getSchemeRegistry();
        sr.register(new Scheme("https", 443, ssf));
    }
}

绕过https的证书限制

HttpRequest.java

public class HttpRequest {

    private final static Log log = LogFactory.getLog(HttpRequest.class);
    public static final String GZIP_CONTENT_TYPE = "application/x-gzip";
    public static final String DEFUALT_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=UTF-8";
    public static final String USER_AGENT = "Mozilla/5.0 (Linux; Android 4.4.4;  en-us; Nexus 4 Build/JOP40D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2307.2 Mobile Safari/537.36";

    public static int default_socketTimeout = 10000;
    public static final String _DEFAULT_CODING = "UTF-8";

    private HttpRequest(){}

    /**
     * 发送HTTP_POST请求  时默认采用UTF-8解码
     * 该方法会自动对<code>params</code>中的[中文][|][ ]等特殊字符进行<code>URLEncoder.encode(string,encodeCharset)</code>
     * @param reqURL  请求的url
     * @param params 请求参数
     */
    public static String sendPostRequest(String reqURL, Map<String, Object> params) throws Exception {
        return sendPostRequest(reqURL, params, false, null, null,null);
    }

    /**
     * 发送HTTP_POST请求  时默认采用UTF-8解码
     * @param reqURL  请求的url
     * @param params 请求参数
     * @param contentType  参数类型
     */
    public static String sendPostRequest(String reqURL, Map<String, Object> params, ContentType contentType) throws Exception {
        return sendPostRequest(reqURL, params, false, null, null,contentType);
    }

    /**
     * 发送HTTP_GET请求
     * @param reqURL 请求的url地址(含参数),默认采用utf-8编码
     */
    public static String sendGetRequest(String reqURL) throws Exception {
        return sendGetRequest(reqURL,null,false);
    }

    public static String sendGetRequest(String reqURL, String encoding, Boolean inGZIP) throws Exception {
        long responseLength = 0;//响应长度
        String responseContent = null; //响应内容
        CloseableHttpClient httpClient;
        if(reqURL.startsWith("https:")){
            httpClient = new SSLClient();
        }else{
            httpClient = HttpClients.createDefault();
        }
        HttpGet httpGet = new HttpGet(reqURL);

        if(inGZIP){
            httpGet.setHeader(HTTP.CONTENT_TYPE,GZIP_CONTENT_TYPE);
        }
        httpGet.setHeader(HTTP.USER_AGENT, USER_AGENT);
        httpGet.setHeader(HTTP.CONTENT_TYPE,DEFUALT_CONTENT_TYPE);
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(default_socketTimeout).setConnectTimeout(default_socketTimeout).build();
        httpGet.setConfig(requestConfig);

        try {
            CloseableHttpResponse response = httpClient.execute(httpGet);
            try {
                HttpEntity entity = response.getEntity();
                if(null != entity){
                    responseLength = entity.getContentLength();

                    if(inGZIP) {
                        responseContent = unGZipContent(entity, encoding == null ? "UTF-8" : encoding);
                    } else {
                        responseContent = EntityUtils.toString(entity, encoding == null ? "UTF-8" : encoding);
                    }

                    close(entity);
                }
                log.debug("请求地址: " + httpGet.getURI());
                log.debug("响应状态: " + response.getStatusLine());
                log.debug("响应长度: " + responseLength);
                log.debug("响应内容: " + responseContent);
            } finally {
                response.close();//关闭连接,释放资源
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return responseContent;
    }

    public static String sendPostRequest(String reqURL, Map<String, Object> params, Boolean gzip, String encodeCharset, String decodeCharset, ContentType content_type) throws Exception {
        String responseContent = null;
        CloseableHttpClient httpClient;

        if(reqURL.startsWith("https:")){
            httpClient = new SSLClient();
        }else{
            httpClient = HttpClients.createDefault();
        }

        HttpPost httpPost = new HttpPost(reqURL);

        if(content_type == null){
            content_type = ContentType.APPLICATION_FORM_URLENCODED;
        }

        if(gzip){
            httpPost.setHeader(HTTP.CONTENT_TYPE,GZIP_CONTENT_TYPE);
        }
        httpPost.setHeader(HTTP.USER_AGENT, USER_AGENT);
        httpPost.setHeader(HTTP.CONTENT_TYPE,content_type.getMimeType());

        List<NameValuePair> formParams = new ArrayList<>(); //创建参数队列
        Set<Map.Entry<String, Object>>  paramSet = params.entrySet();

        if(paramSet.size() > 0){
            for(Map.Entry<String,Object> entry : paramSet){
                formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()+""));
            }
        }
        log.debug("send params:"+formParams.toString());

        try {
            // 设置参数
            if(ContentType.APPLICATION_JSON.getMimeType().equals(content_type.getMimeType())){
                httpPost.setEntity(new StringEntity(JSON.toJSONString(params), encodeCharset==null ? _DEFAULT_CODING : encodeCharset));
            }else if(ContentType.APPLICATION_FORM_URLENCODED.getMimeType().equals(content_type.getMimeType())){
                httpPost.setEntity(new UrlEncodedFormEntity(formParams, encodeCharset==null ? _DEFAULT_CODING : encodeCharset));
            }

            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(default_socketTimeout).setConnectTimeout(default_socketTimeout).build();//设置请求和传输超时时间
            httpPost.setConfig(requestConfig);
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                HttpEntity entity = response.getEntity();
                if (null != entity) {
                    String contentType = "";
                    Header[] headers = httpPost.getHeaders(HTTP.CONTENT_TYPE);
                    if (headers != null && headers.length > 0) {
                        contentType = headers[0].getValue();
                    }

                    if (contentType.equalsIgnoreCase(GZIP_CONTENT_TYPE)) {
                        responseContent = unGZipContent(entity, decodeCharset == null ? _DEFAULT_CODING : decodeCharset);
                    } else {
                        responseContent = EntityUtils.toString(entity, decodeCharset == null ? _DEFAULT_CODING : decodeCharset);
                    }
                    close(entity);
                }

                log.debug("请求地址: " + httpPost.getURI());
                log.debug("响应状态: " + response.getStatusLine());
                log.debug("响应内容: " + responseContent);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return responseContent;
    }
    /**
     * 解压
     */
    private static String unGZipContent(HttpEntity entity, String encoding) throws IOException {
        StringBuilder responseContent = new StringBuilder();
        GZIPInputStream gis = new GZIPInputStream(entity.getContent());
        int count = 0;
        byte[] data = new byte[1024];
        while ((count = gis.read(data, 0, 1024)) != -1) {
            String str = new String(data, 0, count,encoding);
            responseContent.append(str);
        }
        return responseContent.toString();
    }

    /**
     * 压缩
     */
    public static ByteArrayOutputStream gZipContent(String sendData) throws IOException{
        if (CommonUtils.isEmpty(sendData)) {
            return null;
        }

        ByteArrayOutputStream originalContent = new ByteArrayOutputStream();
        originalContent.write(sendData.getBytes(StandardCharsets.UTF_8));

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        GZIPOutputStream gzipOut = new GZIPOutputStream(baos);
        originalContent.writeTo(gzipOut);
        gzipOut.close();
        return baos;
    }

    private static void close(HttpEntity entity) throws IOException {
        if (entity == null) {
            return;
        }
        if (entity.isStreaming()) {
            final InputStream instream = entity.getContent();
            if (instream != null) {
                instream.close();
            }
        }
    }

    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>(2);
        map.put("uuid","1");
//        String string = HttpRequest.sendGetRequest("https://www.baidu.com?uuid=1");
//        String s = HttpRequest.sendPostRequest("https://www.baidu.com",map);
//        System.out.println(s);
    }

}

这个工具类是从网上拷贝的,如果不满意可自行替换。

Parser.java

public interface Parser {

    <T extends AbstractResponse> T parse(String var1, Class<T> var2) throws Exception;
}

ParserFactory.java


@Getter
public class ParserFactory{

    public static final Parser JSON_PARSER = new JsonParser();

}

后面可能存在很多解析方式,这儿做了一个拓展

JsonParser.java

public class JsonParser implements Parser {

    private final ObjectMapper mapper = new ObjectMapper();

    public JsonParser() {
        this.mapper.getDeserializationConfig().set(DeserializationConfig.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
        this.mapper.getDeserializationConfig().set(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
        this.mapper.getDeserializationConfig().set(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    public <T extends AbstractResponse> T parse(String json, Class<T> responseClass) throws Exception {
        AbstractResponse response;

        try {
            if (UtilsService.isEmpty(json)) {
                throw new Exception("response json is empty!");
            } else {
                response = this.fromJson(json, responseClass);
                return (T)response;
            }
        } catch (Exception e) {
            throw new Exception(e);
        }
    }
    /**
     * 将JSON文本解析为 responseClass实例
     */
    private <T extends AbstractResponse> T fromJson(String json, Class<T> responseClass) throws Exception {
        ObjectNode rootNode;

        try {
            rootNode = (ObjectNode)this.mapper.readTree(json);
        } catch (Exception e) {
            rootNode = (ObjectNode)this.mapper.readTree(JSON.toJSONString(json));
        }

        String innerJson = this.mapper.writeValueAsString(rootNode);
        mapper.configure(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
        return this.mapper.readValue(innerJson, responseClass);
    }
}

MyClient.java

@Getter
@Setter
public class MyClient implements Client {

    private static final Logger logger = Logger.getLogger(WalletClient.class);
    public static final String CHARSET_UTF8 = "UTF-8";
    private int connectTimeout;
    private int readTimeout;

    /**
     * 初始化客户端构造函数
     * todo: 如果需要自定义http请求超时时间,请重载一个构造函数
     */
    public MyClient() {
        this.connectTimeout = 0;
        this.readTimeout = 0;
    }

    @Override
    public <T extends AbstractResponse> T execute(Request<T> request) throws Exception {
        //获取请求地址
        String apiUrl = request.getApiUrl();

        if(UtilsService.isEmpty(apiUrl)){
            throw new ParamDeficiencyException(ExceptionEnum.CLIENT_INIT_ERR);
        }
        String responseMsg = "";
        try {
            switch (request.getMethod()){
                case "POST":
                    responseMsg = HttpRequest.sendPostRequest(apiUrl,(Map)request.getUrlParams(),request.getContentType());
                    break;
                case "GET":
                    responseMsg = HttpRequest.sendGetRequest(apiUrl);
            }
        } catch (Exception e) {
            throw new HttpException("【api调用异常 url:{"+request.getApiUrl()+"} 报错文本:{"+e.getMessage()+"}】");
        }
        String jsonStr = machineJson(responseMsg,request);

        return this.parser(jsonStr,request.getResponseClass());
    }

    private String machineJson(String responseMsg,Request request) throws Exception {

        JSONObject jsonObject = new JSONObject();

        jsonObject.put("code",200);
        jsonObject.put("url",request.getApiUrl());

        //获取泛型类
        Class cls;
        try {
            cls = (Class)((ParameterizedType)request.getResponseClass().getGenericSuperclass()).getActualTypeArguments()[0];
        } catch (Exception e) {
            throw new Exception(e);
        }
        jsonObject.put("responseMsg", JSON.parseObject(responseMsg,cls));
        return jsonObject.toJSONString();
    }

    private <T extends AbstractResponse> T parser(String responseMsg, Class<T> responseClass) throws Exception {
        return ParserFactory.JSON_PARSER.parse(responseMsg,responseClass);
    }
}

测试

    @Test
    public void testSdk() throws Exception {

        Client client = new MyClient();

        MyFirstRequest request = new MyFirstRequest();
        //请求参数
        request.setUserId("9527");
        request.setUserName("xiaomingblog.cn");
        
        //执行 并且使用对应的Response模型类接受
        MyFirstResponse response = client.execute(request);

        System.out.println(response.getResponseMsg().getAddress());
        
    }

OVER

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×