Java顺丰同城接口开发

顺丰同城接口

阅读官方文档

首先必须仔细阅读好官方文档

http://commit-openic.sf-express.com/open/api/docs/index#/homepage

顺丰同城开发平台

先注册成为开发者,成功后会获得开发者ID(devId)和开发者密钥(devKey)

开发者信息

这里比较重要的是sign签名的生成
签名生成方式如下:

  • 将POST发送的字符串内容进行JSON序列化
  • 使用&连接JSON的数据及dev_id、dev_key
  • 对上步得到的字符串用MD5加密然后Base64计算得到签名密钥
  • 请求的应答为JSON,其中字段可能随未来版本迭代而增加,请Java用户在映射为对象时,若使用Jackson库解析JSON,务必添加 @JsonIgnoreProperties 注解
  • 请求内容必须为UTF-8编码,否则结构解析会失败【下单会报push_time字段错误】

注意:由于JSON没有明确的跨语言规范,因此对JSON解析之后再进行重新编码,并不一定能还原原始JSON字符串。所以请严格按照原始报文进行签名和验签!

文档中还注明了回调接口访问和签名方式,请求为POST方式

工具类的介绍

这里我使用了spring-boot-sfcity项目中的工具类

https://github.com/neatlife/spring-boot-sfcity (如果帮助到你的话去给大佬点个star吧)

SignUtil类

首先是SignUtil类的介绍,这个工具类其实实现的功能就是第一步中将生成签名的文字转换为代码。

String toSign = content + "&" + devId + "&" + devKey;   // 拼接加密前的字符串

String md5Result = md5(toSign.getBytes(StandardCharsets.UTF_8));    // 进行md5加密

String finalResult = base64Encode(md5Result.getBytes(StandardCharsets.UTF_8));   // 再进行base64计算

最后得到的finalResult就是我们加密后的签名sign

JsonUtil类

ObjectMapper:实现json反序列化为java对象
toObject方法:用于将字符串转换为指定的类型
该工具类中还有其他的方法,但是目前我做的任务中还没有用到,所以暂时就不介绍了。

HttpUtil类
Post方法

post返回值的是Response对象;
需要使用JsonUtil将请求发送的参数对象转为String类型;
HttpHeaders用于生成请求头,HttpEntity用于将headers和请求数据(body)进行整合,用于post请求;
postForEntity:进行post请求,参数是链接、请求体;
返回的结果是ResponseEntity,再使用JsonUtil.toObject方法进行转换成Response类

JsonUtil.toObject(httpResponse.getBody(), Response.class);  // 转换后的Response对象
DateUtil类

这个类主要解决之后我们会遇到的一个问题,获取秒级时间戳,其实也很简单,只是下面一句话

System.currentTimeMillis() / 1000   // 获取秒级时间戳
UrlGenerateUtil类

这个类的主要目的是根据用户的不同的请求生成对应的Url,查看文档可以知道,每个功能的接口Url是不同的,所以需要相应的生成对应的Url。

这里举个例子,加入我们想要创建订单,则对应创建订单地址应该是
https://commit-openic.sf-express.com/open/api/external/createorder?sign=$sign
这个地址我们拆分成三个部分,首先是hostServer,其次是Url,最后是签名;由于是测试环境,所以hostServer = “https://commit-openic.sf-express.com”
现在是url,根据创建订单页面中的post请求地址我们可以知道,这里的
url = “/open/api/external/createorder”
sign由我们后台生成。最后的结果就是上面的地址了。
这里我们举一个接下来会用到的例子,创建订单

    private static final String CREATE_ORDER_URL = "/open/api/external/createorder";

    public static String getCreateOrderUrl(String sfLogisticsHost) {
        return sfLogisticsHost + CREATE_ORDER_URL;
    }

    public static String getOrderStatusUrl(String sfLogisticsHost){ return sfLogisticsHost + ORDER_STATUS_URL;}

这里还有一个小问题,getCreateOrderUrl方法中需要传递的sfLogisticsHost是什么?从哪儿来呢?
这里根据拼接规则可以发现这是官网文档中给的接口地址,这些我们都可以写在配置文件中

配置文件编写

创建logistics.properties文件,将配置信息编写进去

# 顺丰物流的配置信息
logistics.developer-id= 1578751034
logistics.developer-key= 4fb6604b07f7ca1df68efdd435674f61
logistics.shop-id= 3243279847393
logistics.api-url= https://commit-openic.sf-express.com

将这些信息封装

@Data
@Component
@ConfigurationProperties(prefix = "logistics")
public class LogisticsProperties {

