开始要到丰桥官网注册账号,丰桥会提供客户编码和校验码到你的邮箱;官方有接入教程,没啥可纠结的,一路申请到底。
因为我这边散户多,用户量小,没有对接面单打印SDK,和我一样需求的,可申请免面单,完成审核即可;
可在“我的API”中添加需要的接口,最后添加顺丰月结账号,以配置生产环境。
在丰桥官网–帮助中心–软件下载中,有一个官方提供的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";
}
顺丰使用的数据交换格式是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("");
strBuilder.append("" );
strBuilder.append("" + ShunFenConfig.clientCode + "");
strBuilder.append("");
strBuilder.append(").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(" ");
strBuilder.append("");
strBuilder.append("");
return strBuilder.toString();
}
/**
* 订单结果查询
* 订单查询接口是在下单后没有返回运单号时主动查询运单号使用
* @param orderId
* @return
*/
public static String query(String orderId){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("");
strBuilder.append("" );
strBuilder.append("" + ShunFenConfig.clientCode + "");
strBuilder.append("");
strBuilder.append(" ");
strBuilder.append("");
strBuilder.append("");
return strBuilder.toString();
}
/**
* 客户在确定将货物交付给顺丰托运后,将面单上的一些重要信息,如快件重量通过此接口发送给顺丰
* @param orderId
* @return
*/
public static String confirm(String orderId){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("");
strBuilder.append("" );
strBuilder.append("" + ShunFenConfig.clientCode + "");
strBuilder.append("");
strBuilder.append(" ");
strBuilder.append("");
strBuilder.append("");
return strBuilder.toString();
}
/**
* 客户在发货前取消订单
* @param orderId
* @return
*/
public static String cancle(String orderId){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("");
strBuilder.append("" );
strBuilder.append("" + ShunFenConfig.clientCode + "");
strBuilder.append("");
strBuilder.append("" );
strBuilder.append("");
strBuilder.append("");
strBuilder.append("");
return strBuilder.toString();
}
/**
* 客户可通过此接口查询顺丰运单路由,系统将返回当前时间点已发生的路由信息
* @param expressNumber
* @return
*/
public static String trace(String expressNumber){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("");
strBuilder.append("" );
strBuilder.append("" + ShunFenConfig.clientCode + "");
strBuilder.append("");
strBuilder.append(" ");
strBuilder.append("");
strBuilder.append("");
strBuilder.append("");
return strBuilder.toString();
}
}
测试下单接口:
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
具体描述:已签收,感谢使用顺丰,期待再次为您服务(测试数据)
下面几个类是关于顺丰响应码的实体类
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;
}
}
记录下,方面下次使用。拿走拿走别客气。