java對接第三方快遞---順豐SDK


背景介紹:APP內商城,需要對接第三方快遞,公司本身和順豐有月結賬戶合作,故選用順豐。

開發之前

開始要到豐橋官網註冊賬號,豐橋會提供客戶編碼和校驗碼到你的郵箱;官方有接入教程,沒啥可糾結的,一路申請到底。

因爲我這邊散戶多,用戶量小,沒有對接面單打印SDK,和我一樣需求的,可申請免面單,完成審覈即可;

可在“我的API”中添加需要的接口,最後添加順豐月結賬號,以配置生產環境。

在這裏插入圖片描述

接入SDK

在豐橋官網–幫助中心–軟件下載中,有一個官方提供的sdk,下載下來,如圖所示:
在這裏插入圖片描述
SF-CSIM-EXPRESS-SDK-V1.6.jar放在項目lib下,在pom文件中添加依賴

    <dependency>
            <groupId>ShunFeng</groupId>
            <artifactId>ShunFeng</artifactId>
            <version>1.0</version>
            <scope>system</scope>
            <systemPath>${basedir}/src/lib/SF-CSIM-EXPRESS-SDK-V1.6.jar</systemPath>
    </dependency>

添加一橋靜態變量配置類,替換成你的參數.

package com.chain.express.config;

/**
 * @Auther: songweichao
 * @Date: 2020/1/7 12:21
 * @Description:
 */
public class ShunFenConfig {

    // 月結卡號
    public static String cardId="********";

    //豐橋平臺顧客編碼
    public static String  clientCode="********";

    //豐橋平臺校驗碼
    public static String checkword="********";

    //豐橋平臺下訂單url
    public static String accessUrl="http://bsp-oisp.sf-express.com/bsp-oisp/sfexpressService";


}

2.1、工具類

順豐使用的數據交換格式是xml,需要一個xml轉實體的工具類。

package com.chain.express.utils;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import java.io.Reader;
import java.io.StringReader;

/**
 * XML轉實體互轉工具類
 * @Auther: songweichao
 * @Date: 2020/1/8 10:20
 * @Description:
 */
public class XmlBuilder {

    /**
     * 將XML轉爲指定的BEAN
     * @param clazz
     * @param xmlStr
     * @return
     * @throws Exception
     */
    public static Object xmlStrToOject(Class<?> clazz, String xmlStr) throws Exception {
    
        Object xmlObject = null;
        Reader reader = null;
        JAXBContext context = JAXBContext.newInstance(clazz);
        // XML 轉爲對象的接口
        Unmarshaller unmarshaller = context.createUnmarshaller();
        reader = new StringReader(xmlStr);
        xmlObject = unmarshaller.unmarshal(reader);

        if (null != reader) {
            reader.close();
        }
        return xmlObject;
    }
}

添加一個順豐組裝請求xml的工具類

package com.chain.express.utils;

import com.chain.base.utils.BaseLogger;
import com.chain.express.config.ShunFenConfig;
import com.chain.order.entity.Order;

/**
 * 順豐工具類
 * @Auther: songweichao
 * @Date: 2020/1/7 12:02
 * @Description:
 */