    @Value("${logistics.developer-id}")
    private Integer developerId;

    @Value("${logistics.developer-key}")
    private String developerKey;

    @Value("${logistics.api-url}")
    private String apiUrl;

    @Value("${logistics.shop-id}")
    private String shopId;

}

工具我们介绍的差不多了,接下来我们进行下一步。相信各位小伙伴看了文档会发现,我们在发送请求时需要设定一些参数,那这就涉及到我们需要为这些参数设计一个能够对他们进行get/set的类。

我把所有的request请求需要用到的参数类全部放到了request文件夹下。这里举一个例子:订单创建

Request类编写

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

@Data
public class CreateOrderRequest{
    /**
     * 是	同城开发者ID
     */
    @JsonProperty("dev_id")
    private Integer devId;

    /**
     * 	0	是	店铺ID
     */
    @JsonProperty("shop_id")
    private String shopId;
    /**
     * 	店铺ID类型 否
     * 	1:顺丰店铺ID ;2:接入方店铺ID
     */
    @JsonProperty("shop_type")
    private Integer shopType;

    /**
     * 商家订单号 是
     * 不允许重复
     */
    @JsonProperty("shop_order_id")
    private String shopOrderId;

    /**
     * 订单接入来源 是
     * 1:美团;2:饿了么;3:百度;4:口碑;其他请直接填写中文字符串值
     */
    @JsonProperty("order_source")
    private String orderSource;

    /**
     * 取货序号 否
     * 与order_source配合使用
     * 如:饿了么10号单,表示如下:
     * order_source=2;order_sequence=10。
     * 用于骑士快速寻找配送物
     */
    @JsonProperty("order_sequence")
    private String orderSequence;

    /**
     * 坐标类型 否
     * 1:百度坐标,2:高德坐标
     */
    @JsonProperty("lbs_type")
    private Integer lbsType;

    /**
     * 用户支付方式 是
     * 1:已付款 0:货到付款
     */
    @JsonProperty("pay_type")
    private Integer payType;

    /**
     * 代收金额 否
     * 单位:分
     */
    @JsonProperty("receive_user_money")
    private Integer receiveUserMoney;

    /**
     * 用户下单时间 是
     * 秒级时间戳
     */
    @JsonProperty("order_time")
    private Integer orderTime;

    /**
     * 是否是预约单 是
     * 0:非预约单;1:预约单
     */
    @JsonProperty("is_appoint")
    private Integer isAppoint;

    /**
     * 用户期望送达时间 否
     * 预约单需必传,秒级时间戳
     */
    @JsonProperty("expect_time")
    private Integer expectTime;
    /**
     * 是否保价 是
     * 0:非保价;1:保价
     */
    @JsonProperty("is_insured")
    private Integer isInsured;

    /**
     * 保价金额 否
     * 单位:分
     */
    @JsonProperty("declared_value")
    private Integer declaredValue;

    /**
     * 订单备注 否
     */
    @JsonProperty("remark")
    private String remark;

    /**
     * 物流流向 否
     * 1:从门店取件送至用户;
     * 2:从用户取件送至门店
     */
    @JsonProperty("rider_pick_method")
    private Integer riderPickMethod;

    /**
     * 返回字段控制标志位(二进制) 否
     * 1:价格,2:距离,4:重量,组合条件请相加
     * 例如全部返回为填入7
     */
    @JsonProperty("return_flag")
    private Integer returnFlag;

    /**
     * 推单时间 是
     * 秒级时间戳
     */
    @JsonProperty("push_time")
    private Integer pushTime;

    /**
     * 版本号 是
     * 参照文档主版本号填写
     * 如:文档版本号1.7,version=17
     */
    @JsonProperty("version")
    private Integer version;

    /**
     * 收货人信息 是
     * Obj,详见receive结构
     */
    @JsonProperty("receive")
    private Receive receive;

    /**
     * 发货店铺信息 否
     * Obj,详见shop结构,
     * 平台级开发者(如饿了么)需传入
     * 如无特殊说明此字段可忽略
     */
    @JsonProperty("shop")
    private Shop shop;

    /**
     * 订单详情 是
     * Obj,详见order_detail结构
     */
    @JsonProperty("order_detail")
    private OrderDetail orderDetail;

}

这里的Shop和OrderDetail在文档中又有多个属性,所以这里也当成一个类,同样的根据文档的参数进行类的构建


import lombok.Builder;
import lombok.Data;

/**
 * 店铺信息
 */

