Skip to content
Yizzuide edited this page Nov 3, 2020 · 3 revisions

Instructions(模块说明)

Echo is a powerful tool for invoking third-party services and base on Spring RestTemplate, which unified parameter signature and response field processing.

Echo是一个调用第三方服务利器,基于Spring RestTemplate,实现了统一的参数签名与响应字段处理。

Dependencies(依赖)

<dependency>
  <groupId>com.github.yizzuide</groupId>
  <artifactId>milkomeda-spring-boot-starter</artifactId>
  <version>${milkomeda-last-version}</version>
</dependency>

Enable with annotation(启用模块)

@EnableEcho
@SpringBootApplication
public class MilkomedaDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(MilkomedaDemoApplication.class, args);
    }
}

Config with application.yml(在Spring Boot项目的application.yml里配置)

milkomeda:
  # Only show log with dev environment, the `condition` is condition match object addition from framework.
  # 仅在dev环境开启info日志, `condition`是框架附加的条件判别对象
  show-log: ${condition.equals(${spring.application.name}, dev)}
  echo:
    read-timeout: 20s

Example

1. Define response data

The interface EchoResponseData specifies the specification of 'code', 'msg' and 'data', if the fields are different, you need to adapt them.

EchoResponseData接口定了规范的codemsgdata,如果字段相同就需要适配一下。

@Data
public class SimpleEchoResponseData<T> implements EchoResponseData<T> {
    private String code;
    private String errorMsg;
    private T data;

    // If the getter for the parameter is inconsistent with the interface definition, you can do so.
    // 如果参数的getter跟接口定义不一致,可以这样适配
    @Override
    public String getMsg() {
        return errorMsg;
    }
}

2. Extends echo request

@Slf4j
@Component
public class SimpleEchoRequest extends EchoRequest {
    // Must be override to return reponse data type
    // 必须返回一个响应映射类型
    @Override
    protected <T> EchoResponseData<T> responseData() {
        return new SimpleEchoResponseData<>();
    }

    // If request content type is application/x-www-form-urlencoded, add and override below.
    // 如果使用application/x-www-form-urlencoded表单通讯方式,覆盖下面方法。
    /*@Override
    protected void appendHeaders(HttpHeaders headers) {
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    }*/

    // Signature
    @Override
    protected void signParam(Map<String, Object> inParams, Map<String, Object> outParams) {
        // If request content type is application/x-www-form-urlencoded, `outParams` type is LinkedMultiValueMap
        // 如果使用application/x-www-form-urlencoded表单通讯方式,则outParams为LinkedMultiValueMap
        /*if (outParams instanceof LinkedMultiValueMap) {
            LinkedMultiValueMap multiValueMap = (LinkedMultiValueMap) outParams;
            multiValueMap.add("appId", "1000");
            multiValueMap.add("timestamp", "2019-09-21 17:12:00");
            multiValueMap.add("version", "1.0");
            // ...
        }*/

        // If request content type is application/json
        // 使用json通信方式的添加方式
        outParams.put("appId", "1000");
        outParams.put("version", "1.0");
        // Remove empty value
        DataTypeConvertUtil.clearEmptyValue(inParams); 
        String bizContent = JSONUtil.serialize(inParams);
        outParams.put("biz_data", bizContent);
        // Convert map to form data, such as name=val&name2=val2...
        String signStr = DataTypeConvertUtil.map2FormData(outParams, false);
        log.info("sign str:{}", signStr);

        String sign = EncryptUtil.sign(signStr, getPriKey(), EncryptUtil.SIGN_TYPE_RSA2);
        outParams.put("sign", sign);
    }

    // Signature verification
    // 验签
    @Override
    public Map<String, Object> verifyParam(Map<String, Object> inParams) {
        String sign = (String) inParams.remove("sign");
        String signStr = DataTypeConvertUtil.map2FormData(inParams, false);
        log.info("sign str:{}", signStr);
        boolean isVerified = EncryptUtil.verify(signStr, sign, getParPubKey(), EncryptUtil.SIGN_TYPE_RSA2);
        if (!isVerified) {
            log.error("verify fail,params:{}", inParams);
            return null;
        }
        return inParams;
    }