public class ExpressUtils extends BaseLogger{
    /**
     * 下訂單
     * order 是我自己的訂單類,換成你業務中有的POJO
     * @param order
     * @return
     */
    public static String  produce(Order order){

        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append("<?xml version='1.0' encoding='UTF-8'?>");
        strBuilder.append("<Request service='OrderService' lang='zh-CN'>");
        strBuilder.append("<Head>" + ShunFenConfig.clientCode + "</Head>");
        strBuilder.append("<Body>");
        strBuilder.append("<Order").append(" ");
        strBuilder.append("orderid='" + order.getOrderNumber() + "'").append(" ");
        //  1 代表順丰標快 -- 詳情查看平臺 快件產品類別表,注意:標快支持子母單,速配不支持
        strBuilder.append("express_type='1'").append(" ");
        // 寄件方信息
        strBuilder.append("j_province='北京'").append(" ");
        strBuilder.append("j_city='北京市'").append(" ");
        strBuilder.append("j_county='中國'").append(" ");
        strBuilder.append("j_company='超琦科技有限公司'").append(" ");
        strBuilder.append("j_contact='客服007'").append(" ");
        strBuilder.append("j_tel='18888888888'").append(" ");
        strBuilder.append("j_address='北京市朝陽區'").append(" ");
        // 收件方信息
        strBuilder.append("d_province='" + order.getReceiverProvince() + "'").append(" ");
        strBuilder.append("d_city='" + order.getReceiverCity() + "'").append(" ");
        strBuilder.append("d_county='" + order.getReceiverCounty() + "'").append(" ");
        strBuilder.append("d_county=''").append(" ");
        strBuilder.append("d_company='ceshi'").append(" ");
        strBuilder.append("d_tel='" + order.getReceiverPhone() + "'").append(" ");
        strBuilder.append("d_contact='" +  order.getReceiverPhone() + "'").append(" ");
        strBuilder.append("d_address='" + order.getReceiverAddress() + "'").append(" ");
        strBuilder.append("remark='" + order.getRemark() + "'").append(" ");
        strBuilder.append("parcel_quantity='1'").append(" ");
        strBuilder.append("pay_method='1'").append(" ");
        strBuilder.append("custid ='"+ ShunFenConfig.cardID+"'").append(" ");
        strBuilder.append("customs_batchs=''").append(" ");
       // is_docall這個參數,當你沒有申請面單打印,那就取值爲1,表示由快遞小哥上門取件
        strBuilder.append("is_docall='1'").append(">");
        strBuilder.append("</Order>");
        strBuilder.append("</Body>");
        strBuilder.append("</Request>");
        return strBuilder.toString();
    }

    /**
     * 訂單結果查詢
     * 訂單查詢接口是在下單後沒有返回運單號時主動查詢運單號使用
     * @param orderId
     * @return
     */
    public static String query(String orderId){
        StringBuilder strBuilder = new StringBuilder();

        strBuilder.append("<?xml version='1.0' encoding='UTF-8'?>");
        strBuilder.append("<Request service='OrderSearchService' lang='zh-CN'>");
        strBuilder.append("<Head>" + ShunFenConfig.clientCode + "</Head>");
        strBuilder.append("<Body>");
        strBuilder.append("<OrderSearch orderid='"+orderId+"'/>");
        strBuilder.append("</Body>");
        strBuilder.append("</Request>");

        return strBuilder.toString();
    }

    /**
     * 客戶在確定將貨物交付給順豐託運後,將面單上的一些重要信息,如快件重量通過此接口發送給順豐
     * @param orderId
     * @return
     */
    public static String confirm(String orderId){

        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append("<?xml version='1.0' encoding='UTF-8'?>");
        strBuilder.append("<Request service='OrderConfirmService' lang='zh-CN'>");
        strBuilder.append("<Head>" + ShunFenConfig.clientCode + "</Head>");
        strBuilder.append("<Body>");
        strBuilder.append("<OrderConfirm orderid='"+orderId+"'dealtype='1' />");
        strBuilder.append("</Body>");
        strBuilder.append("</Request>");
        return strBuilder.toString();
    }


    /**
     * 客戶在發貨前取消訂單
     * @param orderId
     * @return
     */
    public static String cancle(String orderId){

        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append("<?xml version='1.0' encoding='UTF-8'?>");
        strBuilder.append("<Request service='OrderConfirmService' lang='zh-CN'>");
        strBuilder.append("<Head>" + ShunFenConfig.clientCode + "</Head>");
        strBuilder.append("<Body>");
        strBuilder.append("<OrderConfirm orderid='"+orderId+"' dealtype='2' >");
        strBuilder.append("</OrderConfirm>");
        strBuilder.append("</Body>");
        strBuilder.append("</Request>");
        return strBuilder.toString();
    }

    /**
     * 客戶可通過此接口查詢順豐運單路由,系統將返回當前時間點已發生的路由信息
     * @param expressNumber
     * @return
     */
    public static String trace(String expressNumber){

        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append("<?xml version='1.0' encoding='UTF-8'?>");
        strBuilder.append("<Request service='RouteService' lang='zh-CN'>");
        strBuilder.append("<Head>" + ShunFenConfig.clientCode + "</Head>");
        strBuilder.append("<Body>");
        strBuilder.append("<RouteRequest tracking_type='1' method_type='1' tracking_number='"+expressNumber+"' />");
        strBuilder.append("</RouteRequest>");
        strBuilder.append("</Body>");
        strBuilder.append("</Request>");
        return strBuilder.toString();
    }


}

