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;
    }
}

记录下,方面下次使用。拿走拿走别客气。

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