@Data
@Builder
public class Shop {
    /**
     * 店铺名称 是
     */
    private String shopName;

    /**
     * 店铺电话 是
     */
    private String shopPhone;

    /**
     * 店铺地址 是
     */
    private String shopAddress;

    /**
     * 店铺经度 否
     */
    private String shopLng;

    /**
     * 店铺纬度 否
     */
    private String shopLat;
}

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;

import java.util.List;

@Data
@Builder
public class OrderDetail {
    /**
     * 用户订单总金额(单位:分) 是
     */
    @JsonProperty("total_price")
    private Integer totalPrice;

    /**
     * 物品类型 是
     * 1快餐;2送药;3百货;
     * 4脏衣服收;5干净衣服派;6生鲜;
     * 7保单;8饮品;9现场勘查;
     * 10快递;12文件证照;13蛋糕;
     * 14鲜花;15电子数码;16服装鞋帽;
     * 17汽车配件;18珠宝;20披萨;
     * 21中餐;99其他
     */
    @JsonProperty("product_type")
    private Integer productType;

    /**
     * 实收用户金额(单位:分)否
     */
    @JsonProperty("user_money")
    private Integer userMoney;

    /**
     * 实付商户金额(单位:分) 否
     */
    @JsonProperty("shop_money")
    private Integer shopMoney;

    /**
     * 物品重量(单位:克)是
     */
    @JsonProperty("weight_gram")
    private Integer weightGram;

    /**
     * 物品体积(单位:升) 否
     */
    @JsonProperty("volume_litre")
    private Integer volumeLitre;

    /**
     * 商户收取的配送费(单位:分) 否
     */
    @JsonProperty("delivery_money")
    private Integer deliveryMoney;

    /**
     * 物品个数 是
     */
    @JsonProperty("product_num")
    private Integer productNum;

    /**
     * 物品种类个数 是
     */
    @JsonProperty("product_type_num")
    private Integer productTypeNum;

    /**
     * 物品详情 是
     */
    @JsonProperty("product_detail")
    private List<ProductDetail> productDetail;
}


有了request,当然还得有response,根据文档中的返回值,编写好response类。

首先是response类,这个是文档中规定的返回请求。

{
    "error_code": 0,
    "error_msg": "",
    "error_data": null,//详细报错信息(报错的时候非空)
    "result": {}
}

Response类编写


@Data
public class Response {
    @JsonProperty("error_code")
    private Integer errorCode;

    @JsonProperty("error_msg")
    private String errorMsg;

    /**
     * 详细报错信息(报错的时候非空)
     */
    @JsonProperty("error_data")
    private Object errorData;

    @JsonProperty("result")
    private Object result;
}

但其实result才是我们想要的返回的内容,所以我们要根据不同的请求编写不同的result类,这里我们编写的类是CreateOrderResponse类。

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

@Data
public class CreateOrderResponse extends Response{
    /**
     * 3165848793513984",//顺丰订单号(标准默认为int,可以设置为string)
     */
    @JsonProperty("sf_order_id")
    private Long sfOrderId;

    /**
     * 509008343346",//顺丰运单号(需要设置)
     */
    @JsonProperty("sf_bill_id")
    private String sfBillId;

    /**
     * 15104092022333",//商家订单号
     */
    @JsonProperty("shop_order_id")
    private String shopOrderId;

    /**
     * 1510680568" //推送时间
     */
    @JsonProperty("push_time")
    private Integer pushTime;

    /**
     * 以下字段受请求参数中 return_flag 控制:return_flag中未包含的,此字段将不存在,请注意!
     * 1300, //配送费价格,当return_flag中包含1时返回,单位分(值为计算出来此单总价)
     */
    @JsonProperty("total_price")
    private Integer totalPrice;

    /**
     * 1234, //配送距离,当return_flag中包含2时返回,单位米(值为计算出来实际配送距离)
     */
    @JsonProperty("delivery_distance_meter")
    private Integer deliveryDistanceMeter;

    /**
     * 1000, //商品重量,当return_flag中包含4时返回,单位克(值为下单传入参数回传)
     */
    @JsonProperty("weight_gram")
    private Integer weightGram;

}

好了,我们现在把所有要用到的实体类都已经编写完成了。接下来还有两个问题,第一是发送请求前需要规定好request中的一些参数,第二就是发送post请求。我们一步一步解决。

这里我们先收集一些需要的参数,在官网的开发者中心可以看到必要的参数

点击店铺可以看到我们测试需要的一些店铺信息
在这里插入图片描述

密钥

初始化各类信息

