一、背景
最近公司让我对接顺丰同城急送的API,讲讲里面需要注意的几点
官方的API文档有些示例代码也不全,具体细节不多说,如果你现在也需要对接他们API,可以参考本篇博客再配合官方文档结合起来看,可以让您再开发的时候少掉两根头发,对您会有一定帮助的
官网api文档
首先你们要对接他们产品之前,需要得到账号,账号这边是同事给我的,
开始对接之前,必须要搞清楚你们对接的是店铺还是企业版的,区别就是企业版本的顺丰官方会给你一个卡号,是月结卡,你公司本月下的单,扣费就在这个卡里面扣款,然后月底结算费用,店铺的是没有的(其他的后续我再补充)
二、代码
用他这个签名是没毛病的,但是如果你传入的数据只有一层对象,那么是可以的,但是,如果你传入的数据是二维map甚至更多层级(这里根据他的接口参数决定),那这样再调用他的sign签名会出问题,因为他这个postData参数必须是json格式的字符串,而且还要排序的,postData里面是你这个接口所有的参数,具体代码还是看我的这个更实在,sign具体看generateOpenSign方法:
package com.admin.business.controller.sfsamecity;
import com.admin.util.HttpUtils;
import com.alibaba.fastjson.JSONObject;
import net.sf.json.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.*;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Component;
@Component
public class SFHapper {
protected Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${sf.same.city.devId}")
private String devId;
@Value("${sf.same.city.secretKey}")
private String secretKey;
/**
* 预创建订单(店铺)
* https://openic.sf-express.com/open/api/docs/index#/apidoc
*/
public String precreateorder(Map<String, Object> param){
try {
param.put("dev_id",Integer.parseInt(devId));
String jsonString = formatAndSortMap(param,0);
String sign = generateOpenSign(jsonString,Integer.parseInt(devId),secretKey);
String resultStr = null;
try {
resultStr = HttpUtils.postJson("https://openic.sf-express.com/open/api/external/precreateorder?sign="+sign, jsonString);
}catch (Exception e){
e.printStackTrace();
}
return resultStr;
} catch (Exception e) {
logger.error(e.getMessage());
}
return null;
}
/**
* https://openic.sf-express.com/open/api/docs/index#/apidoc
* 创建订单(店铺)
*
*/
public String createorder(Map<String, Object> param){
try {
param.put("dev_id",Integer.parseInt(devId));
String jsonString = formatAndSortMap(param,0);
String sign = generateOpenSign(jsonString,Integer.parseInt(devId),secretKey);
String resultStr = null;
try {
resultStr = HttpUtils.postJson("https://openic.sf-express.com/open/api/external/createorder?sign="+sign, jsonString);
}catch (Exception e){
e.printStackTrace();
}
return resultStr;
} catch (Exception e) {
logger.error(e.getMessage());
}
return null;
}
/**
* 预创建订单(企业)
* https://openic.sf-express.com/open/api/docs/index#/apidoc
*/
public String precreateorderEnterprise(Map<String, Object> param){
try {
param.put("dev_id",Integer.parseInt(devId));
String jsonString = formatAndSortMap(param,0);
String sign = generateOpenSign(jsonString,Integer.parseInt(devId),secretKey);
String resultStr = null;
try {
resultStr = HttpUtils.postJson("https://openic.sf-express.com/open/api/external/precreateorder4c?sign="+sign, jsonString);
}catch (Exception e){
e.printStackTrace();
}
return resultStr;
} catch (Exception e) {
logger.error(e.getMessage());
}
return null;
}
/**
* https://openic.sf-express.com/open/api/docs/index#/apidoc
* 创建订单(企业)
*
*/
public String createorderEnterprise(Map<String, Object> param){
try {
param.put("dev_id",Integer.parseInt(devId));
String jsonString = formatAndSortMap(param,0);
String sign = generateOpenSign(jsonString,Integer.parseInt(devId),secretKey);
String resultStr = null;
try {
resultStr = HttpUtils.postJson("https://openic.sf-express.com/open/api/external/createorder4c?sign="+sign, jsonString);
}catch (Exception e){
e.printStackTrace();
}
return resultStr;
} catch (Exception e) {
logger.error(e.getMessage());
}
return null;
}
/**
* https://openic.sf-express.com/open/api/docs/index#/apidoc
* 预取消订单
*/
public String precancelorder(Map<String, Object> param){
try {
param.put("dev_id",Integer.parseInt(devId));
String jsonString = formatAndSortMap(param,0);
String sign = generateOpenSign(jsonString,Integer.parseInt(devId),secretKey);
String resultStr = null;
try {
resultStr = HttpUtils.postJson("https://openic.sf-express.com/open/api/external/precancelorder?sign="+sign, jsonString);
}catch (Exception e){
e.printStackTrace();
}
return resultStr;
} catch (Exception e) {
logger.error(e.getMessage());
}
return null;
}
/**
* https://openic.sf-express.com/open/api/docs/index#/apidoc
* 取消订单(店铺)
*/
public String cancelorder(Map<String, Object> param){
try {
param.put("dev_id",Integer.parseInt(devId));
String jsonString = formatAndSortMap(param,0);
String sign = generateOpenSign(jsonString,Integer.parseInt(devId),secretKey);
String resultStr = null;
try {
resultStr = HttpUtils.postJson("https://openic.sf-express.com/open/api/external/cancelorder?sign="+sign, jsonString);
}catch (Exception e){
e.printStackTrace();
}
return resultStr;
} catch (Exception e) {
logger.error(e.getMessage());
}
return null;
}
/**
* https://openic.sf-express.com/open/api/docs/index#/apidoc
* 获取配送员轨迹H5(店铺)
*/
public String riderviewv2(Map<String, Object> param){
try {
param.put("dev_id",Integer.parseInt(devId));
String jsonString = formatAndSortMap(param,0);
String sign = generateOpenSign(jsonString,Integer.parseInt(devId),secretKey);
String resultStr = null;
try {
resultStr = HttpUtils.postJson("https://openic.sf-express.com/open/api/external/riderviewv2?sign="+sign, jsonString);
}catch (Exception e){
e.printStackTrace();
}
return resultStr;
} catch (Exception e) {
logger.error(e.getMessage());
}
return null;
}
public static void main(String[] args) throws IOException {
Map<String, Object> param = new HashMap<>();
param.put("shop_id","3243xxx93");
param.put("dev_id",1691xxx52);
// param.put("shop_type",1);
param.put("user_lng","1xx.16427833749388");
param.put("user_lat","2xx.558482814127863");
param.put("user_address","广东省深圳市罗湖区中xxxxxx");
param.put("weight","20");
param.put("product_type",18);
// 转换为秒级时间戳
long timestampInMillis = System.currentTimeMillis();
long timestampInSeconds = timestampInMillis / 1000;
param.put("push_time",timestampInSeconds);
System.out.println(timestampInSeconds);
// param.put("total_price","");
// param.put("is_appoint","");
// param.put("appoint_type","");
// param.put("expect_time","");
// param.put("expect_pickup_time","");
// param.put("lbs_type","");
// param.put("is_insured","");
// param.put("is_person_direct","");
// param.put("vehicle","");
// param.put("four_wheeler_type","");
// param.put("declared_value","");
// param.put("gratuity_fee","");
// param.put("rider_pick_method","");
// param.put("return_flag","");
String jsonString = formatAndSortMap(param,0);
String sign = generateOpenSign(jsonString,169xxx52,"3c58cb1exxxxxx867");
System.out.println(sign);
}
/**
* 生成签名
* @param jsonString
* @param devId
* @param appKey
* @return
*
*/
public static String generateOpenSign(String jsonString, Integer devId, String appKey) throws IOException {
String sb = jsonString+"&" + devId + "&" + appKey;
MessageDigest md = null;
String ret = null;
try {
md = MessageDigest.getInstance("MD5");
byte[] md5 = md.digest(sb.toString().getBytes("utf-8"));
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < md5.length; offset++) {
i = md5[offset];
if (i < 0) {
i += 256;
}
if (i < 16) {
buf.append("0");
}
buf.append(Integer.toHexString(i));
}
ret = Base64.encodeBase64String(buf.toString().getBytes("utf-8"));
} catch (Exception e) {
throw new RuntimeException(e);
}
return ret;
}
// 将 Map 转换成指定格式的字符串并排序键值对
public static String formatAndSortMap(Map<String, Object> map, int indentLevel) {
// 将 Map 的键值对转换成 List
List<Map.Entry<String, Object>> entryList = new ArrayList<>(map.entrySet());
// 对 List 中的键值对按照键进行排序
Collections.sort(entryList, Comparator.comparing(Map.Entry::getKey));
// 构建格式化后的字符串
StringBuilder sb = new StringBuilder();
String indent = getIndent(indentLevel);
sb.append("{\n");
for (Map.Entry<String, Object> entry : entryList) {
sb.append(indent).append(" \"").append(entry.getKey()).append("\": ");
Object value = entry.getValue();
if (value instanceof Map) {
// 如果值是 Map,则递归处理
sb.append(formatAndSortMap((Map<String, Object>) value, indentLevel + 1));
} else if (value instanceof List) {
// 如果值是 List,则递归处理
sb.append(formatList((List<?>) value, indentLevel + 1));
} else if (value instanceof String) {
sb.append("\"").append(value).append("\"");
} else {
sb.append(value);
}
sb.append(",\n");
}
sb.deleteCharAt(sb.length() - 2); // 删除最后一个逗号
sb.append(indent).append("}");
return sb.toString();
}
// 将 List 转换成指定格式的字符串
private static String formatList(List<?> list, int indentLevel) {
StringBuilder sb = new StringBuilder();
String indent = getIndent(indentLevel);
sb.append("[\n");
for (Object value : list) {
sb.append(indent).append(" ");
if (value instanceof Map) {
// 如果值是 Map,则递归处理
sb.append(formatAndSortMap((Map<String, Object>) value, indentLevel + 1));
} else if (value instanceof String) {
sb.append("\"").append(value).append("\"");
} else {
sb.append(value);
}
sb.append(",\n");
}
sb.deleteCharAt(sb.length() - 2); // 删除最后一个逗号
sb.append(indent).append("]");
return sb.toString();
}
// 根据缩进级别生成缩进字符串
private static String getIndent(int indentLevel) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < indentLevel; i++) {
sb.append(" "); // 使用两个空格作为缩进
}
return sb.toString();
}
}
三、回调
官方回调接口文档
前面的工作其实还好,我在开发回调这里没有注意到他们是有文档的,后面才发现这里可以自助操作,我当时是下生产订单(当然,这样是要你们给真实的钱出去的),然后真实的骑手过来取货,然后联系他们的人员,把你下的这订单指派给这位骑手小哥,我这里还的感谢那位骑手小哥,他坐在我旁边配合我测试,配合我联调,非常谢谢他
联调的话,你要再后台配置你的回调URL
以上动作配置好了之后,就可以下面操作
package com.admin.business.interfaces.service.sfsamecity;
import cn.hutool.json.JSONObject;
import com.admin.frame.base.ConfigMapper;
import com.admin.util.BeanUtils;
import com.google.common.collect.ImmutableMap;
import com.itextpdf.text.log.Logger;
import com.itextpdf.text.log.LoggerFactory;
import org.apache.commons.lang.StringUtils;
import org.json.JSONException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import static javax.crypto.Cipher.SECRET_KEY;
@RestController
@RequestMapping("/sfsamecity")
public class SFSameCityBackController {
protected Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${sf.same.city.devId}")
private String devId;
@Value("${sf.same.city.secretKey}")
private String secretKey;
/**
* https://openic.sf-express.com/open/api/docs/index#/apidoc
* 配送状态更改回调
*
*
* shop_id string(64) 空 是 店铺ID
* sf_order_id string(64) 0 是 顺丰订单ID
* shop_order_id string(64) 0 是 商家订单ID
* url_index string 空 是 回调url前缀 rider_status
* operator_name string 空 是 配送员姓名
* operator_phone string 空 是 配送员电话
* rider_lng string 空 是 配送员位置经度
* rider_lat string 空 是 配送员位置纬度
* order_status int 空 是 订单状态 10-配送员接单/改派;12:配送员到店;15:配送员配送中
* status_desc string 空 是 状态描述 文案见上个字段的注释
* push_time int 空 是 状态变更时间
*
*/
@RequestMapping("/getSFCallbackOrderStatus")
public Map<String,Object> getSFCallbackOrderStatus(HttpServletRequest request, HttpServletResponse response) throws IOException, JSONException {
Map<String,Object> resMap = new HashMap<>();
System.out.println("----------------------------顺丰同城配送状态更改-----------------------------------------");
StringBuilder requestBody = new StringBuilder();
try (BufferedReader reader = request.getReader()) {
String line;
while ((line = reader.readLine()) != null) {
requestBody.append(line);
}
}
// 解析JSON数据
JSONObject json = new JSONObject(requestBody.toString());
// 从JSON对象中获取参数
String sign = request.getParameter("sign");
Integer order_status = json.getInt("order_status");
String sf_order_id = json.getStr("sf_order_id");
String sign2 = validateSignature(jsonObject.toString());
if (!sign2.equals(sign)) {
System.out.println("..................sign签名错误..................");
resMap.put("error_code",500);
resMap.put("error_msg","error sign签名错误");
return resMap;
}
try {
if(order_status==10){
//10-配送员接单
delivery.setSfSameCityStatusExpress(2);
}else if(order_status==12){
//12:配送员到店
delivery.setSfSameCityStatusExpress(3);
}else if(order_status==15){
//15:配送员配送中
resMap.put("error_code",0);
resMap.put("error_msg","success");
return resMap;
}
}catch (Exception e){
System.out.println("顺丰同城配送状态更改异常:");
e.printStackTrace();
}
resMap.put("error_code",500);
resMap.put("error_msg","error");
return resMap;
}
private String validateSignature(String jsonString) throws IOException {
return SFHapper.generateOpenSign(jsonString,Integer.parseInt(devId),secretKey);
}
}
四、上线
前面的开发,联调,测试阶段结束了,终于等到上线了,这个时候由于他们流程原因,需要审核一段时间,是邮箱审核,具体看他们审核进度,跟我们说是要等一个星期,如果很急的话,可以提前跟他们讲,把材料给他们,把事情讲清楚,然后提前审批,不然等开发完了,上线还得再等一星期
这个是目前我对接他们的现状,希望他们做的越来越好,规范化,标准化,越做越强大
如果小伙伴们有什么疑问,欢迎下面评论。欢迎指正。如还有什么不懂的加我 QQ:517861659
如果没有及时回复,可以点我先问问AI机器人编辑https://chatgpt.byabstudio.com/login?code=202307011314