目前,我正在准备一个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,也许我会在不久的将来对此进行探索。
前提条件:
- Java jdk1.8 安装了
- Maven 比如版本3.9.2安装了
- 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