由于公司外卖业务需要用到顺丰的配送体系,技术上需要对接顺丰 个人比较感兴趣,但是顺丰没有提供sdk,所以研究下自己写了一个
完整代码已上传github ?:
技术选型
三方sdk编写有两种实现方式
- 不依赖框架,更通用,但是集成成本较高
- 依赖框架,比如spring boot,这样使用起来效率更高
为了提高使用效率,这里选择基于spring boot框架进行编写
前期准备
有很多基于spring boot的sdk了,骨架就不需要自行搭建了,找了下面几个进行参考
研究api文档
顺丰同城api文档地址:
目前顺丰同城的开发者api个人可以注册,注册后可以设置回调地址
找出文档中的关键点
在后续设计sdk上,考虑下这些点,可以让sdk更好用
顺丰状态回调可能会失败,通过定时调用查询订单状态接口可以补齐状态
可以实时获取配送员的坐标,这个可用在app上实时显示配送员位置功能
创建项目
虽然项目是作为和spring boot一起使用的,但是我们并不需要依赖完整的spring boot框架,所以创建一个maven项目就是ok的
指定groupId, ArtifactId
为了享受spring boot的自动配置,需要pom.xml里面加上spring-boot-autoconfigure库依赖
复制代码 org.springframework.boot spring-boot-autoconfigure
加上常用的http,lombok等库,最终pom文件内容如下
复制代码 4.0.0 com.github.neatlife sfcity 1.0-SNAPSHOT org.apache.maven.plugins maven-compiler-plugin org.springframework.boot spring-boot-autoconfigure org.springframework.boot spring-boot-starter-test org.springframework.boot spring-boot-starter-web org.projectlombok lombok 1.18.0 provided com.fasterxml.jackson.core jackson-core 2.9.8 com.fasterxml.jackson.core jackson-databind 2.9.8 com.fasterxml.jackson.core jackson-annotations 2.9.8 org.springframework.boot spring-boot-dependencies 2.0.2.RELEASE pom import
自动配置api的密钥
把从顺丰api后台获取的配置信息写入配置文件:src/main/resources/application.properties 所有配置如下
sfcity.developer-id= xxxsfcity.developer-key= xxxsfcity.shop-id= xxxsfcity.api-url= https://commit-openic.sf-express.com复制代码
使用spring boot的自动配置机制,能够很方便的从配置文件中读取配置 核心代码如下
@ConfigurationProperties(prefix = "sfcity")@Datapublic class Properties { private Integer developerId; private String developerKey; private String shopId; private String apiUrl;}复制代码
参考:
- src/main/java/com/github/neatlife/AutoConfiguration.java
- src/main/java/com/github/neatlife/Properties.java
映射请求参数和响应参数
因为要做一个通用的sdk库,那么所有的请求参数和响应参数都需要映射,方便使用
这里为了演示就拿创建订单接口举例了
创建订单请求实体
响应实体
还有一些关联的实体一并创建,最终实体效果如下:
http处理工具类
使用resetTemplate进行请求,参考: 核心代码如下:
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); HttpEntityhttpEntity = new HttpEntity<>(content, headers); ResponseEntity httpResponse = restTemplate.postForEntity(url, httpEntity, String.class); Response response = JsonUtil.toObject(httpResponse.getBody(), Response.class); if (response.getErrorCode() != 0) { log.error("errorData: {}", response.getErrorData()); throw new RuntimeException(response.getErrorMsg()); } return response;}复制代码
json处理工具类
json处理工具类直接从自己编写的框架里拿,参考:
签名工具类
顺丰提供了java的签名示例代码
在其基础上修改即可,核心代码如下
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));}复制代码
完整代码参考:src/main/java/com/github/neatlife/util/SignUtil.java
定义接口常量
把需要调用的接口地址放到统一的常量文件中,方便管理 核心代码如下:
public class ApiUrlConstant { private static final String CREATE_ORDER_URL = "/open/api/external/createorder"; private static String sfCityHost; public static String getCreateOrderUrl() { return sfCityHost + CREATE_ORDER_URL; } public static void setSfCityHost(String sfCityHost) { ApiUrlConstant.sfCityHost = sfCityHost; }}复制代码
调用顺丰创建订单接口
上面步骤都准备完成后,进行到最重要的调用环节了,有了上面的准备,这一步也比较容易了 核心代码如下
public CreateOrderResponse createOrder(CreateOrderRequest createOrderRequest) { createOrderRequest.setDevId(developerId); createOrderRequest.setShopId(shopId); Response response = HttpUtil.post( developerId, developerKey, ApiUrlConstant.getCreateOrderUrl(), createOrderRequest ); return JsonUtil.toObject(response.getResult(), CreateOrderResponse.class);}复制代码
编写自动测试
创建测试文件:src/test/java/com/github/neatlife/SfClientTest.java 填充测试数据 调用创建订单方法
@Testpublic void createOrder() { CreateOrderResponse createOrderResponse = sfClient.createOrder(createOrderRequest()); Assert.assertNotNull(createOrderResponse.getSfOrderId());}private CreateOrderRequest createOrderRequest() { CreateOrderRequest createOrderRequest = new CreateOrderRequest(); createOrderRequest.setShopOrderId(System.currentTimeMillis() + ""); createOrderRequest.setOrderSource("测试"); createOrderRequest.setPayType(1); createOrderRequest.setOrderTime(DateUtil.currentSecond().intValue()); createOrderRequest.setIsAppoint(0); createOrderRequest.setIsInsured(0); createOrderRequest.setRiderPickMethod(1); createOrderRequest.setPushTime(DateUtil.currentSecond().intValue()); createOrderRequest.setVersion(17); createOrderRequest.setShop( Shop.builder() .shopName("店铺名") .shopPhone("13266666666") .shopAddress("朝阳区高碑店镇四惠大厦F1-008") .shopLng("116.514236") .shopLat("39.905328") .build() ); createOrderRequest.setReceive( Receive.builder() .userName("小明") .userPhone("13288888888") .userPhone("北京") .userLng("116.3534196") .userLat("40.0159778") .userAddress("朝阳区高碑店镇四惠大厦F1-008") .cityName("北京市") .build() ); createOrderRequest.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 createOrderRequest;}复制代码
填充测试数据时注释对照顺丰文档,保证必填字段都有值
查看运行效果:
顺丰返回了创建订单成功的响应?
一些注意的点
自动配置时,把api地址注入接口常量文件中,方便读取
http调用会有失败的可能,需要考虑进行请求补偿,一般有下面两种重试方式
- 定时任务,定时进行补偿
- 使用消息队列的补偿机制
创建外卖订单前,可以先调用顺丰预创建订单核查顺丰是否会接单
打包
mvn clean package -Dmaven.test.skip=true
查看效果
然后把jar包拷到需要的项目就可以使用了后续传到了maven中央仓库,也可以直接使用maven下载
在spring boot项目中使用jar包
用idea打开spring boot项目,在项目设置的库依赖里引用jar包
然后在启动类中让spring boot扫到这个库,就可以自动配置加载了
其它接口
按照上面创建订单的步骤编写+测试即可
上传maven中央仓库
参考:
持续更新...