Java开发SDK详解->服务端开发

一、前言

  • 最近在对接外部平台的接口,对方提供了一个sdk,开发中直接引入到项目中。自己只需要组装参数,直接调用sdk中的方法就可以实现接口对接。
  • sdk中包含了参数校验,加密验签,Http接口调用,还提供了各个接口入参和出参的实体类。 这些为我们减少很多的开发量,同时将一些不合规的入参直接在客户端过滤掉,减少资源浪费。还提供了一套完整的加密验签方法,避免了双发对接中因为加解密验签方法的不同导致的一些问题。很大程度上提高了我们的对接中开发联调的成本。
  • sdk优点这么多,下面我们来开发一个简单的sdk吧。

二、步骤概述

  1. 首先我们开发一下服务端: 服务端是一个SpringBoot项目提供了一个用户列表查询的接口。入参包含调用方分唯一编号(appId),加密的入参信息(data),入参的加解密方式使用RSA非对称加密算法。

  2. 其次开发SDK: SDK是一个普通的maven项目,提供用户列表查询接口的Http调用方法,还有接口的参数校验,入参的加密,返回信息的解密。最后将项目打成jar包。

  3. 最后开发客户端调试: 客户端是一个SpringBoot项目,项目中引入上面打包好的SDK,组装参数,调用用户列表查询接口。

三、服务端开发

其实服务端很简单了,就是一个普通的@RestController接口,入参是Json格式。业务相关的入参要加密传递。这里我们服务端使用私钥加解密,客户端使用公钥加解密。

3.1代码实现
3.1.1 服务端入口(SysUserApiController)

这个类是接收客户端请求的入口,继承了ApiAbstract

package com.lh.api;

import com.alibaba.fastjson.JSONObject;
import com.lh.common.BaseResponse;
import com.lh.entity.api.HopeRequest;
import com.lh.entity.api.HopeResponse;
import com.lh.entity.common.PageModel;
import com.lh.entity.sys.SysUser;
import com.lh.entity.sys.SysUserDTO;
import com.lh.service.SysUserService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/api/user/")
public class SysUserApiController extends ApiAbstract<BaseResponse<PageModel<SysUser>>> {

    @Resource
    private SysUserService sysUserService;

    @Override
    public BaseResponse<PageModel<SysUser>> work(JSONObject o) {
        return sysUserService.queryUserList(JSONObject.toJavaObject(o, SysUserDTO.class));
    }

    @PostMapping("/queryUserList")
    public HopeResponse queryUserList(@RequestBody HopeRequest hopeRequest) {
        return process(hopeRequest);
    }

}

3.1.2 接口的抽象类(ApiAbstract)

这个类提供一些公用的方法,私钥的获取,入参的解密,出参的加密等。

package com.lh.api;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.lh.entity.api.HopeRequest;
import com.lh.entity.api.HopeResponse;
import com.lh.utils.RsaUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;

@Slf4j
public abstract class ApiAbstract<R> {

    private static final Map<String, String> PRIVATE_KEY_MAP = new HashMap<>();

    static {
        PRIVATE_KEY_MAP.put("000001", "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKWVEnI3IDn+Q4qkX5VXfbYg3t+jCfPkYyW2OdYsck8T7F26xVgfP/cLRyTbuy/uYc/ROsns8vFQfJ5Qn08Y5nZ/KsFGKQo0Thx7WKE2BDGisgnrwb7vR4fI6vrmZDFFhriVQ4cww8eCJw3/a8YKPAv8RPx5BTTIhECg6c7xq8C5AgMBAAECgYBqqH6sUbQRhyYsnTrwsN3KPpwxxAy6LKhs91eEDTdIltXPfmp2HBMQ6BPYuy11b4FT4zuSLG5FYQoU1Gv6AFkaegxvTPXiaXLAIaeAdIPycwCYpHO3FoyUlfTDA5YuCVgXxwRhIUuJBPyFEi5nVKgadnE2vSl/sV6U7/adAWMELQJBAOi+HoI0eP33SZCiqXyJyABdXG2ZWJqN3PYWfvOafuqLSUT2WuVW2qiWZhzdMboY8FhvUVMWpdoM9LB+5yG6w68CQQC2IOXgIONfwCSwp4jDA8RRgcRkEtN8IFhtE6ezDt4Alqc5kmg0bRARSq/QDV3OxFw59jniif2XfE9TYcXlLZQXAkBYk7KsvyelEC4RO7bhTNavPNjmZUwRVhhYF+us7omK3gO+mTuyuMFzE/o81foM51zha0w6IQ5x1Chz23g/oALFAkEApU6e1xuCxPnFU4H8o+TbX4+FDb/4rIU/PWHFqMQpxCb5iUvVwR/soYVEpnY7WtznSMSwLWpJ/iEabvgjgqwn6QJAb21uWtuRTuBBQj1nTk9YgyiALHk4xSQqMEOGHFVEGQ5NAmMgSCZq6XbfwCZ4lBIyXGjrRJs6q4WsEBO9BDfdGw==");
    }

