顺丰同城急送API的坑(附源码)

一、背景

最近公司让我对接顺丰同城急送的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 的键值对转换成 ListList<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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/825149.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

爬虫 | 基于 requests 实现加密 POST 请求发送与身份验证

Hi&#xff0c;大家好&#xff0c;我是半亩花海。本项目旨在实现一个简单的 Python 脚本&#xff0c;用于向指定的 URL 发送 POST 请求&#xff0c;并通过特定的加密算法生成请求头中的签名信息。这个脚本的背后是与某个特定的网络服务交互&#xff0c;发送特定格式的 JSON 数据…

LeetCode in Python 1338. Reduce Array Size to The Half (数组大小减半)

数组大小减半思路简单&#xff0c;主要是熟悉python中collections.Counter的用法&#xff0c;采用贪心策略即可。 示例&#xff1a; 图1 数组大小减半输入输出示例 代码&#xff1a; class Solution:def minSetSize(self, arr):count Counter(arr)n, ans 0, 0for i, valu…

北大字节联合发布视觉自动回归建模(VAR):通过下一代预测生成可扩展的图像

北大和字节发布一个新的图像生成框架VAR。首次使GPT风格的AR模型在图像生成上超越了Diffusion transformer。 同时展现出了与大语言模型观察到的类似Scaling laws的规律。在ImageNet 256x256基准上,VAR将FID从18.65大幅提升到1.80,IS从80.4提升到356.4,推理速度提高了20倍。 相…

关于Jetson空间不足的解决问题(sd卡挂载和conda更改环境安装路径)

文章目录 问题描述挂载sd卡到指定目录查看conda路径更改环境路径指定路径安装conda虚拟环境 问题描述 因为在做毕设的时候&#xff0c;用到了Jetson&#xff0c;发现这个空间太小了&#xff0c;如果下conda的包根本不够用&#xff0c;所以就想挂载sd卡&#xff0c;然后把环境安…

国外GIS软件排名简介<30个>

简介 国外gisgeography网站进行了一次GIS软件排名&#xff0c;通过分析、制图、编辑等因素进行测试&#xff0c;具体规则如下&#xff1a; 分析&#xff1a;矢量/栅格工具、时态、地统计、网络分析和脚本。 制图&#xff1a;地图类型、坐标系、地图布局/元素、标注/注记、3D …

C#到底属于编译型语言还是解释型语言?

C#是一种编译型语言&#xff0c;也称为静态类型语言&#xff0c;这意味着C#代码在运行之前需要经过编译器的编译处理&#xff0c;并生成一个可执行的本地代码文件&#xff08;通常是.exe或.dll文件&#xff09;。相反&#xff0c;解释型语言将代码转换为低级代码后直接执行&…

计算机视觉——手机目标检测数据集

这是一个手机目标检测的数据集&#xff0c;数据集的标注工具是labelimg,数据格式是voc格式&#xff0c;要训练yolo模型的话&#xff0c;可以使用脚本改成txt格式&#xff0c;数据集标注了手机&#xff0c;标签名&#xff1a;telephone,数据集总共有1960张&#xff0c;有一部分是…

软件无线电安全之GNU Radio基础 -上

GNU Radio介绍 GNU Radio是一款开源的软件工具集&#xff0c;专注于软件定义无线电&#xff08;SDR&#xff09;系统的设计和实现。该工具集支持多种SDR硬件平台&#xff0c;包括USRP、HackRF One和RTL-SDR等。用户可以通过GNU Radio Companion构建流程图&#xff0c;使用不同…

BackTrader 中文文档(二十七)

原文&#xff1a;www.backtrader.com/ 数据 - 多个时间框架 原文&#xff1a;www.backtrader.com/blog/posts/2015-08-24-data-multitimeframe/data-multitimeframe/ 有时&#xff0c;使用不同的时间框架进行投资决策&#xff1a; 周线用于评估趋势 每日执行进入 或者 5 分钟…

软考132-上午题-【软件工程】-沟通路径

