由于公司外卖业务需要用到顺丰的配送体系,技术上需要对接顺丰 个人比较感兴趣,但是顺丰没有提供sdk,所以研究下自己写了一个
完整代码已上传github :tada:: github.com/neatlife/sf…
三方sdk编写有两种实现方式
为了提高使用效率,这里选择基于spring boot框架进行编写
有很多基于spring boot的sdk了,骨架就不需要自行搭建了,找了下面几个进行参考
顺丰同城api文档地址: commit-openic.sf-express.com/open/api/do…
目前顺丰同城的开发者api个人可以注册,注册后可以设置回调地址
在后续设计sdk上,考虑下这些点,可以让sdk更好用
顺丰状态回调可能会失败,通过定时调用查询订单状态接口可以补齐状态
可以实时获取配送员的坐标,这个可用在app上实时显示配送员位置功能
虽然项目是作为和spring boot一起使用的,但是我们并不需要依赖完整的spring boot框架,所以创建一个maven项目就是ok的
指定groupId, ArtifactId
为了享受spring boot的自动配置,需要pom.xml里面加上spring-boot-autoconfigure库依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
复制代码
加上常用的http,lombok等库,最终pom文件内容如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.neatlife</groupId>
<artifactId>sfcity</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
复制代码
把从顺丰api后台获取的配置信息写入配置文件:src/main/resources/application.properties 所有配置如下
sfcity.developer-id= xxx sfcity.developer-key= xxx sfcity.shop-id= xxx sfcity.api-url= https://commit-openic.sf-express.com 复制代码
使用spring boot的自动配置机制,能够很方便的从配置文件中读取配置 核心代码如下
@ConfigurationProperties(prefix = "sfcity")
@Data
public class Properties {
private Integer developerId;
private String developerKey;
private String shopId;
private String apiUrl;
}
复制代码
参考:
因为要做一个通用的sdk库,那么所有的请求参数和响应参数都需要映射,方便使用
这里为了演示就拿创建订单接口举例了
创建订单请求实体
响应实体
还有一些关联的实体一并创建,最终实体效果如下:
使用resetTemplate进行请求,参考: juejin.im/post/5b88b1… 核心代码如下:
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());
throw new RuntimeException(response.getErrorMsg());
}
return response;
}
复制代码
json处理工具类直接从自己编写的框架里拿,参考: github.com/neatlife/jf…
顺丰提供了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 填充测试数据 调用创建订单方法
@Test
public 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;
}
复制代码
填充测试数据时注释对照顺丰文档,保证必填字段都有值
查看运行效果:
顺丰返回了创建订单成功的响应:smile:
然后把jar包拷到需要的项目就可以使用了