    /**
     * 统一处理
     *
     * @param hopeRequest 统一请求
     * @return 统一响应
     */
    public HopeResponse process(HopeRequest hopeRequest) {
        try {
            String privateKey = getPrivateKey(hopeRequest.getAppId());
            JSONObject o = before(hopeRequest.getData(), privateKey);
            System.out.println(o);
            R r = work(o);
            return after(r, privateKey);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("ServerAbstract process exception! hopeRequest={}", hopeRequest, e);
            return HopeResponse.builder()
                    .success(false)
                    .message("系统异常")
                    .build();
        }

    }

    /**
     * 处理前置解密
     *
     * @param data       请求密文
     * @param privateKey 秘钥
     * @return 请求参数
     */
    private JSONObject before(String data, String privateKey) {
        return JSON.parseObject(RsaUtil.decryptPrivate(privateKey, data));
    }

    /**
     * 业务处理
     *
     * @param o 入参
     * @return 返回
     */
    abstract R work(JSONObject o);

    /**
     * 后置处理加密
     *
     * @param r          业务数据
     * @param privateKey 秘钥
     * @return 统一返回
     */
    private HopeResponse after(R r, String privateKey) {
        return HopeResponse.builder()
                .success(true)
                .message("交易成功")
                .data(RsaUtil.encryptPrivate(privateKey, JSON.toJSONString(r)))
                .build();
    }

    /**
     * 通过appId获取对应的私钥,不同的接入方提供不同的公私钥。
     * 实际业务开发中这些会存在文件中或者配置中心中如阿波罗,这里简单实现
     *
     * @param appId appId
     * @return 私钥
     */
    private String getPrivateKey(String appId) {
        return PRIVATE_KEY_MAP.get(appId);
    }
}
3.1.3 接口入参实体类(HopeRequest)

这个是接口的入参,这里简单演示,一个客户端唯一编号(用来获取对应的私钥),一个是加密的入参。

package com.lh.entity.api;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HopeRequest {
    /**
     * 客户端唯一编号
     */
    private String appId;

    /**
     * 加密后业务相关的入参
     */
    private String data;
}

3.1.4 接口返回实体类(HopeResponse)

这个是服务端接口的返回信息。返回的字段有成功表示,提示信息,还有业务相关的查询结果(查询到的用户信息)加密的信息。

package com.lh.entity.api;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HopeResponse {

    /**
     * 是否成功
     */
    private boolean success;

    /**
     * 返回信息
     */
    private String message;

    /**
     * 业务相关的返回信息,私钥加密之后的
     */
    private String data;

}
3.1.5 具体的业务实现层(SysUserService)

这里是具体的业务层,通过入参去数据库查询对应的结果,在封装成实体类返回,这里不再深入。PageMode是用查询的结果封装的分页实体类。BaseResponse是业务层处理的结果有返回码和提示信息。其实这里不用封装这么多层,我这个是用以前的老代码简单的做个示例。

package com.lh.service;

import com.lh.common.BaseResponse;
import com.lh.entity.sys.SysUser;
import com.lh.entity.common.PageModel;
import com.lh.entity.sys.SysUserDTO;

public interface SysUserService {
    BaseResponse<PageModel<SysUser>> queryUserList(SysUserDTO dto);
}
3.1.6 业务相关的入参(SysUserDTO)

这个类是真正传递业务相关查询条件的实体类。PageBase 是一个分页相关的类,参数为pageSize和pageIndex。

package com.lh.entity.sys;

import com.lh.entity.common.PageBase;
import lombok.Data;

@Data
public class SysUserDTO extends PageBase {

    /**
     * 昵称
     */
    private String userId;

    /**
     * 用户名称
     */
    private String userName;

    /**
     * 用户手机号
     */
    private String userPhone;

    /**
     * 用户邮箱
     */
    private String userEmail;

    /**
     * 用户状态 0、正常 1、锁定 2、注销
     */
    private Integer status;
}
3.1.7 RSA加解密的工具类(RsaUtil)
package com.lh.utils;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;

@Slf4j
public class RsaUtil {

    /**
     * 算法加解密算法
     */
    private static final String ALGORITHM = "RSA";

    /**
     * 最大加密字节数,超出最大字节数需要分组加密
     */
    private static final Integer MAX_ENCRYPT_BLOCK = 117;

