今天使用Java去访问顺丰的开放平台时,JSON转换一直不成功,最终发现是

java 申请顺分单号 顺丰 java_ci


可以看到这里是

"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 和 数字签名,这里我实践发现,第二种一直是服务不可用,所以这里只能用前一种。

看一下官网的请求示例

java 申请顺分单号 顺丰 java_Data_02


他发的请求是

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(){}
}

partnerIdverifyTestCode都是你注册顺丰时发给你的。
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);
    }

顺丰下订单

官网是

下订单 看到这里的

java 申请顺分单号 顺丰 java_java 申请顺分单号_03


公共请求,我们需要创建一个这样的类,使用内部类即可。

然后响应的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;
    }