首先给CreateOrderRequest附上请求必要的一些参数,由于之后我们肯定不是以写死的方式来赋值(废话),所以我们应该在调用的时候传递一个符合条件的参数,但是这里是测试,我这里给出一个初始化各项参数的方法,仅供测试使用。

private CreateOrderRequest initCreatePreOrderRequest() {
        PreCreateOrderRequest preCreateOrderRequest = new PreCreateOrderRequest();
        preCreateOrderRequest.setProductType(1);
        preCreateOrderRequest.setUserLng("116.3534196");
        preCreateOrderRequest.setUserLat("40.0159778");
        preCreateOrderRequest.setUserAddress("123456");
        preCreateOrderRequest.setShopOrderId(System.currentTimeMillis() + "");
        preCreateOrderRequest.setOrderSource("测试");
        preCreateOrderRequest.setPayType(1);
        preCreateOrderRequest.setOrderTime(DateUtil.currentSecond().intValue());
        preCreateOrderRequest.setIsAppoint(0);
        preCreateOrderRequest.setIsInsured(0);
        preCreateOrderRequest.setRiderPickMethod(1);
        preCreateOrderRequest.setPushTime(DateUtil.currentSecond().intValue());
        preCreateOrderRequest.setVersion(17);
        preCreateOrderRequest.setShop(
                Shop.builder()
                        .shopName("店铺名")
                        .shopPhone("13266666666")
                        .shopAddress("朝阳区高碑店镇四惠大厦F1-008")
                        .shopLng("116.514236")
                        .shopLat("39.905328")
                        .build()
        );
        preCreateOrderRequest.setReceive(
                Receive.builder()
                        .userName("小明")
                        .userPhone("13288888888")
                        .userLng("116.3534196")
                        .userLat("40.0159778")
                        .userAddress("朝阳区高碑店镇四惠大厦F1-008")
                        .cityName("北京市")
                        .build()
        );
        preCreateOrderRequest.setOrderDetail(
                OrderDetail.builder()
                        .totalPrice(100)
                        .productType(1)
                        .weightGram(500)
                        .productNum(1)
                        .productTypeNum(1)
                        .productDetail(
                                Stream.of(
                                        ProductDetail.builder()
                                                .productName("小炒肉")
                                                .productNum(1)
                                                .build()
                                ).collect(Collectors.toList())
                        )
                        .build()
        );
        return preCreateOrderRequest;
    }

接下来是Service,这里就只展示实现类的代码,接口就由各位自己去实现了。


@Service
@Slf4j
public class SfServiceImpl implements SfService {
    // 这里写请求不同功能的方法
    @Autowired
    private LogisticsProperties logisticsProperties;


    /**
     * 创建订单
     * @param createOrderRequest
     * @return CreateOrderResponse
     */
    @Override
    public CreateOrderResponse createOrder(CreateOrderRequest createOrderRequest){

        // 开发者ID赋值
        createOrderRequest.setDevId(logisticsProperties.getDeveloperId());
        createOrderRequest.setShopId(logisticsProperties.getShopId());  // shopId需要传递进来
        String content = JsonUtil.toJsonString(createOrderRequest);    // 将请求对象转换为String
        log.info("正在向顺丰接口进行请求......");
        // 发送POST请求
        Response response = HttpUtil.post(
                    logisticsProperties.getDeveloperId(),
                    logisticsProperties.getDeveloperKey(),
                    UrlGenerateUtil.getCreateOrderUrl(logisticsProperties.getApiUrl()),
                    content
            );
        if(StrUtil.isNotEmpty(response.getErrorMsg())){
            log.error("顺丰接口返回的错误信息:" + response.getErrorMsg());
        }
        return JsonUtil.toObject(response.getResult(), CreateOrderResponse.class);
    }
}

ok,我们所有的准备已经完成了,现在可以开始给顺丰发请求了。

我们在需要使用到功能的地方添加上相应的代码

    @Autowired
	private SfService sfService

    public void test(){
         ...
        /**
         * 构建一个对象,符合CreateOrderRequest,对象为createOrderRequest
         */
        System.out.println(sfService.createOrder(createOrderRequest));
    }

我们的调用createOrder方法,传递相应的参数后,观察返回的结果。

结果分析

我这里直接输出返回结果,也就是之前提到的result对象。

返回结果

可以看到这里有返回的顺丰订单号、顺丰运单号等。现在我们就可以利用这些信息给回到我们想要显示的地方了。

当然,如果我们传入的参数有缺失,返回的结果中error_msg和error_data会有详细的信息。根据返回的结果进行修改即可。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值