一、定义 1-1、沟通路径1 沟通路径 1-2、沟通路径2 沟通路径 n-1 二、真题 真题1&#xff1a; 真题2&#xff1a; 真题3&#xff1a;

发布 Chrome/Edge浏览器extension扩展到应用商店

Chrom Extension发布流程 创建和发布自定义 Chrome 应用和扩展程序&#xff1a;https://support.google.com/chrome/a/answer/2714278?hlzh-Hans 在 Chrome 应用商店中发布&#xff1a;https://developer.chrome.com/docs/webstore/publish?hlzh-cn 注册开发者帐号&#…

图解CPU的实模式与保护模式

哈喽&#xff0c;大家好&#xff0c;我是呼噜噜&#xff0c;好久没有更新old linux了&#xff0c;在上一篇文章Linux0.12内核源码解读(7)-陷阱门初始化中&#xff0c;我们简要地提及了中断&#xff0c;但是中断机制在计算机世界里非常重要&#xff0c;处处都离不开中断&#xf…

Element——组件

element官网 https://element.eleme.cn/#/zh-CN/component/layout vscode格式化快捷键&#xff1a;shiftaltf table表格 <template><el-table:data"tableData"style"width: 100%"><el-table-columnprop"date"label"日期…

Git使用总结(不断更新中)

branch 本地分支操作 删除本地分支 git branch -d <local-branch-name>远端分支操作 从远端分支创建本地分支 git checkout -b <local-branch-name> origin/<remote-branch-name>git ignore 如果工程的代码文件中有不希望上传到远端的文件&#xff0c;…

排列特征重要性(Permutation Feature Importance)

5个条件判断一件事情是否发生&#xff0c;每个条件可能性只有2种&#xff08;发生或者不发生&#xff09;&#xff0c;计算每个条件对这件事情发生的影响力。排列特征重要性模型的程序。 例一 在机器学习领域&#xff0c;排列特征重要性&#xff08;Permutation Feature Impor…

【honggfuzz学习笔记】honggfuzz的基本特性

本文架构 1.动机2.honggfuzz的基本概念官网描述解读 3. honggfuzz的反馈驱动(Feedback-Driven)软件驱动反馈&#xff08;software-based coverage-guided fuzzing&#xff09;代码覆盖率代码覆盖率的计量单位 代码覆盖率的统计方式 硬件驱动反馈&#xff08; hardware-based co…

CTFHUB RCE作业

题目地址&#xff1a;CTFHub 完成情况如图&#xff1a; 知识点&#xff1a; preg_match_all 函数 正则匹配函数 int preg_match_all ( string $pattern , string $subject [, array &$matches [, int $flags PREG_PATTERN_ORDER [, int $offset 0 ]]] )搜索 subject 中…

【Python小游戏】植物大战僵尸的实现与源码分享

文章目录 Python版植物大战僵尸环境要求方法源码分享初始化页面&#xff08;部分&#xff09;地图搭建&#xff08;部分&#xff09;定义植物类 &#xff08;部分&#xff09;定义僵尸类&#xff08;部分&#xff09;游戏运行入口 游戏源码获取 Python版植物大战僵尸 已有的植…

【Proteus】51单片机对直流电机的控制

直流电机&#xff1a;输出或输入为直流电能的旋转电机。能实现直流电能和机械能互相转换的电机。把它作电动机运行时是直流电动机&#xff0c;电能转换为机械能&#xff1b;作发电机运行时是直流发电机&#xff0c;机 械能转换为电能。 直流电机的控制&#xff1a; 1、方向控制…

动态多目标测试函数DF1-DF14,FDA1-FDA5,SDP1-SDP12的TurePOF(MATLAB代码)

动态多目标测试函数FDA1、FDA2、FDA3、FDA4、FDA5的turePOF&#xff08;MATLAB代码&#xff09; 动态多目标测试函数DF1-DF14的turePOF变化&#xff08;提供MATLAB代码&#xff09; 动态多目标测试函数SDP1-SDP12的TurePOF变化视频&#xff08;含MATLAB代码及参考文献&#xff…