2.2、測試中

測試下單接口:

public static void main(String[] args) {
        /****************************** 下單測試*****************************/

        Order order = new Order();
        order.setOrderNumber("TEST20200110");
        order.setReceiverAddress("北京市豐臺區花鄉橋");
        order.setReceiver("Test");
        order.setReceiverCounty("中國");
        order.setReceiverProvince("北京市");
        order.setReceiverCity("北京市");
        order.setReceiverPhone("18888888886");

        String produceBody = ExpressUtils.produce(order);
        System.out.println("下單請求報文:" + produceBody);
        String respXml = CallExpressServiceTools.callSfExpressServiceByCSIM(ShunFenConfig.accessUrl, produceBody, ShunFenConfig.clientCode, ShunFenConfig.checkword);
        System.out.println("下單響應報文:" + respXml);
        SfExpressResponse sfExpressResponse = null;
        try {
            sfExpressResponse = (SfExpressResponse) XmlBuilder.xmlStrToOject(SfExpressResponse.class, respXml);
            OrderResponse orderResponse = sfExpressResponse.getBody().getOrderResponse();
            System.out.println("下訂單返回狀態碼:"+orderResponse.getFilterResult() + "順豐單號:"+ orderResponse.getMailNo());

        } catch (Exception e) {
            System.out.println("下訂單返回異常狀態碼:"+sfExpressResponse.getERROR().getCode() + "響應正文:"+sfExpressResponse.getERROR().getText());

            e.printStackTrace();
        }

}

返回結果:

// 堆棧信息省略......
下單請求報文:<?xml version='1.0' encoding='UTF-8'?><Request service='OrderService' lang='zh-CN'><Head>SXWXJS</Head><Body><Order orderid='TEST202001101443' express_type='1' j_province='北京' j_city='北京市' j_county='中國' j_company='超琦科技有限公司' j_contact='客服007' j_tel='18888888888' j_address='北京市朝陽區' d_province='北京市' d_city='北京市' d_county='中國' d_county='' d_company='ceshi' d_tel='18888888886' d_contact='18888888886' d_address='北京市豐臺區花鄉橋' parcel_quantity='1' pay_method='1' custid ='**********' customs_batchs='' is_docall='1'></Order></Body></Request>
14:43:37.505 [main] DEBUG org.apache.http.client.protocol.RequestAddCookies - CookieSpec selected: default
14:43:37.539 [main] DEBUG org.apache.http.client.protocol.RequestAuthCache - Auth cache not set in the context
//中間日誌省略了......
下單響應報文:<?xml version='1.0' encoding='UTF-8'?><Response service="OrderService"><Head>OK</Head><Body><OrderResponse filter_result="2" destcode="010" mailno="SF7444402649723" origincode="010" orderid="TEST202001101443"><rls_info rls_errormsg="SF7444402649723:" invoke_result="OK" rls_code="1000"><rls_detail waybillNo="SF7444402649723" sourceCityCode="010" destCityCode="010" destDeptCode="010FT" destDeptCodeMapping="010WB" destTeamCode="044" destTransferCode="010WB" destRouteLabel="010WB-010FT" proName="順丰標快" cargoTypeCode="C201" limitTypeCode="T4" expressTypeCode="B1" codingMapping="B1B" xbFlag="0" printFlag="000000000" twoDimensionCode="MMM={'k1':'010WB','k2':'010FT','k3':'044','k4':'T4','k5':'SF7444402649723','k6':'','k7':'166127c4'}" proCode="T4" printIcon="00000000" checkCode="166127c4" destGisDeptCode="010FT"/></rls_info></OrderResponse></Body></Response>
下訂單返回狀態碼:2順豐單號:SF7444402649723

當然了,你要以同樣的單號再次下單,會返回重複下單的錯誤