    /**
     * 最大解密字节数,超出最大字节数需要分组解密
     */
    private static final Integer MAX_DECRYPT_BLOCK = 128;

    /**
     * 请求报文公钥解密
     *
     * @param publicKeyString 公钥
     * @param text            报文
     * @return 加密报文
     */
    public static String encryptPublic(String publicKeyString, String text) {
        try {
            PublicKey publicKey = getPublicKey(publicKeyString);
            return encryptRSA(publicKey, text);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("RsaUtil encryptPublic exception! publicKeyString={} text={}", publicKeyString, text);
            return null;
        }
    }

    /**
     * 应答报文公钥解密
     *
     * @param publicKeyString 公钥
     * @param text            应答密文
     * @return 解密报文
     */
    public static String decryptPublic(String publicKeyString, String text) {
        try {
            PublicKey publicKey = getPublicKey(publicKeyString);
            return decryptRSA(publicKey, text);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("RsaUtil decryptPublic exception! publicKeyString={} text={}", publicKeyString, text);
            return null;
        }
    }

    /**
     * 请求报文私钥解密
     *
     * @param privateKeyString 私钥
     * @param text             报文
     * @return 加密报文
     */
    public static String encryptPrivate(String privateKeyString, String text) {
        try {
            PrivateKey privateKey = getPrivateKey(privateKeyString);
            return encryptRSA(privateKey, text);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("RsaUtil encryptPrivate exception! publicKeyString={} text={}", privateKeyString, text);
            return null;
        }
    }

    /**
     * 应答报文私钥解密
     *
     * @param privateKeyString 私钥
     * @param text             应答密文
     * @return 解密报文
     */
    public static String decryptPrivate(String privateKeyString, String text) {
        try {
            PrivateKey privateKey = getPrivateKey(privateKeyString);
            return decryptRSA(privateKey, text);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("RsaUtil decryptPrivate exception! privateKeyString={} text={}", privateKeyString, text);
            return null;
        }
    }

    /**
     * RSA 加密
     *
     * @param key  密钥
     * @param text 原文
     * @return 密文
     * @throws Exception 异常
     */
    private static String encryptRSA(Key key, String text) throws Exception {
        // 创建加密对象
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 对加密进行初始化 第一个参数是加密模式,第二个参数是你想用的公钥加密还是私钥加密
        cipher.init(Cipher.ENCRYPT_MODE, key);
        // 分段加密
        byte[] make = doCrypt(text.getBytes(), cipher, MAX_ENCRYPT_BLOCK);
        return Base64.encode(make);
    }

    /**
     * RSA 解密
     *
     * @param key  密钥
     * @param text 密文
     * @return 明文
     * @throws Exception 异常
     */
    private static String decryptRSA(Key key, String text) throws Exception {
        // 创建加解密对象
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 对解密进行初始化 第一个参数是加密模式,第二个参数是你想用的公钥解密还是私钥解密
        cipher.init(Cipher.DECRYPT_MODE, key);
        //分段解密
        byte[] make = doCrypt(Base64.decode(text), cipher, MAX_DECRYPT_BLOCK);
        return new String(make);
    }

    /**
     * 分段加解密
     *
     * @param data     要加解密的内容数组
     * @param cipher   加解密对象
     * @param maxBlock 分段大小
     * @return 结果
     * @throws IllegalBlockSizeException 异常
     * @throws BadPaddingException       异常
     */
    private static byte[] doCrypt(byte[] data, Cipher cipher, Integer maxBlock) throws IllegalBlockSizeException, BadPaddingException {
        int inputLength = data.length;
        // 标识
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache;
        while (inputLength - offSet > 0) {
            if (inputLength - offSet > maxBlock) {
                cache = cipher.doFinal(data, offSet, maxBlock);
                offSet += maxBlock;
            } else {
                cache = cipher.doFinal(data, offSet, inputLength - offSet);
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }

    /**
     * 获取私钥
     *
     * @param privateKeyString 私钥路径
     * @return 私钥
     */
    private static PrivateKey getPrivateKey(String privateKeyString) throws Exception {
        // 创建key的工厂
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        // 创建私钥key的规则
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString));
        // 返回私钥对象
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * 获取公钥
     *
     * @param publicKeyString 公钥
     * @return 公钥
     * @throws Exception 异常
     */
    private static PublicKey getPublicKey(String publicKeyString) throws Exception {
        // 创建key的工厂
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        // 创建公钥key的规则
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(publicKeyString));
        // 返回公钥对象
        return keyFactory.generatePublic(keySpec);
    }
}
3.2 启动项目

现在服务端已经开发好了,启动项目等待sdk开发好客户端调用吧~

四、最后

  • 11
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值