今天使用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-下订单接口-速运类APISFCommonReq 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));//沙箱URLHttpPost 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@NoArgsConstructorprivate 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";}@Dataprivate static class CargoDetails {@NotNullprivate BigDecimal amount;@NotNullprivate BigDecimal count;@NotNullprivate String currency;private String goodPrepardNo;private String hsCode;@NotNullprivate String name;private String productRecordNo;@NotNullprivate String sourceArea;private String taxNo;@NotNullprivate String unit;@NotNullprivate BigDecimal weight;}public class ExpressAddressDTO implements Serializable {Integer districtId;String districtName;String phone;String address;String name;
}@Dataprivate 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;@Dataprivate static class ApiResultData {public Boolean success;public String errorCode;public String errorMsg;public MsgData msgData;@Dataprivate 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;@Dataprivate static class WaybillNoInfo {public Integer waybillType;public String waybillNo;}@Dataprivate static class RouteLabelInfo {public String code;public RouteLabelData routeLabelData;public String message;@Dataprivate 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-CNString 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@Builderprivate static class PrintTemplate {//关联云打印接口后,点击查看,可在接口详情页获取模板编码private String templateCode;//版本号,传固定值:2.0private String version;//pdf格式private String fileType;private List<Document> documents;//true: 同步,false: 异步,默认异步private Boolean sync;private ExtJson extJson;}@Data@AllArgsConstructor@NoArgsConstructorprivate 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;}