今天使用Java去访问顺丰的开放平台时,JSON转换一直不成功,最终发现是
可以看到这里是
"apiResultData": "{\"success\": .........
它是以 " 开头的!!!如果是对象的话,那么json是这样的:
"address": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA",
"zip": "12345"
}
对象是以 { 开头 !!!
然后我一天的bug都是因为,我的接收对象使用了
private static class RouteQueryResponse {
public String apiResponseID;
public String apiErrorMsg;
public String apiResultCode;
public ApiResultData apiResultData;
}
这里的apiResultData应该是String类型
那么言归正传,这里是要讲Java连接顺丰开放平台,那么首先是需要认证,认证的话 顺丰认证 有两种方式,OAuth2 和 数字签名,这里我实践发现,第二种一直是服务不可用,所以这里只能用前一种。
看一下官网的请求示例
他发的请求是
https://sfapi-sbox.sf-express.com/oauth2/accessToken?partnerID=XXXXXXXX&grantType=password&secret=0705GuswG6BwiTTEbYMkIkZHxxxxxxxxx
所以我们只要拼接一下即可。
然后他响应成功是返回accessToken,我们直接存到缓存里即可,后面请求其他接口必须使用这个accessToken
public String SFToken(Object request) throws IOException {
// TODO SF-获取签名-数字签名认证说明
//目前是测试方式获得
String url = "https://sfapi-sbox.sf-express.com/oauth2/accessToken?partnerID=" + partnerId + "&grantType=password&secret=" + verifyTestCode;
HttpPost post = new HttpPost(url);
post.setHeader("Content-type", "application/x-www-form-urlencoded;charset=UTF-8");
String response = httpClient.execute(post);
log.info("#sf-Token response, {}", JsonUtils.toStr(response));
SFTokenResponseBody str = JsonUtils.fromStr(response, SFTokenResponseBody.class);
//把accessToken放到cache中,2小时
cacheManager.put(CommonCacheManager.CacheType.SF_ACCESS_TOKEN_V2, "accessTokenv2", str.getAccessToken(), 120 * 60);
return str.getAccessToken();
}
@Data
@AllArgsConstructor
public class SFTokenResponseBody {
String accessToken;
String refreshToken;
public SFTokenResponseBody(){}
}
partnerId 和verifyTestCode都是你注册顺丰时发给你的。
httpClient和JsonUtils均可使用其他的平替
放到cache中的那一句代码可以自己进行修改
这只是认证,然后拿到了accessToken,可以去请求其他接口。
这里先看一下通用的方法:
public void setCommonParams(HttpPost httpPost, SFCommonReq req) throws UnsupportedEncodingException {
httpPost.setHeader("Content-type", "application/x-www-form-urlencoded;charset=UTF-8");
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("accessToken", req.getAccessToken()));
params.add(new BasicNameValuePair("partnerID", req.getPartnerID()));
params.add(new BasicNameValuePair("requestID", req.getRequestID()));
params.add(new BasicNameValuePair("serviceCode", req.getServiceCode()));
params.add(new BasicNameValuePair("timestamp", req.getTimestamp()));
params.add(new BasicNameValuePair("msgData", req.getMsgData()));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "UTF-8");
httpPost.setEntity(entity);
}
顺丰下订单
官网是
下订单 看到这里的
公共请求,我们需要创建一个这样的类,使用内部类即可。
然后响应的json也需要一个对象接收。
然后代码如下:
public SFOrderResponseBody orderV2(int orderId, int cityId, ExpressAddressDTO expressAddressDTO) throws IOException {
// SF-下订单接口-速运类API
SFCommonReq req = SFCommonReq.builder()
.partnerID(partnerId)
.requestID(UUID.randomUUID().toString())
.serviceCode(serviceCode.makeOrder)
.timestamp(String.valueOf(System.currentTimeMillis()))
.accessToken(getOrGenerateToken()) //这里只是从缓存获取accessToken
.build();
HashMap<String, String> msgData = new HashMap<>();
msgData.put("language", "zh-CN");
LinkedList<CargoDetails> cargoDetails = new LinkedList<>();
CargoDetails details = new CargoDetails();
details.setName(expressAddressDTO.getName());
cargoDetails.add(details);
ContactInfo contactInfo = new ContactInfo();
contactInfo.setAddress(expressAddressDTO.getAddress());
contactInfo.setTel(expressAddressDTO.getPhone());
contactInfo.setCity(districtService.findById(cityId).getName());
contactInfo.setContact(expressAddressDTO.getName());
//只能发到中国
contactInfo.setCountry("CN");
contactInfo.setCounty(expressAddressDTO.getDistrictName());
contactInfo.setMobile(expressAddressDTO.getPhone());
// "postCode":"580058",不用填
//找到省
contactInfo.setProvince(districtService.findByCode(districtService.findById(cityId).getCode()).getName());
msgData.put("cargoDetails", String.valueOf(cargoDetails));
msgData.put("contactInfoList", String.valueOf(Collections.singletonList(contactInfo)));
msgData.put("orderId", String.valueOf(orderId));
//顺丰特快
msgData.put("expressTypeId", String.valueOf(1));
//1:寄方付 2:收方付 3:第三方付
msgData.put("payMethod", String.valueOf(2));
msgData.put("isReturnRoutelabel", String.valueOf(1));
req.setMsgData(JsonUtils.toStr(msgData));
//沙箱URL
HttpPost httpPost = new HttpPost("https://sfapi-sbox.sf-express.com/std/service");
setCommonParams(httpPost, req);
log.info("发送的请求是:" + httpPost);
String s = httpClient.execute(httpPost);
try {
// 解析 JSON 格式的响应
createOrderResponse cor = JsonUtils.fromStr(s, createOrderResponse.class);
//特殊处理
createOrderResponse.ApiResultData apiResultData = JsonUtils.fromStr(cor.getApiResultData(), createOrderResponse.ApiResultData.class);
if (!"A1000".equals(cor.getApiResultCode())) {
//请求失败
log.error("请求失败,响应是:" + s);
SFOrderResponseBody sfOrderResponseBody = new SFOrderResponseBody();
sfOrderResponseBody.setRetry(true);
sfOrderResponseBody.setOrderId(s);
return sfOrderResponseBody;
}
//请求成功
Boolean success = apiResultData.getSuccess();
SFOrderResponseBody sfOrderResponseBody = new SFOrderResponseBody();
if (success) {
sfOrderResponseBody.setOrderId(apiResultData.getMsgData().getOrderId());
sfOrderResponseBody.setRetry(false);
} else {
sfOrderResponseBody.setRetry(true);
}
return sfOrderResponseBody;
} catch (Exception e) {
e.printStackTrace();
SFOrderResponseBody sfOrderResponseBody = new SFOrderResponseBody();
sfOrderResponseBody.setRetry(true);
sfOrderResponseBody.setOrderId("异常是" + e + ",响应是" + s);
return sfOrderResponseBody;
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
private static class SFCommonReq {
String partnerID;
String requestID;
String serviceCode;
String timestamp;
String accessToken;
String msgData;
}
private static class serviceCode {
//下单
private static final String makeOrder = "EXP_RECE_CREATE_ORDER";
//查询路径
private static final String route = "EXP_RECE_SEARCH_ROUTES";
//查询订单
private static final String searchOrder = "EXP_RECE_SEARCH_ORDER_RESP";
private static final String print = "COM_RECE_CLOUD_PRINT_WAYBILLS";
private static final String cloudPrint = "COM_PUSH_CLOUD_PRINT_WAYBILLS";
}
@Data
private static class CargoDetails {
@NotNull
private BigDecimal amount;
@NotNull
private BigDecimal count;
@NotNull
private String currency;
private String goodPrepardNo;
private String hsCode;
@NotNull
private String name;
private String productRecordNo;
@NotNull
private String sourceArea;
private String taxNo;
@NotNull
private String unit;
@NotNull
private BigDecimal weight;
}
public class ExpressAddressDTO implements Serializable {
Integer districtId;
String districtName;
String phone;
String address;
String name;
}
@Data
private static class ContactInfo {
private String address;
private String city;
private String contact;
private Integer contactType;
private String country;
private String county;
private String mobile;
private String postCode;
private String province;
private String tel;
}
@Data //这个就是接收的对象
private static class createOrderResponse {
public String apiErrorMsg;
public String apiResponseID;
public String apiResultCode;
public String apiResultData;
@Data
private static class ApiResultData {
public Boolean success;
public String errorCode;
public String errorMsg;
public MsgData msgData;
@Data
private static class MsgData {
public String orderId;
public String originCode;
public String destCode;
public Integer filterResult;
public String remark;
public String url;
public String paymentLink;
public Boolean isUpstairs;
public Boolean isSpecialWarehouseService;
public String mappingMark;
public String agentMailno;
public Object returnExtraInfoList;
public List<WaybillNoInfo> waybillNoInfoList;
public List<RouteLabelInfo> routeLabelInfo;
public Object contactInfoList;
@Data
private static class WaybillNoInfo {
public Integer waybillType;
public String waybillNo;
}
@Data
private static class RouteLabelInfo {
public String code;
public RouteLabelData routeLabelData;
public String message;
@Data
private static class RouteLabelData {
public String waybillNo;
public String sourceTransferCode;
public String sourceCityCode;
public String sourceDeptCode;
public String sourceTeamCode;
public String destCityCode;
public String destDeptCode;
public String destDeptCodeMapping;
public String destTeamCode;
public String destTeamCodeMapping;
public String destTransferCode;
public String destRouteLabel;
public String proName;
public String cargoTypeCode;
public String limitTypeCode;
public String expressTypeCode;
public String codingMapping;
public String codingMappingOut;
public String xbFlag;
public String printFlag;
public String twoDimensionCode;
public String proCode;
public String printIcon;
public String abFlag;
public String destPortCode;
public String destCountry;
public String destPostCode;
public String goodsValueTotal;
public String currencySymbol;
public String cusBatch;
public String goodsNumber;
public String errMsg;
public String checkCode;
public String proIcon;
public String fileIcon;
public String fbaIcon;
public String icsmIcon;
public String destGisDeptCode;
public Object newIcon;
}
}
}
}
}
路由查询接口
和上面类似,也是创建json的接收类,然后设置一下请求,这里不放完整代码了,最后放整个类的代码
//沙箱环境
String url = "https://sfapi-sbox.sf-express.com/std/service";
HttpPost post = new HttpPost(url);
SFCommonReq req = SFCommonReq.builder()
.partnerID(partnerId)
.requestID(UUID.randomUUID().toString())
.serviceCode(serviceCode.route)
.timestamp(String.valueOf(System.currentTimeMillis()))
.accessToken(getOrGenerateToken())
.build();
HashMap<String, String> msgData = new HashMap<>();
msgData.put("language", "zh-CN");
msgData.put("trackingType", String.valueOf(1));
msgData.put("trackingNumber", mailNo);
req.setMsgData(JsonUtils.toStr(msgData));
setCommonParams(post, req);
log.info("发送的请求是:" + post);
String s = httpClient.execute(post);
try {
// 解析 JSON 格式的响应
RouteQueryResponse rqr = JsonUtils.fromStr(s, RouteQueryResponse.class);
if (!"A1000".equals(rqr.getApiResultCode())) {
//请求失败
log.error("请求失败,响应是:" + s);
SFResponse<SFRouteInfos> response = new SFResponse<>();
response.setBody(null);
SFResponseHead head = new SFResponseHead();
head.setCode("500");
head.setMessage("请求失败了,返回的响应是" + s);
response.setHead(head);
return response;
}
//请求成功
//特殊处理
RouteQueryResponse.ApiResultData apiResultData = JsonUtils.fromStr(rqr.getApiResultData(), RouteQueryResponse.ApiResultData.class);
订单结果查询接口
//沙箱环境
String url = "https://sfapi-sbox.sf-express.com/std/service";
HttpPost post = new HttpPost(url);
SFCommonReq req = SFCommonReq.builder()
.partnerID(partnerId)
.requestID(UUID.randomUUID().toString())
.serviceCode(serviceCode.searchOrder)
.timestamp(String.valueOf(System.currentTimeMillis()))
.accessToken(getOrGenerateToken())
.build();
OrderSearchReqDto orderSearchReqDto = new OrderSearchReqDto();
orderSearchReqDto.setOrderId(String.valueOf(orderId));
orderSearchReqDto.setSearchType(String.valueOf(1));
orderSearchReqDto.setLanguage("zh-CN");
req.setMsgData(JsonUtils.toStr(orderSearchReqDto));
setCommonParams(post, req);
String s = httpClient.execute(post);
@Data
private static class OrderSearchReqDto {
String orderId;
//查询类型:1正向单 2退货单
String searchType;
//响应报文的语言, 缺省值为zh-CN
String language;
}
顺丰云打印
链接是 云打印
这里采用的是同步,也就是访问了接口,顺丰就返回文件url
String url = "https://sfapi-sbox.sf-express.com/std/service";
HttpPost post = new HttpPost(url);
SFCommonReq req = SFCommonReq.builder()
.partnerID(partnerId)
.requestID(UUID.randomUUID().toString())
.serviceCode(serviceCode.print)
.timestamp(String.valueOf(System.currentTimeMillis()))
.accessToken(getOrGenerateToken())
.build();
PrintTemplate template = PrintTemplate.builder()
.sync(true) //设置同步
.templateCode(templateCode)
.version("2.0").build();
Document document = new Document();
document.setMasterWaybillNo(String.valueOf(orderId));
LinkedList<Document> documents = new LinkedList<>();
template.setDocuments(documents);
req.setMsgData(JsonUtils.toStr(template));
setCommonParams(post, req);
String s = httpClient.execute(post);
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
private static class PrintTemplate {
//关联云打印接口后,点击查看,可在接口详情页获取模板编码
private String templateCode;
//版本号,传固定值:2.0
private String version;
//pdf格式
private String fileType;
private List<Document> documents;
//true: 同步,false: 异步,默认异步
private Boolean sync;
private ExtJson extJson;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
private static class Document {
private String masterWaybillNo;
private String branchWaybillNo;
private String backWaybillNo;
private String seq;
private String sum;
private Boolean isPrintLogo;
private String remark;
private String waybillNoCheckType;
private String waybillNoCheckValue;
private String customData;
}