接口签名
约 2219 字大约 7 分钟
2025-12-04
回收开放平台渠道接口请求身份验证方式/本算法只支持 GET与POST方式提交,且POST只支持JSON编码
- 请求头中含有以下key-value pairs
- Whaleyes-Appkey: myj分配的appkey
- Whaleyes-Sign: 采用sha1算法签名
- Whaleyes-Nonce: 只可使用一次的随机值
- Whaleyes-Timestamp: 以
毫秒计的时间戳
签名算法
按timestamp + nonce + appkey + appSecret+data的顺序将所有参数拼接.
注意此处的"+"仅是方便文档编写与阅读,不参与实际运算data的构成:
2.1 如果是GET请求则将所有的值为非空 key-value对拼接: eg: data===> key1=value1&key2=value2...&keyN=valueN 注意此处的"="与"&"仅是方便文档编写与阅读,不参与实际运算
2.2 如果是POST请求,则将请求体marshal后拼接
将拼接后的全部内容按字母升序排序
将3步结果中两端空白符号剪除
将4步的结果使用SHA1算法签名
将5步结果转化成小写形式
案列
原始数据 :
appkey = d5d47248-b073-4940-a413-1ff34f1c1742
appsecret = 45a756ce-84e3-42d9-8735-2bd07b557742
timespan = 1609817584159
nonce = bf0a1ac5925f4f4c800f5c52352cc132post 请求 data 示例
data = {"pickupEndTime":"2020-12-24 16:45","pickupRemark":";图书订单;","pickupStartTime":"2020-12-24 15:45","recycleType":0,"sendCity":"杭州市","sendCounty":"江干区","sendDetail":"哈哈哈哈哈哈哈哈哈","sendName":"无言","sendPhone":"18771562716","sendProvince":"浙江省"}sha1及小写化后的签名串:
a7eed54faabd426ab6848d295057fe720e2c27f1
get 请求 data 示例
https://www.xxxxxxxx.com/OpenPlatform/GetIsbnInfoToOpenPlatform?isbnList=9787539981680,9787040494792,9787302301080python 示例
import io
import hashlib
def get_sorted_string(app_key, app_secret, timespan, nonce, data):
buf = io.StringIO()
buf.write(timespan)
buf.write(nonce)
buf.write(app_key)
buf.write(app_secret)
if isinstance(data, bytes):
buf.write(data.decode())
elif isinstance(data, str):
buf.write(data)
elif isinstance(data, dict):
for k, v in data.items():
if v == "":
continue
buf.write(k)
buf.write(v)
raw = buf.getvalue()
sorted_string = ''.join(sorted(raw))
return sorted_string
def get_sha1_hash(data):
sha1 = hashlib.sha1()
sha1.update(data.encode('utf-8'))
return sha1.hexdigest()
# 示例调用
app_key = "d5d47248-b073-4940-a413-1ff34f1c1742"
app_secret = "45a756ce-84e3-42d9-8735-2bd07b557742"
# 以毫秒计的时间戳
timespan = "1609817584159"
nonce = "bf0a1ac5925f4f4c800f5c52352cc132"
# post 请求中的 data
post_data = '{"pickupEndTime":"2020-12-24 16:45","pickupRemark":";图书订单;","pickupStartTime":"2020-12-24 15:45","recycleType":0,"sendCity":"杭州市","sendCounty":"江干区","sendDetail":"哈哈哈哈哈哈哈哈哈","sendName":"无言","sendPhone":"18771562716","sendProvince":"浙江省"}'
sorted_string = get_sorted_string(app_key, app_secret, timespan, nonce, post_data)
print(sorted_string)
# sorted_string.strip() 将结果中两端空白符号剪除
sign_str = get_sha1_hash(sorted_string.strip())
print(sign_str.lower())
print('----------------------分割线----------------------')
timespan = "1722954781840"
# get 请求中的 data
# https://www.xxxxxxxx.com/OpenPlatform/GetIsbnInfoToOpenPlatform?isbnList=9787539981680,9787040494792,9787302301080
get_data = {
"isbnList": "9787539981680,9787040494792,9787302301080"
}
sorted_string = get_sorted_string(app_key, app_secret, timespan, nonce, get_data)
print(sorted_string)
sign_str = get_sha1_hash(sorted_string.strip())
print(sign_str.lower())java 示例
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
public class Main {
public static String getSortedString(String appKey, String appSecret, String timespan, String nonce, Object data) {
StringBuilder buf = new StringBuilder();
buf.append(timespan);
buf.append(nonce);
buf.append(appKey);
buf.append(appSecret);
if (data instanceof byte[]) {
byte[] bytes = (byte[]) data;
buf.append(new String(bytes, StandardCharsets.UTF_8));
} else if (data instanceof String) {
String str = (String) data;
buf.append(str);
} else if (data instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, String> dict = (Map<String, String>) data;
for (Map.Entry<String, String> entry : dict.entrySet()) {
if ("".equals(entry.getValue())) {
continue;
}
buf.append(entry.getKey());
buf.append(entry.getValue());
}
}
String raw = buf.toString();
char[] chars = raw.toCharArray();
Arrays.sort(chars);
String sortedString = new String(chars);
return sortedString;
}
public static String getSha1Hash(String data) {
try {
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
sha1.update(data.getBytes(StandardCharsets.UTF_8));
byte[] hashBytes = sha1.digest();
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-1 algorithm not found", e);
}
}
public static void main(String[] args) {
// 示例调用
String appKey = "d5d47248-b073-4940-a413-1ff34f1c1742";
String appSecret = "45a756ce-84e3-42d9-8735-2bd07b557742";
// 以毫秒计的时间戳
String timespan = "1609817584159";
String nonce = "bf0a1ac5925f4f4c800f5c52352cc132";
// post 请求中的 data
String postData = "{\"pickupEndTime\":\"2020-12-24 16:45\",\"pickupRemark\":\";图书订单;\",\"pickupStartTime\":\"2020-12-24 15:45\",\"recycleType\":0,\"sendCity\":\"杭州市\",\"sendCounty\":\"江干区\",\"sendDetail\":\"哈哈哈哈哈哈哈哈哈\",\"sendName\":\"无言\",\"sendPhone\":\"18771562716\",\"sendProvince\":\"浙江省\"}";
String sortedString = getSortedString(appKey, appSecret, timespan, nonce, postData);
System.out.println(sortedString);
// sortedString.trim() 将结果中两端空白符号剪除
String signStr = getSha1Hash(sortedString.trim());
System.out.println(signStr.toLowerCase());
System.out.println("----------------------分割线----------------------");
timespan = "1722954781840";
// get 请求中的 data
// https://www.xxxxxxxx.com/OpenPlatform/GetIsbnInfoToOpenPlatform?isbnList=9787539981680,9787040494792,9787302301080
Map<String, String> getData = new HashMap<>();
getData.put("isbnList", "9787539981680,9787040494792,9787302301080");
sortedString = getSortedString(appKey, appSecret, timespan, nonce, getData);
System.out.println(sortedString);
signStr = getSha1Hash(sortedString.trim());
System.out.println(signStr.toLowerCase());
}
}c# 示例
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
class Program
{
static string GetSortedString(string appKey, string appSecret, string timespan, string nonce, object data)
{
var buffer = new StringBuilder();
buffer.Append(timespan);
buffer.Append(nonce);
buffer.Append(appKey);
buffer.Append(appSecret);
if (data is byte[] bytes)
{
buffer.Append(Encoding.UTF8.GetString(bytes));
}
else if (data is string str)
{
buffer.Append(str);
}
else if (data is Dictionary<string, object> dict)
{
foreach (var kvp in dict)
{
if (string.IsNullOrEmpty(kvp.Value?.ToString()))
continue;
buffer.Append(kvp.Key);
buffer.Append(kvp.Value);
}
}
string raw = buffer.ToString();
return string.Join("", raw.OrderBy(c => c));
}
static string GetSha1Hash(string data)
{
using (var sha1 = SHA1.Create())
{
byte[] hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(data));
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
static void Main()
{
string appKey = "d5d47248-b073-4940-a413-1ff34f1c1742";
string appSecret = "45a756ce-84e3-42d9-8735-2bd07b557742";
// 以毫秒计的时间戳
string timespan = "1609817584159";
string nonce = "bf0a1ac5925f4f4c800f5c52352cc132";
// post 请求中的 data
string postData = "{\"pickupEndTime\":\"2020-12-24 16:45\",\"pickupRemark\":\";图书订单;\",\"pickupStartTime\":\"2020-12-24 15:45\",\"recycleType\":0,\"sendCity\":\"杭州市\",\"sendCounty\":\"江干区\",\"sendDetail\":\"哈哈哈哈哈哈哈哈哈\",\"sendName\":\"无言\",\"sendPhone\":\"18771562716\",\"sendProvince\":\"浙江省\"}";
string sortedString = GetSortedString(appKey, appSecret, timespan, nonce, postData);
Console.WriteLine(sortedString);
// sortedString.Trim() 将结果中两端空白符号剪除
string signStr = GetSha1Hash(sortedString.Trim());
Console.WriteLine(signStr);
Console.WriteLine("----------------------分割线----------------------");
timespan = "1722954781840";
// get 请求中的 data
// https://www.xxxxxxxx.com/OpenPlatform/GetIsbnInfoToOpenPlatform?isbnList=9787539981680,9787040494792,9787302301080
var getData = new Dictionary<string, object>
{
{ "isbnList", "9787539981680,9787040494792,9787302301080" }
};
sortedString = GetSortedString(appKey, appSecret, timespan, nonce, getData);
Console.WriteLine(sortedString);
signStr = GetSha1Hash(sortedString.Trim());
Console.WriteLine(signStr);
}
}nodejs 示例
const crypto = require('crypto');
function getSortedString(appKey, appSecret, timespan, nonce, data) {
let buf = '';
buf += timespan;
buf += nonce;
buf += appKey;
buf += appSecret;
if (Buffer.isBuffer(data)) {
buf += data.toString('utf8');
} else if (typeof data === 'string') {
buf += data;
} else if (typeof data === 'object' && data !== null) {
for (const [k, v] of Object.entries(data)) {
if (v === '') {
continue;
}
buf += k;
buf += v;
}
}
const raw = buf;
const sortedString = raw.split('').sort().join('');
return sortedString;
}
function getSha1Hash(data) {
const sha1 = crypto.createHash('sha1');
sha1.update(data, 'utf8');
return sha1.digest('hex');
}
// 示例调用
const appKey = "d5d47248-b073-4940-a413-1ff34f1c1742";
const appSecret = "45a756ce-84e3-42d9-8735-2bd07b557742";
// 以毫秒计的时间戳
let timespan = "1609817584159";
const nonce = "bf0a1ac5925f4f4c800f5c52352cc132";
// post 请求中的 data
const postData = '{"pickupEndTime":"2020-12-24 16:45","pickupRemark":";图书订单;","pickupStartTime":"2020-12-24 15:45","recycleType":0,"sendCity":"杭州市","sendCounty":"江干区","sendDetail":"哈哈哈哈哈哈哈哈哈","sendName":"无言","sendPhone":"18771562716","sendProvince":"浙江省"}';
let sortedString = getSortedString(appKey, appSecret, timespan, nonce, postData);
console.log(sortedString);
// trim() 将结果中两端空白符号剪除
let signStr = getSha1Hash(sortedString.trim());
console.log(signStr.toLowerCase());
console.log('----------------------分割线----------------------');
timespan = "1722954781840";
// get 请求中的 data
// https://www.xxxxxxxx.com/OpenPlatform/GetIsbnInfoToOpenPlatform?isbnList=9787539981680,9787040494792,9787302301080
const getData = {
"isbnList": "9787539981680,9787040494792,9787302301080"
};
sortedString = getSortedString(appKey, appSecret, timespan, nonce, getData);
console.log(sortedString);
signStr = getSha1Hash(sortedString.trim());
console.log(signStr.toLowerCase());go 示例
package main
import (
"crypto/sha1"
"fmt"
"sort"
"strings"
)
func getSortedString(appKey, appSecret, timespan, nonce string, data interface{}) string {
buf := &strings.Builder{}
buf.WriteString(timespan)
buf.WriteString(nonce)
buf.WriteString(appKey)
buf.WriteString(appSecret)
switch v := data.(type) {
case []byte:
buf.Write(v)
case string:
buf.WriteString(v)
case map[string]string:
for k, v := range v {
if v == "" {
continue
}
buf.WriteString(k)
buf.WriteString(v)
}
}
raw := buf.String()
chars := []rune(raw)
sort.Slice(chars, func(i, j int) bool {
return chars[i] < chars[j]
})
sortedString := string(chars)
return sortedString
}
func getSha1Hash(data string) string {
sha1 := sha1.New()
sha1.Write([]byte(data))
return fmt.Sprintf("%x", sha1.Sum(nil))
}
func main() {
// 示例调用
appKey := "d5d47248-b073-4940-a413-1ff34f1c1742"
appSecret := "45a756ce-84e3-42d9-8735-2bd07b557742"
// 以毫秒计的时间戳
timespan := "1609817584159"
nonce := "bf0a1ac5925f4f4c800f5c52352cc132"
// post 请求中的 data
postData := `{"pickupEndTime":"2020-12-24 16:45","pickupRemark":";图书订单;","pickupStartTime":"2020-12-24 15:45","recycleType":0,"sendCity":"杭州市","sendCounty":"江干区","sendDetail":"哈哈哈哈哈哈哈哈哈","sendName":"无言","sendPhone":"18771562716","sendProvince":"浙江省"}`
sortedString := getSortedString(appKey, appSecret, timespan, nonce, postData)
fmt.Println(sortedString)
// strings.TrimSpace() 将结果中两端空白符号剪除
signStr := getSha1Hash(strings.TrimSpace(sortedString))
fmt.Println(strings.ToLower(signStr))
fmt.Println("----------------------分割线----------------------")
timespan = "1722954781840"
// get 请求中的 data
// https://www.xxxxxxxx.com/OpenPlatform/GetIsbnInfoToOpenPlatform?isbnList=9787539981680,9787040494792,9787302301080
getData := map[string]string{
"isbnList": "9787539981680,9787040494792,9787302301080",
}
sortedString = getSortedString(appKey, appSecret, timespan, nonce, getData)
fmt.Println(sortedString)
signStr = getSha1Hash(strings.TrimSpace(sortedString))
fmt.Println(strings.ToLower(signStr))
}php 示例
<?php
function getSortedString($appKey, $appSecret, $timespan, $nonce, $data) {
$buf = '';
$buf .= $timespan;
$buf .= $nonce;
$buf .= $appKey;
$buf .= $appSecret;
if (is_string($data)) {
$buf .= $data;
} elseif (is_array($data)) {
foreach ($data as $k => $v) {
if ($v === '') {
continue;
}
$buf .= $k;
$buf .= $v;
}
}
$raw = $buf;
// 修正:使用mb_convert_encoding确保UTF-8编码,然后按Unicode码点排序
$chars = preg_split('//u', $raw, -1, PREG_SPLIT_NO_EMPTY);
sort($chars, SORT_STRING);
$sortedString = implode('', $chars);
return $sortedString;
}
function getSha1Hash($data) {
$sha1 = hash('sha1', $data, false);
return $sha1;
}
// 示例调用
$appKey = "d5d47248-b073-4940-a413-1ff34f1c1742";
$appSecret = "45a756ce-84e3-42d9-8735-2bd07b557742";
// 以毫秒计的时间戳
$timespan = "1609817584159";
$nonce = "bf0a1ac5925f4f4c800f5c52352cc132";
// post 请求中的 data
$postData = '{"pickupEndTime":"2020-12-24 16:45","pickupRemark":";图书订单;","pickupStartTime":"2020-12-24 15:45","recycleType":0,"sendCity":"杭州市","sendCounty":"江干区","sendDetail":"哈哈哈哈哈哈哈哈哈","sendName":"无言","sendPhone":"18771562716","sendProvince":"浙江省"}';
$sortedString = getSortedString($appKey, $appSecret, $timespan, $nonce, $postData);
echo "Sorted string: " . $sortedString . "\n";
// trim() 将结果中两端空白符号剪除
$signStr = getSha1Hash(trim($sortedString));
echo "POST Sign: " . strtolower($signStr) . "\n";
echo "----------------------分割线----------------------\n";
$timespan = "1722954781840";
// get 请求中的 data
// https://www.xxxxxxxx.com/OpenPlatform/GetIsbnInfoToOpenPlatform?isbnList=9787539981680,9787040494792,9787302301080
$getData = array(
"isbnList" => "9787539981680,9787040494792,9787302301080"
);
$sortedString = getSortedString($appKey, $appSecret, $timespan, $nonce, $getData);
echo "Sorted string: " . $sortedString . "\n";
$signStr = getSha1Hash(trim($sortedString));
echo "GET Sign: " . strtolower($signStr) . "\n";
?>请求案列
询价请求接口 get 请求
params 参数

header 参数

curl 请求示例
# 请求示例
# 下方为示例参数,正确参数以计算结果为准
curl --location --request GET 'https://testapiv3.manyoujing.net/api/OpenPlatform/GetIsbnInfoToOpenPlatform?isbnList=9787539981680' \
--header 'Whaleyes-Appkey: d5d47248-b073-4940-a413-1ff34f1c1742' \
--header 'Whaleyes-Sign: a7eed54faabd426ab6848d295057fe720e2c27f1' \
--header 'Whaleyes-Nonce: bf0a1ac5925f4f4c800f5c52352cc132' \
--header 'Whaleyes-Timestamp: 1609817584159' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--header 'Content-Type: application/json' \
--header 'Accept: */*' \
--header 'Host: testapiv3.manyoujing.net' \
--header 'Connection: keep-alive'