最近对接了拼多多项目和菜鸟物流项目,对接过程中遇到了不少问题,不过现在都解决了。现在把遇到的问题和解决方案分享出来,希望帮到一些需要的人。
遇到最大问题就是签名问题,其实可以归结为编码问题。就是两个语言的不同。他们是Java语言,我们是C#语言。一方面是他们文档写得不够详细,还有一些小错误,误导了我们。所以花费不少时间。
现在开始吧!
菜鸟物流他们提供的签名demo代码
public static void main(String[] args) {
String appSecret = "test3as9dj1kj08488c49b69ead19339"; //替换你的appSecret
//logistics_interface参数
String logisticsInterfaceStr = "{\"cpCode\":\"YUNDA\",\"mailNo\":\"123456\",\"pushType\":\"1\",\"receiverAddress\":{\"address\":\"test\"},\"receiverTel\":\"18888888888\",\"subCallBackParam\":\"test\"}";
JSONObject json = JSON.parseObject(logisticsInterfaceStr);
String sortStr = getAloneKeys(json);
//排序后放入map
Map<String, String> map = new HashMap<String, String>();
map.put("logistics_interface", sortStr);
map.put("nonce", "1686210870");
String sign = getSign(appSecret, map);
System.out.println("sign:" + sign);//sign:c8e33b8fc5767727e57218e6c24afefc
}
他们的签名步骤
S1. 秘钥appSecret(由接口方提供)
S2. 对logistics_interface和nonce按照字段名小写从小到大排序
S3. 构造URL 键值对的形式,拼接成字符串A
S4. 把密钥appSecret加到字符串A前,例:appSecret+A
S5. 再用md5加密,编码utf8
这里他们提供了签名密钥,签名的业务数据和签名结果,但是他们没有提供最后的签名字符串。
就是这个
var resultStr = appSecret + tempStr;
待签名的最终字符串:resultStr
test3as9dj1kj08488c49b69ead19339logistics_interface=%7B%22cpCode%22%3A%22YUNDA%22%2C%22mailNo%22%3A%22123456%22%2C%22pushType%22%3A%221%22%2C%22receiverAddress%22%3A%7B%22address%22%3A%22test%22%7D%2C%22receiverTel%22%3A%2218888888888%22%2C%22subCallBackParam%22%3A%22test%22%7D&nonce=1686210870
c#有关签名方法 最开始我是使用
HttpUtility.UrlEncode
结果死活与他们提供的签名不一致,那就是有问题。找了很久没有解决,后面问了同事,他说要用这个方法
WebUtility.UrlEncode
换了之后果然与他们提供签名一致了。
但是我想找出他们两个的不同,于是我用两个方法生成对比,发内容是一样的,但是有些字母大小写不一致,如下:
%7b%22cpCode%22%3a%22YUNDA%22%2c%22mailNo%22%3a%22123456%22%2c%22pushType%22%3a%221%22%2c%22receiverAddress%22%3a%7b%22address%22%3a%22test%22%7d%2c%22receiverTel%22%3a%2218888888888%22%2c%22subCallBackParam%22%3a%22test%22%7d
%7B%22cpCode%22%3A%22YUNDA%22%2C%22mailNo%22%3A%22123456%22%2C%22pushType%22%3A%221%22%2C%22receiverAddress%22%3A%7B%22address%22%3A%22test%22%7D%2C%22receiverTel%22%3A%2218888888888%22%2C%22subCallBackParam%22%3A%22test%22%7D
所以得出结论:他们是有一些区别的
于是又问gpt平台,有什么办法把他们弄成一致呢? 平台给出了我答案——就是下面这个自定义编码方法。
在自定义的 CustomUrlEncode 方法中,我们遍历输入字符串的每个字符,并检查字符是否为保留字符(字母、数字、连字符、下划线、句点、波浪号)。如果是保留字符,则直接追加到结果字符串中;否则,将字符转换为字节数组,并使用 % 后跟两个十六进制数字表示该字节,最后将其追加到结果字符串中。
通过以上代码,您可以确保自定义的 CustomUrlEncode 方法生成的结果与 System.Net.WebUtility.UrlEncode 完全一致。
自定义编码方法
static string CustomUrlEncode(string input)
{
var sb = new StringBuilder();
foreach (char c in input)
{
if (IsUnreserved(c))
{
sb.Append(c);
}
else
{
byte[] bytes = Encoding.UTF8.GetBytes(c.ToString());
foreach (byte b in bytes)
{
sb.Append('%');
sb.Append(b.ToString("X2"));
}
}
}
return sb.ToString();
}
static bool IsUnreserved(char c)
{
return char.IsLetterOrDigit(c) || c == '-' || c == '_' || c == '.' || c == '~';
}
public string GetCommonSign(SortedDictionary<string, object> dicResult)
{
dicResult.Remove("sign");
// var xxx = "9b1aed5de48c9de4572246f63d6d524263ee6d3f";
var appSecret = "test3as9dj1kj08488c49b69ead19339";
StringBuilder sb = new StringBuilder();
foreach (var item in dicResult)
{
if (item.Value == null)
{
}
else
{
if (item.Value is string)
{
// sb.AppendFormat("{0}={1}&", item.Key.ToLower(), System.Net.WebUtility.UrlEncode(item.Value.ToString()));
sb.AppendFormat("{0}={1}&", item.Key.ToLower(), CustomUrlEncode(item.Value.ToString()));
}
else
{
sb.AppendFormat("{0}={1}&", item.Key.ToLower(),CustomUrlEncode(JsonConvert.SerializeObject(item.Value)));
}
}
}
string tempStr=sb.ToString().TrimEnd('&');
var resultStr = appSecret + tempStr;
//大写
return MD5(resultStr.Trim()).ToUpper();
}
测试得到一致的结果。
上面的方法没法处理中文和空白字符,修正处理代码
static string CustomUrlEncode(string input)
{
var temp= input.Replace(" ", "+");
StringBuilder sb = new StringBuilder();
byte[] bytes = Encoding.UTF8.GetBytes(temp);
foreach (byte b in bytes)
{
char c = (char)b;
// 判断是否为保留字符,并且是字母或数字
if ((b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') || (b >= '0' && b <= '9') ||
b == '-' || b == '_' || b == '+' || b == '.' || b == '~')
{
sb.Append(c);
}
else
{
sb.Append(Uri.HexEscape(c));
}
}
return sb.ToString();
}
因此,我们可以使用两种方法来解决编码问题了。
其他辅助代码
public static string MD5(string str)
{
using (var md5 = System.Security.Cryptography.MD5.Create())
{
var result = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
var strResult = BitConverter.ToString(result);
return strResult.Replace("-", "");
}
}
/// <summary>
/// 参数转换
/// </summary>
/// <param name="jsonString"></param>
/// <returns></returns>
public SortedDictionary<string, object> ConvertJsonToSortedDictionary(string jsonString)
{
var jsonObject = JsonConvert.DeserializeObject<SortedDictionary<string, object>>(jsonString);
var sortedDictionary = new SortedDictionary<string, object>(jsonObject);
return sortedDictionary;
}
签名另一种写法
public string GetSign(SortedDictionary<string, object> dicResult)
{
dicResult.Remove("sign");
StringBuilder sb = new StringBuilder();
foreach (var item in dicResult)
{
if (item.Value == null)
{
}
else
{
sb.AppendFormat("{0}={1}", item.Key.ToLower(), item.Value);
sb.Append("&");
}
}
var resultStr = appSecret + sb.ToString().Substring(0, sb.ToString().Length - 1);
return MD5(resultStr.Trim());
}
测试json格式
{
"logistics_interface": "{\"cpCode\":\"YUNDA\",\"mailNo\":\"123456\",\"pushType\":\"1\",\"receiverAddress\":{\"address\":\"test\"},\"receiverTel\":\"18888888888\",\"subCallBackParam\":\"test\"}",
"nonce": "1686210870",
"sign": "sign"
}
实体类
public partial class TestModel555
{
public string logistics_interface { get; set; }
public string nonce { get; set; }
public string sign { get; set; }
}
测试方法
[HttpPost]
public ActionResult CaiNiaoTest(TestModel555 testModel2)
{
string newjson = JsonConvert.SerializeObject(testModel2);
SortedDictionary<string, object> dictionary = caiNiaoServeoce.ConvertJsonToSortedDictionary(newjson);
string sign2 = caiNiaoServeoce.GetCommonSign(dictionary);
return Content("签名==" + sign2);
}
测试结果 签名==C8E33B8FC5767727E57218E6C24AFEFC
签名完善判断
public string GetCommonSignZTO(SortedDictionary<string, object> dicResult,out SortedDictionary<string, string> dictionary2)
{
dicResult.Remove("sign");
// var xxx = "9b1aed5de48c9de4572246f63d6d524263ee6d3f";
dictionary2 =new SortedDictionary<string, string>();
var appSecret = "xxxxxxxxxx";
StringBuilder sb = new StringBuilder();
foreach (var item in dicResult)
{
if (item.Value == null)
{
}
else
{
if (item.Value.GetType() == typeof(string) || item.Value.GetType() == typeof(double) || item.Value.GetType() == typeof(bool) || item.Value.GetType() == typeof(float)|| item.Value.GetType() == typeof(Int64))
{
sb.AppendFormat("{0}={1}&", item.Key.ToLower(), System.Net.WebUtility.UrlEncode(item.Value.ToString()));//
// sb.AppendFormat("{0}={1}&", item.Key.ToLower(), CustomUrlEncode(item.Value.ToString()));
dictionary2.Add(item.Key.ToLower(), item.Value.ToString());
}
else
{ // JsonConvert.SerializeObject(test3)
sb.AppendFormat("{0}={1}&", item.Key.ToLower(), System.Net.WebUtility.UrlEncode(JsonConvert.SerializeObject(item.Value)));
dictionary2.Add(item.Key.ToLower(), JsonConvert.SerializeObject(item.Value));
}
}
}
string tempStr = sb.ToString().TrimEnd('&');
var resultStr = appSecret + tempStr;
//大写
return MD5(resultStr.Trim()).ToLower();
}
public string GetCommonSignZTO(SortedDictionary<string, object> dicResult,out SortedDictionary<string, string> dictionary2)
{
dicResult.Remove("sign");
// var xxx = "9b1aed5de48c9de4572246f63d6d524263ee6d3f";
dictionary2 =new SortedDictionary<string, string>();
var appSecret = "xxxxxxxxxxxxxx";
StringBuilder sb = new StringBuilder();
foreach (var item in dicResult)
{
if (item.Value == null)
{
}
else
{
if (item.Value is string )
{
sb.AppendFormat("{0}={1}&", item.Key.ToLower(), System.Net.WebUtility.UrlEncode(item.Value.ToString()));//
// sb.AppendFormat("{0}={1}&", item.Key.ToLower(), CustomUrlEncode(item.Value.ToString()));
dictionary2.Add(item.Key, item.Value.ToString());
}
else
{
sb.AppendFormat("{0}={1}&", item.Key.ToLower(), System.Net.WebUtility.UrlEncode(JsonConvert.SerializeObject(item.Value)));
if (item.Key == "payType")
{
dictionary2.Add(item.Key, JsonConvert.SerializeObject(item.Value));
}
else
{
dictionary2.Add(item.Key.ToLower(), JsonConvert.SerializeObject(item.Value));
}
}
}
}
string tempStr = sb.ToString().TrimEnd('&');
var resultStr = appSecret + tempStr;
//大写
return MD5(resultStr.Trim()).ToLower();
}
其他方法
CaiNiaoService caiNiaoServeoce = new CaiNiaoService();//
string body = JsonConvert.SerializeObject(order);
SortedDictionary<string, object> dictionary = caiNiaoServeoce.ConvertJsonToSortedDictionary(body);
SortedDictionary<string, string> dictionary2 =null;
order.sign = caiNiaoServeoce.GetCommonSignZTO(dictionary, out dictionary2);
dictionary2.Add("sign", order.sign);
string body2 = "";
int i = 1;
foreach (var item in dictionary2)
{
if (i == dictionary2.Count)
{
body2 += item.Key + "=" + item.Value;
}
else
{
body2 += item.Key + "=" + item.Value + "&";
}
i++;
}
string responseBody = HttpHelperService.SendHttpRequest2(postUrl, body2, "application/x-www-form-urlencoded", headers, 3000);
到此,分享完毕!
参考:菜鸟物流接口文档 www.yuque.com/u34567120/w…