开发Spring-boot应用程序,连接菜鸟物流平台Api并将其部署到BTP Cloud Foundry

目前,我正在准备一个S/4 Hana Cloud的SPA并排扩展做。在这个场景中,来自S/4 Hana云的出库创建事件将触发一个SPA流程,然后SPA流程将使用行动调用物流Api来创建快递订单并保存相关信息。POC的第一步是调用物流Api。我位于中国上海,菜鸟物流平台非常著名,对开发商非常友好。通过菜鸟平台Api,我们可以与DHL、Fedex、EMS、UPS等快递公司进行业务往来。被支持的快递公司处于中文链接中。

事实上,我试过其他快递公司的API,他们需要我提供公司信息和信用卡,这对于我作为解决方案咨询的顾问来说不太好,因为我只需要进行解决方案的原型验证。

今天,我想演示如何生成一个Spring-boot应用程序,并将其部署在BTP Cloud Foundry中,以调用CaiNiao物流平台API,该应用可以直接从SPA Action中使用。然后你可能会问我们为什么不直接调用菜鸟物流Api。究其原因,是菜鸟物流平台API有一个比较复杂的签名逻辑。我们不能在SPA  Action或CPI中直接调用。我认为我们可以在CPI中创建一个Adater,也许我会在不久的将来对此进行探索。

 

前提条件:

  1. Java jdk1.8 安装了
  2. Maven 比如版本3.9.2安装了
  3. Cloud Foundry Command Line Interface 安装了

步骤1 用手机在菜鸟物流平台API注册账户,我们会得到appId和appSecret,它们将在Spring-boot应用程序中使用。

步骤2,使用以下命令生成Spring-boot应用程序

mvn archetype:generate “-DarchetypeGroupId=com.sap.cloud.sdk.archetypes” “-DarchetypeArtifactId=scp-cf-spring” “-DarchetypeVersion=RELEASE”  “-DgroupId=com.sap.sdk” “-DartifactId=expresscost3” “-Dpackage=com.sap.cap.expresscost3”

 

步骤3,在expresscost3/application/pom.xml中添加以下依赖项

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.14</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.31</version>
</dependency>

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>

 

步骤4,在包com.sap.cap.expresscost3下添加包 service

步骤5,在包service下添加类Service,代码如下:

package com.sap.cap.expresscost3.service;


import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;


import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.entity.EntityBuilder;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class Service {

    public static String httpPostWithForm(String url, Map<String, String> params, Map<String, String> headers) {
        // 用于接收返回的结果
        String resultData = "";
        try {
            HttpPost post = new HttpPost(url);
            //设置头部信息
            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    post.setHeader(entry.getKey(), entry.getValue());
                }
            }
            List<BasicNameValuePair> pairList = new ArrayList<>();
            for (String key : params.keySet()) {
                pairList.add(new BasicNameValuePair(key, params.get(key)));
            }
            UrlEncodedFormEntity uefe = new UrlEncodedFormEntity(pairList, "utf-8");
            post.setEntity(uefe);
            // 创建一个http客户端
            CloseableHttpClient httpClient = HttpClientBuilder.create().build();
            // 发送post请求
            HttpResponse response = httpClient.execute(post);
            resultData = EntityUtils.toString(response.getEntity(), "UTF-8");// 返回正常数据
        } catch (Exception e) {
            System.out.println("接口连接失败 e:" + e);
        }
        return resultData;
    }





    public static void wordSort(ArrayList<String> words) {
        for (int i = words.size() - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (words.get(j).compareToIgnoreCase(words.get(j + 1)) > 0) {
                    String temp = words.get(j);
                    words.set(j, words.get(j + 1));
                    words.set(j + 1, temp);
                }
            }
        }
    }

    public static String formatUrlParam(Map<String, String> paraMap, String encode, boolean isLower) {
        String params = "";
        Map<String, String> map = paraMap;
        try {
            List<Map.Entry<String, String>> itmes = new ArrayList<Map.Entry<String, String>>(map.entrySet());
            //对所有传入的参数按照字段名从小到大排序
            //Collections.sort(items); 默认正序
            //可通过实现Comparator接口的compare方法来完成自定义排序
            Collections.sort(itmes, new Comparator<Map.Entry<String, String>>() {
                @Override
                public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
                    // TODO Auto-generated method stub
                    return (o1.getKey().toString().compareTo(o2.getKey()));
                }
            });
            //构造URL 键值对的形式
            StringBuffer sb = new StringBuffer();
            for (Map.Entry<String, String> item : itmes) {
                if (StringUtils.isNotBlank(item.getKey())) {
                    String key = item.getKey();
                    String val = item.getValue();
                    val = URLEncoder.encode(val, encode);
                    if (isLower) {
                        sb.append(key.toLowerCase() + "=" + val);
                    } else {
                        sb.append(key + "=" + val);
                    }
                    sb.append("&");
                }
            }
            params = sb.toString();
            if (!params.isEmpty()) {
                params = params.substring(0, params.length() - 1);
            }
        } catch (Exception e) {
            return "";
        }
        return params;
    }

    public static String getAloneKeys(JSONObject json) {
        ArrayList<String> aloneKeys = new ArrayList<>();
        for (String key : json.keySet()) {
            aloneKeys.add(key);
        }
        // 排序
        wordSort(aloneKeys);
        // 整理排序后的json
        JSONObject newJson = new JSONObject(new LinkedHashMap<>());
        for (String key : aloneKeys) {
            newJson.put(key, json.get(key));
        }
        return newJson.toJSONString();
    }

    public static String getSign(String appSecret, Map<String, String> valueMap) {
        String soreValueMap = formatUrlParam(valueMap, "utf-8", true);//对参数按key进行字典升序排列
        String signValue = appSecret + soreValueMap;//将key拼接在请求参数的前面
        String md5SignValue = MD5Utils.MD5Encode(signValue, "utf8");//形成MD5加密后的签名
        return md5SignValue;
    }



}

