前几天,公司要求我们需要去对接一个关于同城配送的功能,阿粉很荣幸,接到了这个比较简单的功能,有很多做开发的小伙伴,在对接某些开放平台的时候,会感觉到很麻木,因为感觉没啥技术含量,而且感觉还很low,实际上也不是这样,至少从代码层面,你会感觉到,有些人写的还不如你,今天阿粉就来教给大家如何去对接这个顺丰同城配送的api。
注册账号
首先我们可以先找到顺丰同城配送开放平台,顺丰同城开放平台
我们先注册一个账号,并且登录,阿粉已经登录过了,于是就可以看到下面这样的内容了。
申请测试开发者ID
这个开发者ID就是顺丰提供给你调用接口的,不管你去对接百度的API接口也好,还是你去对接腾讯的接口也好,一般都是需要你去创建一个开发者的ID,然后提供给服务商去获取一些加密的操作。
申请完成之后,这时候你实际上就可以开始看他的对接文档了,因为我们已经申请了appid和对应的key。
但是阿粉接下来没想到的时候,顺丰同城,竟然没有对接接口的Demo,只有一个模板的示例代码,阿粉就有点惊讶了,这什么鬼,Demo,看来只能细细的看这个文档了。
看这个,感觉好像不是很给力的样子,毕竟之前已经对接过百度的一些相关的接口了,都是用appid和key去服务商那边去换取一下这个token,然后每次调用接口的时候,直接把这个token带过去,让对方检验,这个顺丰倒好,直接让你把参数都得处理了,还得加密了。
好吧,那我们就按照他给的规则来呗。
先写一个这个加密的方法吧,这样每次调用接口之前,都不用再去处理了。
代码语言:javascript复制public class SignUtil {
/**
* 签名方法
* 参数:content:要发送的JSON结构字符串
* 返回值:签名信息字符串
* 注意:签名计算结果可以到此页面进行验证:http://sftc.jsonce.com/sign/
*/
public static String sign(String appId, String appSecret, String content) {
// 假设原始内容JSON为 {"hello":"kitty"}
// content : "{"hello":"kitty"}"
String toSign = content "&" appId "&" appSecret;
// toSign : "{"hello":"kitty"}&1234567890&0123456789abcdef0123456789abcdef";
String md5Result = md5(toSign.getBytes(StandardCharsets.UTF_8));
// md5Result : "ef3435b1480e553480e19e3e162fb0be"
// signResult : "ZWYzNDM1YjE0ODBlNTUzNDgwZTE5ZTNlMTYyZmIwYmU="
return base64Encode(md5Result.getBytes(StandardCharsets.UTF_8));
}
private static String toHex(byte[] bytes) {
final char[] hexDigits = "0123456789abcdef".toCharArray();
StringBuilder ret = new StringBuilder(bytes.length * 2);
for (byte aByte : bytes) {
ret.append(hexDigits[(aByte >> 4) & 0x0f]);
ret.append(hexDigits[aByte & 0x0f]);
}
return ret.toString();
}
private static String md5(byte[] toSignBytes) {
try {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(toSignBytes);
return toHex(bytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static String base64Encode(byte[] md5ResultBytes) {
java.util.Base64.Encoder be = java.util.Base64.getEncoder();
return be.encodeToString(md5ResultBytes);
}
}
接下来我们就调用顺丰同城测试一个订单吧。
请求接口,那必然少不了Http的请求,我们先封装一个请求顺丰同城的http工具类,这样方便以后的调用。
代码语言:javascript复制public class HttpUtil {
public static Response post(Integer appId, String appSecret, String url, Request request) {
String content = JsonUtil.toJsonString(request);
String sign = SignUtil.sign(appId.toString(), appSecret, content);
url = url "?sign=" sign;
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
HttpEntity<String> httpEntity = new HttpEntity<>(content, headers);
ResponseEntity<String> httpResponse = restTemplate.postForEntity(url, httpEntity, String.class);
Response response = JsonUtil.toObject(httpResponse.getBody(), Response.class);
if (response.getErrorCode() != 0) {
log.error("errorData: {}", response.getErrorData());
}
return response;
}
}
下面就得看看他都是需要什么参数了。
我们给他创建一个实体类
代码语言:javascript复制@Data
public class Shop {
/**
* string(64) 空 是 店铺名称
*/
private String shopName;
/**
* string(64) 空 是 店铺电话
*/
private String shopPhone;
/**
* string(255) 空 是 店铺地址
*/
private String shopAddress;
/**
* string(32) 空 否 店铺经度
*/
private String shopLng;
/**
* string(32) 空 否 店铺纬度
*/
private String shopLat;
}
代码语言:javascript复制public class Receive {
/**
* string(64) 空 是 用户姓名
*/
@JsonProperty("user_name")
private String userName;
/**
* string(64) 空 是 用户电话
*/
@JsonProperty("user_phone")
private String userPhone;
/**
* string(255) 空 是 用户地址
*/
@JsonProperty("user_address")
private String userAddress;
/**
* string(32) 空 是 用户经度
*/
@JsonProperty("user_lng")
private String userLng;
/**
* string(32) 空 是 用户纬度
*/
@JsonProperty("user_lat")
private String userLat;
/**
* string(32) 空 否 发单城市 用来校验是否跨城;请填写城市的中文名称,如北京市、深圳市
*/
@JsonProperty("city_name")
private String cityName;
}
秉着要啥给啥的原则:我们继续,
代码语言:javascript复制@Data
public class OrderDetail {
/**
* int(11) 0 是 用户订单总金额(单位:分)
*/
@JsonProperty("total_price")
private Integer totalPrice;
/**
* int(11) 0 是 物品类型 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;
/**
* int(11) 0 否 实收用户金额(单位:分)
*/
@JsonProperty("user_money")
private Integer userMoney;
/**
* int(11) 0 否 实付商户金额(单位:分)
*/
@JsonProperty("shop_money")
private Integer shopMoney;
/**
* int(11) 0 是 物品重量(单位:克)
*/
@JsonProperty("weight_gram")
private Integer weightGram;
/**
* int(11) 0 否 物品体积(单位:升)
*/
@JsonProperty("volume_litre")
private Integer volumeLitre;
/**
* int(11) 0 否 商户收取的配送费(单位:分)
*/
@JsonProperty("delivery_money")
private Integer deliveryMoney;
/**
* int(11) 0 是 物品个数
*/
@JsonProperty("product_num")
private Integer productNum;
/**
* int(11) 0 是 物品种类个数
*/
@JsonProperty("product_type_num")
private Integer productTypeNum;
/**
* Array 空 是 物品详情;数组结构,详见product_detail结构
*/
@JsonProperty("product_detail")
private List<ProductDetail> productDetail;
}
代码语言:javascript复制public class ProductDetail {
/**
* string(64) 空 是 物品名称
*/
@JsonProperty("product_name")
private String productName;
/**
* int(11) 0 否 物品ID
*/
@JsonProperty("product_id")
private String productId;
/**
* int(11) 0 是 物品数量
*/
@JsonProperty("product_num")
private Integer productNum;
/**
* int(11) 0 否 物品价格
*/
@JsonProperty("product_price")
private String productPrice;
/**
* string(64) 空 否 物品单位
*/
@JsonProperty("product_unit")
private String productUnit;
/**
* string(256) 空 否 备注
*/
@JsonProperty("product_remark")
private String productRemark;
/**
* string(1024) 空 否 详情
*/
@JsonProperty("item_detail")
private String itemDetail;
}
参数我们都拼接完活了,那是不是就应该去写一个Demo试试,看看我们创建的这些类都好用不好用?
接下来我们再创建一个请求地址的类,这样就可以方便我们调用了。
代码语言:javascript复制public class ApiUrlConstant {
private static final String sfCityHost = "https://openic.sf-express.com";
/**
* 创建订单的URL
*/
private static final String CREATE_ORDER_URL = "/open/api/external/createorder";
/**
* 预创建订单的URL
*/
private static final String PRE_CREATE_ORDER_URL = "/open/api/external/precreateorder";
/**
* 订单实时信息查询
*/
private static final String GET_ORDER_STATUS = "/open/api/external/getorderstatus";
public static String getCreateOrderUrl() {
return sfCityHost CREATE_ORDER_URL;
}
public static String getPreCreateOrderUrl() {
return sfCityHost PRE_CREATE_ORDER_URL;
}
public static String getOrderStatusUrl(){
return sfCityHost GET_ORDER_STATUS;
}
}
这时候我们再封装一个请求的request类,
代码语言:javascript复制public class CreateOrderRequest extends Request {
// int(11) 0 是 同城开发者ID 可在顺丰同城开放平台上自助申请
@JsonProperty("dev_id")
private Integer devId;
// string(64) 0 是 店铺ID
@JsonProperty("shop_id")
private String shopId;
// int(11) 1 否 店铺ID类型 1:顺丰店铺ID ;2:接入方店铺ID
@JsonProperty("shop_type")
private Integer shopType;
/**
* string(128) 空 是 商家订单号 不允许重复
*/
@JsonProperty("shop_order_id")
private String shopOrderId;
/**
* string(12) 空 是 订单接入来源 1:美团;2:饿了么;3:百度;4:口碑;其他请直接填写中文字符串值
*/
@JsonProperty("order_source")
private String orderSource;
/**
* string(12) 空 否 取货序号 与order_source配合使用
* 如:饿了么10号单,表示如下:
* order_source=2;order_sequence=10。
* 用于骑士快速寻找配送物
*/
@JsonProperty("order_sequence")
private String orderSequence;
/**
* int(2) 2 否 坐标类型 1:百度坐标,2:高德坐标
*/
@JsonProperty("lbs_type")
private Integer lbsType;
/**
* int(11) 1 是 用户支付方式 1:已付款 0:货到付款
*/
@JsonProperty("pay_type")
private Integer payType;
/**
* int(11) 0 否 代收金额 单位:分
*/
@JsonProperty("receive_user_money")
private Integer receiveUserMoney;
/**
* int(11) 0 是 用户下单时间 秒级时间戳
*/
@JsonProperty("order_time")
private Integer orderTime;
/**
* int(2) 0 是 是否是预约单 0:非预约单;1:预约单
*/
@JsonProperty("is_appoint")
private Integer isAppoint;
/**
* int(11) 0 否 用户期望送达时间 预约单需必传,秒级时间戳
*/
@JsonProperty("expect_time")
private Integer expectTime;
/**
* int(11) 0 是 是否保价,0:非保价;1:保价
*/
@JsonProperty("is_insured")
private Integer isInsured;
/**
* int(11) 0 否 保价金额 单位:分
*/
@JsonProperty("declared_value")
private Integer declaredValue;
/**
* string(1024) 空 否 订单备注
*/
@JsonProperty("remark")
private String remark;
/**
* int(11) 1 否 物流流向 1:从门店取件送至用户;
* 2:从用户取件送至门店
*/
@JsonProperty("rider_pick_method")
private Integer riderPickMethod;
/**
* int 1 否 返回字段控制标志位(二进制) 1:价格,2:距离,4:重量,组合条件请相加
* 例如全部返回为填入7
*/
@JsonProperty("return_flag")
private Integer returnFlag;
/**
* int(11) 0 是 推单时间 秒级时间戳
*/
@JsonProperty("push_time")
private Integer pushTime;
/**
* int(11) 0 是 版本号 参照文档主版本号填写
* 如:文档版本号1.7,version=17
*/
@JsonProperty("version")
private Integer version;
/**
* OBJ 空 是 收货人信息 Obj,详见receive结构
*/
@JsonProperty("receive")
private Receive receive;
/**
* OBJ 空 否 发货店铺信息 Obj,详见shop结构,
* 平台级开发者(如饿了么)需传入
* 如无特殊说明此字段可忽略
*/
@JsonProperty("shop")
private Shop shop;
/**
* OBJ 空 是 订单详情 Obj,详见order_detail结构
*/
@JsonProperty("order_detail")
private OrderDetail orderDetail;
/**
* 是否优先派单,0:否 1:是
*/
@JsonProperty("is_priority_assign")
private Integer isPriorityAssign;
/**
* 是否是专人直送订单,0:否;1:是
*/
@JsonProperty("is_person_direct")
private Integer isPersonDirect;
}
这样就万事大吉了,我们只需要拼接参数,然后调用一下了!
拼接参数,请求调用:
代码语言:javascript复制CreateOrderRequest createOrderRequest = new CreateOrderRequest();
createOrderRequest.setDevId(developerId);
createOrderRequest.setShopId(shopId);
createOrderRequest.setShopType(2);
createOrderRequest.setShopOrderId(UUIDUtil.getUUID());
createOrderRequest.setOrderSource("美团");
createOrderRequest.setLbsType(2);
createOrderRequest.setPayType(1);
LocalDateTime now = LocalDateTime.now();
long l = now.atZone(ZoneOffset.systemDefault()).toEpochSecond();
// createOrderRequest.setOrderTime(DateUtil.currentSecond().intValue());
createOrderRequest.setOrderTime(Integer.parseInt(String.valueOf(l)));
createOrderRequest.setIsAppoint(0);
createOrderRequest.setIsInsured(0);
createOrderRequest.setIsPriorityAssign(0);
createOrderRequest.setIsPersonDirect(0);
createOrderRequest.setRiderPickMethod(1);
createOrderRequest.setPushTime(DateUtil.currentSecond().intValue());
createOrderRequest.setVersion(17);
//初始化门店
Shop shop = new Shop();
shop.setShopName("顺丰同城开放平台");
shop.setShopPhone("18315652836");
shop.setShopLat("116.377441");
shop.setShopLat("40.032368");
OrderDetail orderDetail = new OrderDetail();
orderDetail.setTotalPrice(100);
orderDetail.setProductType(1);
orderDetail.setWeightGram(500);
orderDetail.setProductNum(0);
orderDetail.setProductTypeNum(1);
ProductDetail productDetail = new ProductDetail();
productDetail.setProductName("奶香提子麻薯");
productDetail.setProductNum(1);
List<ProductDetail> productDetails = new ArrayList<>();
orderDetail.setProductDetail(productDetails);
//初始化订单
//初始化接收者
Receive receive = Receive.builder()
.userName("开发人员")
.userPhone("15153221122")
.userLng("116.4255952835083")
.userLat("39.92558135671958")
.userAddress("北京市海淀区")
.cityName("北京市")
.build();
createOrderRequest.setShop(shop);
createOrderRequest.setReceive(receive);
createOrderRequest.setOrderDetail(orderDetail);
HttpUtil.post(
developerId,
developerKey,
ApiUrlConstant.getCreateOrderUrl(),
createOrderRequest
);
我们用postman试一下,看看能不能创建订单成功?
代码语言:javascript复制{
"pushTime": 1652863523,
"sfBillId": "SF6509777913611",
"sfOrderId": "3466306039564105729",
"shopOrderId": "3aea9e25b23140068ecc7aefac389f23",
"totalPrice": 1
}
请求通过,有返回值了,并且他爸订单号都给我们了,这是不是就成功了呢?
看来对接这个同城的API 也是非常简单的了,毕竟创建订单需要的参数是非常麻烦的,当你静下心弄得时候,发现很快就完事了。你学会了么?