顺丰同城急送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/web/574.shtml

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

相关文章

期权小知识科普

期权的交易时间 上交所期权合约的交易时间为每个交易日9:15至9:25、9:30至11:30、13&#xff1a;00至15:00。 其中&#xff0c;9:15至9:25为开盘集合竞价时间&#xff0c;14:57-15:00为收盘集合竞价时间&#xff0c;其余时段为连续竞价时间&#xff0c;交易所规则另有规定的除…

Pytorch-自动微分模块

&#x1f947;接下来我们进入到Pytorch的自动微分模块torch.autograd~ 自动微分模块是PyTorch中用于实现张量自动求导的模块。PyTorch通过torch.autograd模块提供了自动微分的功能&#xff0c;这对于深度学习和优化问题至关重要&#xff0c;因为它可以自动计算梯度&#xff0c…

VUE-列表

VUE-列表 列表功能 如下例子 列表展示 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv&qu…

CorelDRAW Graphics Suite2024最新永久免费版功能强大的图形设计软件

CorelDRAW Graphics Suite是一款功能强大的图形设计软件套件&#xff0c;它提供了丰富的绘图工具、编辑功能和设计资源&#xff0c;适用于从专业设计师到普通用户的广泛群体。以下是CorelDRAW Graphics Suite的一些主要功能特点&#xff1a; 矢量图形设计&#xff1a;CorelDRAW…

Jenkins 流水线多阶段构建

Jenkins流水线配置遇到 无法识别的。需要使用 自定义环境 项。 比如官网的在流水线中使用Docker Started by remote host 172.17.0.1 Obtained Jenkinsfile from git http://10.99.20.51:8082/root/java-devops-demo.git org.codehaus.groovy.control.MultipleCompilationErro…

智慧化赋能园区新未来:探讨智慧园区如何以科技创新为引擎,推动产业转型升级

随着科技的飞速发展&#xff0c;智慧化已成为推动园区产业升级和转型的重要引擎。智慧园区&#xff0c;以其高效、便捷、智能的特性&#xff0c;正逐步改变传统的产业园区模式&#xff0c;为产业发展注入新的活力。本文旨在探讨智慧园区如何以科技创新为引擎&#xff0c;推动产…

制作适用于openstack平台的win10镜像

1. 安装准备 从MSDN下载windows 10的镜像虚拟机开启CPU虚拟化的功能。从Fedora 网站下载已签名的 VirtIO 驱动程序 ISO 。 创建15 GB 的 qcow2 镜像&#xff1a;qemu-img create -f qcow2 win10.qcow2 15G 安装必要的软件 yum install qemu-kvm qemu-img virt-manager libvir…

GaN HEMT中短沟道效应的建模

来源&#xff1a;Modeling of Short-Channel Effects in GaN HEMTs&#xff08;TED 20年&#xff09; 摘要 在本文中&#xff0c;我们提出了一种用于估算GaN高电子迁移率晶体管&#xff08;HEMT&#xff09;器件中短沟道效应&#xff08;SCEs&#xff09;的显式和解析的基于电…

【创建型模式】单例模式

一、单例模式概述 单例模式的定义&#xff1a;又叫单件模式&#xff0c;确保一个类只有一个实例&#xff0c;并提供一个全局访问点。&#xff08;对象创建型&#xff09; 要点&#xff1a; 1.某个类只能有一个实例&#xff1b;2.必须自行创建这个实例&#xff1b;3.必须自行向整…

固体矿产资源储量分类GBT17766-2020

1999分类标准采用三轴体系划分资源量与处理&#xff0c;表达复杂、经济意义划分过细、实用性不强 虽然不再采用”三轴“表达方式&#xff0c;但依然考虑地质可靠程度、经济意义、可行性评价 矿产资源勘查&#xff1a;通常依靠地球科学知识&#xff0c;运用地质填图&#xff0…

python语言零基础入门——变量与简单数据类型

