文档说明
原v6版本demo代码文件由快递100的qq技术群提供,本人在搬运之后做了适当的修改并贡献给大家。
需求说明
- 用户在公司的平台上 下寄件单,平台分为2个,小程序创建和后台系统上传;
- 在系统为其默认推荐一个快递公司、并且创建好寄件单之后,需同步(上传)至快递100,然后可以在快递100的系统上查看到我们的寄件单;
- 在公司系统上,可以查看到用户所下的寄件单以及同步状态,可为寄件单更换快递公司,并向快递100申请打印。
改良说明
- 本文针对的版本为API_V6.0 ,demo也是再次基础上修改的,语言是java;
- 原API直接将参数(如appKey、appSecret、redirectUri等等)写死,我是将其保存为快递账号的表存到数据库中的(这样可以同时使用多个账号,不同用户可归类至不同账号);
表名 | 表含义 | 包含信息 |
---|---|---|
Kuaidi100 | 快递100账号表 | appKey、appSecret、redirectUri等 |
SendExpressList | 寄件单表 | 寄件人、收件人等信息 |
SendExpressListDetail | 寄件单详情表 | 多个物品的重量、价格、名称等 |
ExpressCompany | 快递公司表 | 公司名称、公司编码等 |
Kuaidi100Api
package ***.util;
import ***.entity.Kuaidi100;
import ***.entity.SendExpressList;
import ***.service.ExpressCompanyService;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import javax.annotation.Resource;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* 快递100 Api
* @author sgambler
*
*/
public class Kuaidi100Api {
@Resource
private static ExpressCompanyService expressCompanyService;
static {
try {
SSLContext sslcontext = SSLContext.getInstance("SSL", "SunJSSE");
sslcontext.init(null, new TrustManager[]{new MyX509TrustManager()}, new java.security.SecureRandom());
HostnameVerifier ignoreHostnameVerifier = new HostnameVerifier() {
public boolean verify(String s, SSLSession sslsession) {
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(ignoreHostnameVerifier);
HttpsURLConnection.setDefaultSSLSocketFactory(sslcontext.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 转换寄件单信息为传入快递100的data
* @param se 寄件单
* @return
*/
public static String parseSendExpressListToOrderData(SendExpressList se){
String orderData = "{";
orderData += "\"recMobile\":\"" + se.getReceiverPhone() + "\",";
orderData += "\"recName\":\"" + se.getReceiverName() + "\",";
orderData += "\"recAddr\":\"" + se.getReceiverFullAddress() + "\",";
orderData += "\"sendTel\":\"" + se.getSendPhone() + "\",";
orderData += "\"sendName\":\"" + se.getSendName() + "\",";
orderData += "\"sendAddr\":\"" + se.getSendAddress() + "\",";
orderData += "\"orderNum\":\"" + se.getSendExpressListSn() + "\",";
orderData += "\"weight\":\"" + se.getTotleWeight() + "\",";
orderData += "\"cargo\":\"" + se.getSendExpressListDetails().iterator().next().getItemName() + "\",";
orderData += "\"kuaidiCom\":\"" + se.getExpressCompany().getKuaidiCom() + "\""; //公司编码
orderData += "}";
return orderData;
}
/**
* 校验回调信息的sign
*
* @param callbackData 回调的参数数据
* @return 校验是否通过
*/
public static boolean callbackVerify(Map<String, String> callbackData, Kuaidi100 kuaidi100) {
String appSecret = kuaidi100.getAppSecret();
callbackData = transformToTreeMap(callbackData);
StringBuilder sbd = new StringBuilder(appSecret);
for (Map.Entry<String, String> entry : callbackData.entrySet()) {
if (!"sign".equals(entry.getKey())) {
sbd.append(entry.getKey()).append(entry.getKey());
}
}
sbd.append(appSecret);
return new MD5().encode(sbd.toString()).equals(callbackData.get("sign"));
}
/**
* 生成授权地址
*
* @param state state
* @return 授权地址
* @throws UnsupportedEncodingException
*/
public static String authorize(String state, Kuaidi100 kuaidi100) throws Exception {
String appKey = kuaidi100.getAppKey();
String redirectUri = kuaidi100.getRedirectUri();
String timestamp = String.valueOf(System.currentTimeMillis());
state = null == state ? "" : state;
Map<String, String> data = new TreeMap<>();
data.put("client_id", appKey);
data.put("response_type", "code");
data.put("redirect_uri", redirectUri);
data.put("state", state);
data.put("timestamp", timestamp);
String sign = generateSign(data, kuaidi100);
return "https://b.kuaidi100.com/open/oauth/authorize?response_type=code&client_id=" + appKey + "&redirect_uri=" + URLEncoder.encode(redirectUri, "UTF-8") + "&state=" + state + "×tamp=" + timestamp + "&sign=" + sign;
}
/**
* 用授权得到的code换取accessToken
*
* @param code 临时凭证
* @return
*/
public static String accessToken(String code, Kuaidi100 kuaidi100) throws Exception {
String appKey = kuaidi100.getAppKey();
String appSecret = kuaidi100.getAppSecret();
String redirectUri = kuaidi100.getRedirectUri();
if (null == code || code.length() < 1) {
throw new RuntimeException("无效参数");
}
String timestamp = String.valueOf(System.currentTimeMillis());
Map<String, String> data = new TreeMap<>();
data.put("client_id", appKey);
data.put("client_secret", appSecret);
data.put("grant_type", "authorization_code");
data.put("code", code);
data.put("redirect_uri", redirectUri);
data.put("timestamp", timestamp);
data.put("sign", generateSign(data, kuaidi100));
System.out.println(data);
return httpsRequest("https://b.kuaidi100.com/open/oauth/accessToken", data);
}
/**
* 刷新accessToken
*
* @param refreshToken 刷新token
* @return
*/
public static String refreshToken(String refreshToken, Kuaidi100 kuaidi100) throws Exception {
String appKey = kuaidi100.getAppKey();
String appSecret = kuaidi100.getAppSecret();
if (null == refreshToken || refreshToken.length() < 1) {
throw new RuntimeException("refreshToken为无效参数");
}
String timestamp = String.valueOf(System.currentTimeMillis());
Map<String, String> data = new TreeMap<>();
data.put("client_id", appKey);
data.put("client_secret", appSecret);
data.put("grant_type", "refresh_token");
data.put("refresh_token", refreshToken);
data.put("timestamp", timestamp);
data.put("sign", generateSign(data, kuaidi100));
return httpsRequest("https://b.kuaidi100.com/open/oauth/refreshToken", data);
}
/**
* 导入订单
*
* @param accessToken 身份凭证
* @param orderData 订单信息
* @return
*/
public static String send(String accessToken, String orderData, Kuaidi100 kuaidi100) throws Exception {
String appKey = kuaidi100.getAppKey();
if (null == accessToken || accessToken.length() < 1
|| null == orderData || orderData.length() < 1) {
throw new RuntimeException("无效参数");
}
String timestamp = String.valueOf(System.currentTimeMillis());
Map<String, String> data = new TreeMap<>();
data.put("appid", appKey);
data.put("access_token", accessToken);
data.put("data", orderData);
data.put("timestamp", timestamp);
data.put("sign", generateSign(data, kuaidi100));
return httpsRequest("https://b.kuaidi100.com/v6/open/api/send", data);
}
/**
* 修改订单
*
* @param accessToken 身份凭证
* @param orderData 订单信息
* @return
*/
public static String update(String accessToken, String orderData, Kuaidi100 kuaidi100) throws Exception {
String appKey = kuaidi100.getAppKey();
if (null == accessToken || accessToken.length() < 1
|| null == orderData || orderData.length() < 1) {
throw new RuntimeException("无效参数");
}
String timestamp = String.valueOf(System.currentTimeMillis());
Map<String, String> data = new TreeMap<>();
data.put("appid", appKey);
data.put("access_token", accessToken);
data.put("data", orderData);
data.put("timestamp", timestamp);
data.put("sign", generateSign(data, kuaidi100));
return httpsRequest("https://b.kuaidi100.com/v6/open/api/update", data);
}
/**
* 快速打印
*
* @param accessToken 身份凭证
* @param printList 订单号列表
* @return 打印订单的地址
*/
public static String quickPrint(String accessToken, String printList, Kuaidi100 kuaidi100) {
String appKey = kuaidi100.getAppKey();
if (null == accessToken || accessToken.length() < 1
|| null == printList || printList.length() < 1) {
throw new RuntimeException("无效参数");
}
String timestamp = String.valueOf(System.currentTimeMillis());
Map<String, String> data = new TreeMap<>();
data.put("appid", appKey);
data.put("access_token", accessToken);
data.put("printlist", printList);
data.put("timestamp", timestamp);
String sign = generateSign(data, kuaidi100);
return "https://b.kuaidi100.com/v6/open/api/print?appid=" + appKey + "&access_token=" + accessToken + "&printlist=" + printList + "×tamp=" + timestamp + "&sign=" + sign;
}
/**
* 自动打印
*
* @param accessToken 身份凭证
* @param printList 订单号列表
* @return
*/
public static String autoPrint(String accessToken, String printList, Kuaidi100 kuaidi100) throws Exception {
String appKey = kuaidi100.getAppKey();
String timestamp = String.valueOf(System.currentTimeMillis());
Map<String, String> data = new TreeMap<>();
data.put("appid", appKey);
data.put("access_token", accessToken);
data.put("printlist", printList);
data.put("timestamp", timestamp);
data.put("sign", generateSign(data, kuaidi100));
return httpsRequest("https://b.kuaidi100.com/v6/open/api/autoPrint", data);
}
/**
* 生成签名
*
* @param data 请求数据
* @return 签名
*/
private static String generateSign(Map<String, String> data, Kuaidi100 kuaidi100) {
String appSecret = kuaidi100.getAppSecret();
data = transformToTreeMap(data);
StringBuilder sbd = new StringBuilder(appSecret);
for (Map.Entry<String, String> entry : data.entrySet()) {
sbd.append(entry.getKey()).append(entry.getValue());
}
sbd.append(appSecret);
return new MD5().encode(sbd.toString());
}
/**
* 把map转换为treeMap
*
* @param map 任意类型的map
* @return treeMap
*/
private static Map<String, String> transformToTreeMap(Map<String, String> map) {
return map instanceof TreeMap ? map : new TreeMap<>(map);
}
/**
* 发送http请求
*
* @param url 请求地址
* @param data 请求参数
* @return 接口返回的结果
*/
private static String httpsRequest(String url, Map<String, String> data) throws Exception {
URL apiUrl = new URL(url);
StringBuilder paramSbd = new StringBuilder();
if (null != data) {
for (Map.Entry<String, String> entry : data.entrySet()) {
paramSbd.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
String param = paramSbd.length() > 0 ? paramSbd.substring(0, paramSbd.length() - 1) : "";
try {
HttpURLConnection httpConn = (HttpURLConnection) apiUrl.openConnection();
//设置参数
httpConn.setDoOutput(true);
httpConn.setDoInput(true);
httpConn.setUseCaches(false);
httpConn.setRequestMethod("POST");
//设置请求属性
httpConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
httpConn.setRequestProperty("Charset", "UTF-8");
httpConn.setRequestProperty("Accept", "application/json;charset=UTF-8");
//建立输入流,向指向的URL传入参数
OutputStream out = httpConn.getOutputStream();
out.write(param.getBytes("UTF-8"));
out.flush();
out.close();
//获得响应状态
int resultCode = httpConn.getResponseCode();
if (HttpURLConnection.HTTP_OK == resultCode) {
StringBuilder sbd = new StringBuilder();
String readLine;
BufferedReader responseReader = new BufferedReader(new InputStreamReader(httpConn.getInputStream(), "UTF-8"));
while ((readLine = responseReader.readLine()) != null) {
sbd.append(readLine).append("\n");
}
responseReader.close();
System.out.println(sbd.toString());
return sbd.toString();
} else {
throw new RuntimeException("http请求失败:" + httpConn.getResponseCode());
}
} catch (Exception e) {
throw new RuntimeException("http请求失败", e);
}
}
static class MD5 {
// 获得MD5摘要算法的 MessageDigest 对象
private MessageDigest _mdInst = null;
private char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
private MessageDigest getMdInst() {
if (_mdInst == null) {
try {
_mdInst = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
return _mdInst;
}
String encode(String s) {
try {
byte[] btInput = s.getBytes();
// 使用指定的字节更新摘要
getMdInst().update(btInput);
// 获得密文
byte[] md = getMdInst().digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (byte byte0 : md) {
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
static class MyX509TrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate certificates[], String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] ax509certificate, String s) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
}
}
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 日志
* @return
*/
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("Kuaidi100 java sdk");
return logger;
}
public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
documentBuilderFactory.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
return documentBuilderFactory.newDocumentBuilder();
}
}
总结
此篇只有直接对接快递100的接口api,关于具体业务调用以及回调接口见下章。
如有侵扰,请私信。