    // This method for check reponse is ok
    // 这个方法用于检测响应是否成功
    @Override
    protected void checkResponse(EchoResponseData responseData) throws EchoException {
        if (!("200".equals(responseData.getCode()))) {
            log.error("Response error with msg: {}, code:{}", responseData.getMsg(), responseData.getCode());
        }
    }

    private String getParPubKey() {
        return "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCa9bXW/GTqseFLWxBfnECbxaNcMTAGDojSnmwtUcPd9mwnevRguOIDbOxbSsIwDtN9bw3o16V5N+Y7iuluEHsrWhrhC9RQx6LA9h8nuTE6c1HSstgq7y+DSPvZrbou5zZnDbgP45M2LT2MXd3HaApq+Ocvg5gp11WhKRa4AgXerQIDAQAB";
    }

    private String getPriKey() {
        return "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAJr1tdb8ZOqx4UtbEF+cQJvFo1wxMAYOiNKebC1Rw932bCd69GC44gNs7FtKwjAO031vDejXpXk35juK6W4QeytaGuEL1FDHosD2Hye5MTpzUdKy2CrvL4NI+9mtui7nNmcNuA/jkzYtPYxd3cdoCmr45y+DmCnXVaEpFrgCBd6tAgMBAAECgYBuFRGZ6XFTnQw8uTN3iIwJXSzBCJxiIR8n6K1WwJhRbYbFwT4sHAtLfaym6gPrmgy6NhN+jvuZkpF3SSatLv4f3vwu3ToZcmi6A0LlVzFT7cMHBzMP/Ev09aa0N/j9+ykPlJH06ehkvwz/504GEDwLt2791MxWqtZJjuDNWloWQQJBAOaQ+jgUmjIkKX09/x0a8P13JezBP14UV5cZLvoRWW8XzTkfZx/rpC7irpijvcwBhi45kIg8JrIngYm5/QSkLDECQQCsDakrLtIJLenTSHmFi2KfI2EHYT0deFrK92+VDY15iE7gBQBvbiZiAKwjV33gcrtS6JTZWtxKeOAUUPTiUgc9AkEA1Gb+e6dPHZ3+sqfoWxG0rGuU/nRQQgUPY90JT8mn0BXnMxZg1CEqkR62pVtCv6svx2m0Yiy3oSuPxCcYlawAIQJAW5lORjJAGij6gsTkBagmkkjYoIAxdF4eIE7JdhZoCpr6OyQOjkSbZLOs8Yfj+Tm75zDyBiHshC2ERuyu40r+lQJBAJ+SImDYm9NLqo6hq1+FTI1apKyq8rsuQYL8IRsYAHmOGCNmHN1b/AFitHaptZXYOtiZyuEP8xP86Np8vrTUKSg=";
    }
}

3. Usage(开始使用)

@Slf4j
@RestController
public class EchoController {

    @Resource
    private EchoRequest simpleEchoRequest;

    // Mock a third-party service
    // 模拟一个第三方平台开户接口
    @RequestMapping("/echo/account/open")
    public ResponseEntity<Map<String, Object>> openAccount(@RequestBody Map<String, Object> params) {
        log.info("params:{}", params);
        Map<String, Object> data = new HashMap<>();
        // Signature verification
        // 验签
        Map<String, Object> map = simpleEchoRequest.verifyParam(params);
        if (map == null) {
            data.put("code", "403");
            data.put("error_msg", "signature verification fail");
            data.put("data", null);
        } else {
            data.put("code", "200");
            data.put("error_msg", "");
            data.put("data", new HashMap<String, Object>(){
                private static final long serialVersionUID = -7494033976315538458L;
                {
                    put("order_id", "12343243434324324");
                }});
        }
        return ResponseEntity.ok(data);
    }

    // Test to invoke above service
    // 请求上面第三方平台开户接口
    @RequestMapping("/test/echo/account/open")
    public String requestOpenAccount() {
        Map<String, Object> reqParams = new HashMap<>();
        reqParams.put("uid", "1101");
        reqParams.put("name", "yiz");
        reqParams.put("id_no", "14324357894594483");

         EchoResponseData<Map<String, Object>> responseData = simpleEchoRequest.fetch(HttpMethod.POST, "http://localhost:8080/echo/account/open", reqParams);
        log.info("receive response data: {}", responseData);
        return "OK";
    }
}