开始前
本文使用了花生壳内网穿透工具来进行阿里奇门的接口自测。
开始阅读前,请确保您的开发者账号已经成功申请了“应用”。对接奇门前你需要成功申请应用,并拿到appkey和appsecret 。
奇门简介
奇门是阿里推出的系统总线(我个人是这么理解,企业总线)。通过奇门传输敏感数据(淘系订单敏感数据不通过奇门传输是违规的)。可以把奇门理解为一个中介,淘宝卖家只需与奇门进行一次对接,即可与其它淘宝卖家、erp软件服务商等进行对接。总之,接入奇门好处多多。但,阿里奇门官方给的文档多而乱,让初次接触的开发者摸不着头脑,本篇文章将为你详细讲解要点。
奇门场景介绍
标准场景是ERP-WMS,就是ERP软件与 仓储系统的对接。其它还有几种对接场景,请参阅阿里开放平台,比我描述的详细,我这里略过。
业务场景分析
我有一个天猫店铺,并且已经申请了商家后台应用。我想和我订购的某ERP软件对接。为啥要对接,因为我同时还代运营了几个店铺,这几个店铺和我不是同一法人。我发货的时候是无法获取到我代运营的店铺订单收货人信息的。怎么办?ERP软件服务商它是能获取到任何订购它应用的店铺里面的订单信息的(主要是收件人信息)。但是阿里规定不得外传敏感数据,需要走奇门。所以,我们经过奇门对接,erp服务商将敏感数据推送给我不算违规。
以WMS身份接入奇门
我是一个商家后台应用,以wms身份接入奇门,是允许的。我是wms,我负责发货,erp要把收货人信息传给我,不给我 我怎么发货。所以我需要开发一个接口,接收订单信息的接口,这个接口专门接收erp推送过来的数据。但是存在一个稳定,我自己开发的接口,与erpA服务商对接,等过一段时间,我不用A服务商的软件了,我用B服务商的erp软件,那我岂不是还要重新写一下接收订单信息的接口?或者我这边接口不动,B服务商针对我的接口它那边做变动?这岂不是很麻烦。然而,阿里已经考虑到这个问题,它将ERP-WMS所有可能的接口都列出来。就是它定义了一套标准接口,囊括各种标准情景下的接口字段,我们只需要实现我们需要的接口即可。总之,就是阿里定义了接口标准 ,我们去实现,大家都按照阿里提供的接口标准去开发,接入奇门。以后和别的系统对接就不用再定制开发了,直接联调就可以。
java开发奇门接口
前面说到,我们需要一个接收订单信息的接口(阿里官网定义接口,是入库单接口,我们以接收订单信息为例,意在阐述这其中的原理)。
上代码:
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping
public class QimenController {
@PostMapping(value = "",consumes ={MediaType.APPLICATION_XML_VALUE} ,produces = MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public String qimen(@RequestBody String xmlData){
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String ctype = request.getContentType();
String charset = WebUtils.getResponseCharset(ctype);
try {
Map<String, String> queryMap = TaobaoSignUtil.getQueryMap(request, charset);
String localSign = TaobaoSignUtil.signTopRequest(queryMap, xmlData, "8a76f1224723447fe950f869fdeae80", "md5");
System.out.println(localSign);
String sign = request.getParameter("sign") == null?"":request.getParameter("sign");
System.out.println("远程sign:"+sign);
if(localSign.equals(sign)){
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<response>\n" +
" <flag>success</flag>\n" +
" <code>0</code>\n" +
" <message>验签成功</message>\n" +
"</response>";
}else{
}
} catch (IOException e) {
e.printStackTrace();
}
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response>\n" +
" <flag>failure</flag>\n" +
" <code>-1</code>\n" +
" <message>验签失败</message>\n" +
"</response>";
}
public static void main(String[] args) throws ApiException {
DeliveryorderCreateRequest request = new DeliveryorderCreateRequest();
DeliveryorderCreateRequest.DeliveryOrder deliveryOrder = new DeliveryorderCreateRequest.DeliveryOrder();
DeliveryorderCreateRequest.Item item = new DeliveryorderCreateRequest.Item();
DeliveryorderCreateRequest.Batch batch = new DeliveryorderCreateRequest.Batch();
batch.setRemark("beizhu");
List<DeliveryorderCreateRequest.Batch> batches = new ArrayList<>();
batches.add(batch);
item.setBatchs(batches);
item.setRemark("beizhu");
DeliveryorderCreateRequest.PriceAdjustment priceAdjustment =new DeliveryorderCreateRequest.PriceAdjustment();
priceAdjustment.setRemark("beizhu");
item.setPriceAdjustment(priceAdjustment);
List<DeliveryorderCreateRequest.Item> items = new ArrayList<>();
request.setItems(items);
request.setDeliveryOrder(deliveryOrder);
request.setTimestamp(System.currentTimeMillis());
request.setCustomerId("13222781820");
request.setVersion("3.0");
DefaultQimenClient qimenClient1 = new DefaultQimenClient("http://qimen.api.taobao.com/router/qmtest", "appkey", "与自测页面填写的session一直即可");
// qimenClient1.set
DeliveryorderCreateResponse execute = qimenClient1.execute(request);
String body = execute.getBody();
System.out.println(execute.getFlag());
System.out.println(execute.getMessage());
}
}
上面代码是一个简单,可以用于验签的代码,无实际业务处理逻辑,只是对签名正确性进行了判断。能满足奇门接口的自测。代码中使用的工具类TaobaoUtils:
package com.zirui.openapi.util;
import com.taobao.api.Constants;
import com.taobao.api.internal.util.StringUtils;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URLDecoder;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class TaobaoSignUtil {
/**
*
*
* @param params
* 所有字符型的TOP请求参数(这个是除了sign以外的所有字段)
* @param body
* 请求主体内容
* @param secret
* 签名密钥
* @param signMethod
* 签名方法,目前支持:空(老md5)、md5, hmac_md5三种
* @return 签名
*/
public static String signTopRequest(Map<String, String> params, String body, String secret, String signMethod)
throws IOException {
// 第一步:检查参数是否已经排序
String[] keys = params.keySet().toArray(new String[0]);
Arrays.sort(keys);
// 第二步:把所有参数名和参数值串在一起
StringBuilder query = new StringBuilder();
if (Constants.SIGN_METHOD_MD5.equals(signMethod)) {
query.append(secret);
}
for (String key : keys) {
String value = params.get(key);
if (StringUtils.areNotEmpty(key, value) && !"sign".equals(key) ) {
query.append(key).append(value);
}
}
// 第三步:把请求主体拼接在参数后面
if (body != null) {
query.append(body);
}
// 第四步:使用MD5/HMAC加密
byte[] bytes;
if (Constants.SIGN_METHOD_HMAC.equals(signMethod)) {
bytes = encryptHMAC(query.toString(), secret);
} else if (Constants.SIGN_METHOD_HMAC_SHA256.equals(signMethod)) {
bytes = encryptHMACSHA256(query.toString(), secret);
} else {
query.append(secret);
System.out.println("签名字符串:"+query);
bytes = encryptMD5(query.toString());
}
// 第五步:把二进制转化为大写的十六进制
return byte2hex(bytes);
}
private static byte[] encryptHMAC(String data, String secret) throws IOException {
Object var2 = null;
try {
SecretKey secretKey = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacMD5");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
byte[] bytes = mac.doFinal(data.getBytes("UTF-8"));
return bytes;
} catch (GeneralSecurityException var5) {
throw new IOException(var5.toString());
}
}
private static byte[] encryptHMACSHA256(String data, String secret) throws IOException {
Object var2 = null;
try {
SecretKey secretKey = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
byte[] bytes = mac.doFinal(data.getBytes("UTF-8"));
return bytes;
} catch (GeneralSecurityException var5) {
throw new IOException(var5.toString());
}
}
public static byte[] encryptMD5(String data) throws IOException {
return encryptMD5(data.getBytes("UTF-8"));
}
public static byte[] encryptMD5(byte[] data) throws IOException {
Object var1 = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(data);
return bytes;
} catch (GeneralSecurityException var3) {
throw new IOException(var3.toString());
}
}
public static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for(int i = 0; i < bytes.length; ++i) {
String hex = Integer.toHexString(bytes[i] & 255);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}
public static Map<String, String> getQueryMap(HttpServletRequest request, String charset) throws IOException {
Map<String, String> queryMap = new HashMap();
String queryString = request.getQueryString();
String[] params = queryString.split("&");
for(int i = 0; i < params.length; ++i) {
String[] kv = params[i].split("=");
String key;
if (kv.length == 2) {
key = URLDecoder.decode(kv[0], charset);
String value = URLDecoder.decode(kv[1], charset);
queryMap.put(key, value);
} else if (kv.length == 1) {
key = URLDecoder.decode(kv[0], charset);
queryMap.put(key, "");
}
}
return queryMap;
}
}
TaobaoUtils 中代码参考了淘宝应用sdk源码。
当你写好了这段代码后,接下来就是要想,我如何自测。
在阿里云提供自测的页面,有一个填写自测地址的地方。
我们在本地开发后,外网是访问不了的,且我们每开发一点就部署到服务器也太麻烦了。别担心下面介绍如何使用花生壳内网穿透工具将你本地的程序暴露到外网。
花生壳是啥
花生壳是啥,是一个内网穿透的工具,可以使你的程序让外网访问,试想我们在本地电脑上开发启动程序,通过花生壳暴露到外网,然后在阿里云奇门自测页面填上我们的外网地址,可以边测边该,岂不效率飞起。
自测重点
自测重点是验签,即验证签名判断是否正确,如果你的程序(就我刚贡献的代码)能够准确的验签,那么自测阶段就结束了。我推荐使用花生壳来自测。
联调
自测完成之后,接下来就是联调阶段,联调阶段需要erp服务商配合,由erp服务商发起联调,调用我们自测通过的接口。