//堆棧信息省略......
下單請求報文:<?xml version='1.0' encoding='UTF-8'?><Request service='OrderService' lang='zh-CN'><Head>SXWXJS</Head><Body><Order orderid='TEST202001101443' express_type='1' j_province='北京' j_city='北京市' j_county='中國' j_company='超琦科技有限公司' j_contact='客服007' j_tel='18888888888' j_address='北京市朝陽區' d_province='北京市' d_city='北京市' d_county='中國' d_county='' d_company='ceshi' d_tel='18888888886' d_contact='18888888886' d_address='北京市豐臺區花鄉橋' parcel_quantity='1' pay_method='1' custid ='**********' customs_batchs='' is_docall='1'></Order></Body></Request>
//中間信息省略......
下單響應報文:<?xml version='1.0' encoding='UTF-8'?><Response service="OrderService"><Head>ERR</Head><ERROR code="8016">重複下單</ERROR></Response>
java.lang.NullPointerException
	at com.chain.pay.service.impl.AliPayServiceImpl.main(AliPayServiceImpl.java:397)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
下訂單返回異常狀態碼:8016響應正文:重複下單

路由接口測試:

public static void main(String[] args) {

/******************************* 路由查詢測試******************************/
        System.out.println("-----------------------------------------------");
        String traceBody = ExpressUtils.trace("SF7444402649981");
        System.out.println("請求報文:" + traceBody);
        String resXml3 = CallExpressServiceTools.callSfExpressServiceByCSIM(ShunFenConfig.accessUrl, traceBody, ShunFenConfig.clientCode, ShunFenConfig.checkword);
        System.out.println("響應報文:" + resXml3);

        try {
            SfExpressResponse  sfExpressResponse = (SfExpressResponse) XmlBuilder.xmlStrToOject(SfExpressResponse.class, resXml3);
            List<Route> list = sfExpressResponse.getBody().getRouteResponse().getRoute();
            for (int i = 0; i < list.size(); i++) {
                Route route = list.get(i);
                System.out.println("-----------路由消息第" + i + "條:-------------");
                System.out.println("發生時間:"+route.getAcceptTime());
                System.out.println("節點操作碼:"+route.getOpcode());
                System.out.println("具體描述:"+route.getRemark());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

返回結果:

//堆棧信息省略...
-----------------------------------------------
請求報文:<?xml version='1.0' encoding='UTF-8'?><Request service='RouteService' lang='zh-CN'><Head>SXWXJS</Head><Body><RouteRequest tracking_type='1' method_type='1' tracking_number='SF7444402649981' /></RouteRequest></Body></Request>
//中間信息省略...
響應報文:<?xml version='1.0' encoding='UTF-8'?><Response service="RouteService"><Head>OK</Head><Body><RouteResponse mailno="SF7444402649981" orderid="TEST202001101505"><Route remark="順豐速運 已收取快件(測試數據)" accept_time="2018-05-01 08:01:44" accept_address="廣東省深圳市軟件產業基地" opcode="50"/><Route remark="已簽收,感謝使用順豐,期待再次爲您服務(測試數據)" accept_time="2018-05-02 12:01:44" accept_address="廣東省深圳市軟件產業基地" opcode="80"/></RouteResponse></Body></Response>
-----------路由消息第0條:-------------
發生時間:2018-05-01 08:01:44
節點操作碼:50
具體描述:順豐速運 已收取快件(測試數據)
-----------路由消息第1條:-------------
發生時間:2018-05-02 12:01:44
節點操作碼:80
具體描述:已簽收,感謝使用順豐,期待再次爲您服務(測試數據)

2.3 、JavaBean

下面幾個類是關於順豐響應碼的實體類

package com.chain.express.entity;

import javax.xml.bind.annotation.*;
import java.io.Serializable;

/**
 * @Auther: songweichao
 * @Date: 2020/1/8 10:04
 * @Description:
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "Response")
public class SfExpressResponse implements Serializable {

    private static final long serialVersionUID = 1L;

    //響應狀態
    @XmlElement(name = "Head")
    private String Head;

    //響應失敗原因
    @XmlElement(name = "ERROR")
    private ERROR ERROR;

    //響應結果
    @XmlElement(name = "Body")
    private Body Body;

    public String getHead() {
        return Head;
    }

    public void setHead(String head) {
        Head = head;
    }

    public com.chain.express.entity.ERROR getERROR() {
        return ERROR;
    }

    public void setERROR(com.chain.express.entity.ERROR ERROR) {
        this.ERROR = ERROR;
    }

    public com.chain.express.entity.Body getBody() {
        return Body;
    }

    public void setBody(com.chain.express.entity.Body body) {
        Body = body;
    }
}

package com.chain.express.entity;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;

/**
 * @Auther: songweichao
 * @Date: 2020/1/8 10:05
 * @Description:
 */
@XmlAccessorType(XmlAccessType.NONE)
public class ERROR {

    @XmlAttribute(name = "code")
    private String code;

    @XmlValue
    private String text;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

package com.chain.express.entity;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;

/**
 * @Auther: songweichao
 * @Date: 2020/1/8 10:06
 * @Description:
 */
@XmlAccessorType(XmlAccessType.NONE)
public class Body {

    @XmlElement(name = "OrderResponse")
    private OrderResponse OrderResponse;

    @XmlElement(name = "RouteResponse")
    private RouteResponse RouteResponse;


    public com.chain.express.entity.OrderResponse getOrderResponse() {
        return OrderResponse;
    }

    public void setOrderResponse(com.chain.express.entity.OrderResponse orderResponse) {
        OrderResponse = orderResponse;
    }

    public com.chain.express.entity.RouteResponse getRouteResponse() {
        return RouteResponse;
    }

    public void setRouteResponse(com.chain.express.entity.RouteResponse routeResponse) {
        RouteResponse = routeResponse;
    }
}

package com.chain.express.entity;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;

/**
 * @Auther: songweichao
 * @Date: 2020/1/8 10:06
 * @Description:
 */
@XmlRootElement(name="OrderResponse")
@XmlAccessorType(XmlAccessType.NONE)
public class OrderResponse {

    //訂單號
    @XmlAttribute(name = "orderid")
    private String orderId;

    //運單號
    @XmlAttribute(name = "mailno")
    private String mailNo;

    //原寄地區域代碼(可用於順豐電子運單標籤打印)
    @XmlAttribute(name = "origincode")
    private String originCode;

    //目的地區域代碼(可用於順豐電子運單標籤打印)
    @XmlAttribute(name = "destcode")
    private String destCode;

    //篩單結果:1:人工確認 2:可收派 3:不可以收派
    @XmlAttribute(name = "filter_result")
    private String filterResult;

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public String getMailNo() {
        return mailNo;
    }

    public void setMailNo(String mailNo) {
        this.mailNo = mailNo;
    }

    public String getOriginCode() {
        return originCode;
    }

    public void setOriginCode(String originCode) {
        this.originCode = originCode;
    }

    public String getDestCode() {
        return destCode;
    }

    public void setDestCode(String destCode) {
        this.destCode = destCode;
    }

    public String getFilterResult() {
        return filterResult;
    }

    public void setFilterResult(String filterResult) {
        this.filterResult = filterResult;
    }
}

package com.chain.express.entity;

import javax.xml.bind.annotation.*;
import java.util.List;

/**
 * @Auther: songweichao
 * @Date: 2020/1/8 10:07
 * @Description:
 */
@XmlRootElement(name="RouteResponse")
@XmlAccessorType(XmlAccessType.NONE)
public class RouteResponse {

    //運單號
    @XmlAttribute(name = "mailno")
    private String mailNo;

    //路由
    @XmlElement(name = "Route")
    private List<Route> Route ;

    public String getMailNo() {
        return mailNo;
    }

    public void setMailNo(String mailNo) {
        this.mailNo = mailNo;
    }

    public List<com.chain.express.entity.Route> getRoute() {
        return Route;
    }

    public void setRoute(List<com.chain.express.entity.Route> route) {
        Route = route;
    }
}

package com.chain.express.entity;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;

/**
 * @Auther: songweichao
 * @Date: 2020/1/8 10:08
 * @Description:
 */
@XmlRootElement(name="Route")
@XmlAccessorType(XmlAccessType.NONE)
public class Route {

    //路由節點發生的時間
    @XmlAttribute(name = "accept_time")
    private String acceptTime;


    //路由節點具體描述
    @XmlAttribute(name = "remark")
    private String remark;


    //路由節點操作碼
    @XmlAttribute(name = "opcode")
    private String opcode;

    public String getAcceptTime() {
        return acceptTime;
    }

    public void setAcceptTime(String acceptTime) {
        this.acceptTime = acceptTime;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public String getOpcode() {
        return opcode;
    }

    public void setOpcode(String opcode) {
        this.opcode = opcode;
    }
}

記錄下,方面下次使用。拿走拿走別客氣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章