目录 一、变量 1.创建变量 2.变量的修改 3.变量的命名 &#xff08;1&#xff09;常量 &#xff08;2&#xff09;标识符 &#xff08;3&#xff09;关键字 &#xff08;4&#xff09;命名规则 二、简单数据类型 1.变量的数据类型 2.数据类型 3.整型&#xff08;In…

软考-系统集成项目管理中级--项目人力资源管理(输入输出很重要!!!本章包含案例题,着重复习)

本章历年考题分值统计 本章重点常考知识点汇总清单(掌握部分可直接理解记忆) 1、人力资源管理的过程:(掌握) (1)项目人力资源计划编制:确定与识别项目中的角色、分配项目职责和汇报关系&#xff0c;并记录下来形成书面文件&#xff0c;其中也包括项目人员配备管理计划。…

C语言进阶课程学习记录-第39课 - 程序中的3个基本数据区

C语言进阶课程学习记录-第39课 - 程序中的3个基本数据区 栈实验-栈空间释放后数据无效堆静态存储区实验-静态变量和全局变量小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课程PPT&#xff0c;仅用于个人学习记录 栈 实验-栈空间释放后数据…

吴恩达机器学习笔记:第 8 周-13 聚类(Clustering)13.1-13.2

目录 第 8 周 13、 聚类(Clustering)13.1 无监督学习&#xff1a;简介 第 8 周 13、 聚类(Clustering) 13.1 无监督学习&#xff1a;简介 在这个视频中&#xff0c;我将开始介绍聚类算法。这将是一个激动人心的时刻&#xff0c;因为这是我们学习的第一个非监督学习算法。我们…

Day3 权限管理

Day3 权限管理 这里会总结构建项目过程中遇到的问题&#xff0c;以及一些个人思考&#xff01;&#xff01; 学习方法&#xff1a; 1 github源码 文档 官网 2 内容复现 &#xff0c;实际操作 项目源码同步更新到github 欢迎大家star~ 后期会更新并上传前端项目 创建管理员…

Windows的Tensorrt的安装

Tensorrt的下载 确定自己的CUDA版本,匹配的去下载Tensorrt。 Tensorrt的下载 下载完成之后,直接解压到文件夹即可。 环境变量配置 最重要的一部就是环境变量的配置。 官方的安装指导文件给出了两种方法: (1)要么直接将/lib 添加到环境变量 PATH 中 (诶,windows没有LD_L…

[AI Meta Llama-3] 最强开源大模型Llama 3发布!

最强开源大模型Llama 3发布&#xff01;我们看下重点&#xff1a; 今天&#xff0c;我们介绍Meta Llama 3&#xff0c;这是我们最先进的开源大型语言模型的下一代。Llama 3模型很快将在AWS、Databricks、Google Cloud、Hugging Face、Kaggle、IBM WatsonX、Microsoft Azure、N…

异步 IO 机制 io_uring

一、io_uring 原理 如何解决频繁 copy 的问题 → mmap 内存映射解决。 submit queue 中的节点和 complete queue 中的节点共用一块内存,而不是把 submit queue 中的节点 copy 到 complete queue 中。如何做到线程安全 → 无锁环形队列解决。二、io_uring 使用 内核为 io_uring…

Pytorch实用教程:nn.CrossEntropyLoss()的用法

在 PyTorch 中&#xff0c;nn.CrossEntropyLoss() 是一个非常常用且功能强大的损失函数&#xff0c;特别适合用于多类分类问题。这个损失函数结合了 nn.LogSoftmax() 和 nn.NLLLoss() (Negative Log Likelihood Loss) 两个操作&#xff0c;从而在一个模块中提供完整的交叉熵损失…

[qiankun]: Target container with #container not existed while childOne loading!

主应用container容器不存在导致无法挂载子应用 解决&#xff1a;不要将<div id"container"></div>放在Router标签内&#xff0c;跟Router同级即可