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
479
目前,我正在准备一个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