Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Jacky_Liu
Product and Topic Expert
Product and Topic Expert
411
Currently I am preparing  a scenario of side by side extension for SAP S/4 HANA CLOUD with SPA . In the scenario, a SPA process will be triggered by outbound delivery created event from S/4 Hana cloud , then the SPA process will use action to call a logistic Api to create Express order  and save the related information. The first step for the POC is  calling a logistic Api . I am located in Shanghai China and CaiNiao Logistic Platform is very famous and developer friendly. Through CaiNiao Platform Api , we can handle business with  express company like DHL, Fedex,EMS, UPS accross the world . The supported express company are in the linkage which is in Chinese .

Actually  I tried with other express companies's API, they need me to provide company informations and credit card which is not so good for me since I only need to carry out POC of a solution as a solution advisory.

Today I want to demo how to generated  a spring-boot application and deploy it in BTP Cloud foundry to call  CaiNiao Logistic Platform API which can be consumed directly from  SPA action.  Then you may ask why we don't call the Api directly. The reason is the CaiNiao Logistic Platform API has a complecated logic for sigining .  We can not call it directly in SPA action or CPI . I think we can create a adopter for it in CPI . Maybe I will investicate this in near future .

Prerequisite:

  1.   JDK 1.8 has been installed .

  2.   Mavan like version 3.9.2 has been installed .

  3.   Cloud Foundry Command Line Interface has been installed.


Step 1 ,  Register a account in CaiNiao Logistic Platform API with my mobile phone, I will get appId and appSecret which will be used in the spring-boot application .



 

Step 2, Generate a spring-boot application with the following command


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"

 

Step 3, Add the following dependency in 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>


Step 4, Add package service under package com.sap.cap.expresscost3




Step 5, Add class Service under package service with following code :


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;
}



}


Step 6, Add class MD5Utils under package service with the following code:


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];
}
}

Step 7, Add class ExpressCost under package controllers with the following code :


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);
}



}


Step 8, Build the application:





Step 9, Deploy application to BTP Cloud foundry .






 

Step 10, Test with postman.



 


 

The Ends!

Thanks for your time!

Best regards!

 

Jacky Liu