步骤6,在包service下添加MD5Utils类,代码如下:

package com.sap.cap.expresscost3.service;

import java.security.MessageDigest;

public class MD5Utils
{

    private static final String hexDigIts[] = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};

    /**
     * MD5加密
     * @param origin 字符
     * @param charsetname 编码
     * @return
     */
    public static String MD5Encode(String origin, String charsetname){
        String resultString = null;
        try{
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if(null == charsetname || "".equals(charsetname)){
                resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
            }else{
                resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
            }
        }catch (Exception e){
        }
        return resultString;
    }


    public static String byteArrayToHexString(byte b[]){
        StringBuffer resultSb = new StringBuffer();
        for(int i = 0; i < b.length; i++){
            resultSb.append(byteToHexString(b[i]));
        }
        return resultSb.toString();
    }

    public static String byteToHexString(byte b){
        int n = b;
        if(n < 0){
            n += 256;
        }
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigIts[d1] + hexDigIts[d2];
    }
}

步骤7,使用以下代码在包controllers下添加类ExpressCost:

package com.sap.cap.expresscost3.controllers;

import com.google.gson.reflect.TypeToken;
import com.sap.cap.expresscost3.service.Service;
import  com.sap.cap.expresscost3.service.Service.*;
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import com.alibaba.fastjson.JSONObject;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/express")

public class ExpressCost {

    private String appSecret  = "appSecret from Step 1";
    private String cainiaoUrl = "https://express.xuanquetech.com/express/v1/" ;

    private Service serviceUtil = new Service();


    @RequestMapping(path = "/trace",method = RequestMethod.GET)

    public ResponseEntity<String> expressTrace(@RequestBody JSONObject body){
        System.out.println(body.toString());
        String sortStr = serviceUtil.getAloneKeys(body);
        System.out.println("TraceSortStr:"+sortStr);
        Map<String, String> map = new HashMap<String, String>();
        map.put("logistics_interface", sortStr);
        map.put("nonce", "1686210870");
        String sign = serviceUtil.getSign(appSecret, map);
        System.out.println("sign:" + sign);

        Map<String, String> headers = new HashMap<>();
        headers.put("appid", "appid from step 1");//替换你的app
        headers.put("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
        //组装参数

        Map<String, String> params = new HashMap();
        params.put("nonce", "1686210870");//时间戳(秒)
        params.put("sign", sign);//签名
        params.put("logistics_interface", body.toString());//订阅接口入参字段


        String url = cainiaoUrl+ "queryExpressRoutes" ;
        System.setProperty("console.encoding","UTF-8");
        String resultData = serviceUtil.httpPostWithForm(url, params, headers);
        return ResponseEntity.ok(resultData);
    }

    @RequestMapping(path = "/order",method = RequestMethod.POST)
    public ResponseEntity<JSONObject> order(@RequestBody JSONObject body){



        String url = cainiaoUrl + "orderService";


        String sortStr = serviceUtil.getAloneKeys(body);

        System.out.println("OrderSortStr:"+sortStr);
        Map<String, String> map = new HashMap<String, String>();

        for(String key: body.keySet()){
            map.put(key,body.get(key).toString());
        }

        String sign = serviceUtil.getSign(appSecret, map);
        System.out.println("sign:" + sign);


        Map<String, String> params = new HashMap();
        params.put("sign", sign);//签名

        for(String key: body.keySet()){
            params.put(key,body.get(key).toString());
        }


        Map<String, String> headers = new HashMap<>();
        headers.put("appid", "appId from Step 1");//替换你的app
        headers.put("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");


        String resultData = serviceUtil.httpPostWithForm(url, params, headers);
        System.out.println("result:" + resultData);

        JSONObject result = new JSONObject(JSON.parseObject(resultData));
        return ResponseEntity.ok(result);
    }



}

步骤8,构建应用程序

步骤9,将应用程序部署到BTP Cloud Foundry。

 

步骤10,用postman测试。

结束!

谢谢!

Jacky Liu

Scroll to Top