增加发货平台 2
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
鉴权
|
||||
每个请求在 Header 携带:
|
||||
|
||||
Header 说明
|
||||
X-App-Key App Key
|
||||
X-Timestamp Unix 秒级时间戳(字符串),与服务器相差不超过 5 分钟
|
||||
X-Sign 签名(见下)
|
||||
#签名算法
|
||||
code
|
||||
待签名字符串 = app_key + timestamp + raw_json_body
|
||||
sign = HMAC_SHA256(待签名字符串, app_secret) // 小写十六进制
|
||||
要点 说明
|
||||
raw_json_body 与 HTTP Body 完全一致 的原始字符串;签名后再原样发送
|
||||
是否排序 不需要对 JSON 字段排序;勿解析后再 json_encode 一次
|
||||
Header app_key、timestamp 只放在 Header,不参与 Body JSON
|
||||
中文 PHP 使用 JSON_UNESCAPED_UNICODE;Python 使用 ensure_ascii=False
|
||||
商户后台 开放 API → 调用调试 需先通过密码或动态口令验证,方可加载密钥并查看待签名字符串、签名结果与最终发包参数。
|
||||
|
||||
#PHP
|
||||
php
|
||||
<?php
|
||||
|
||||
$appKey = 'your_app_key';
|
||||
$appSecret = 'your_app_secret';
|
||||
$apiBase = 'http://skin-exchange.yiquyou.icu';
|
||||
|
||||
$payload = [
|
||||
'platform_order_no' => 'YOUR-ORDER-001',
|
||||
'product_code' => '10000001',
|
||||
'platform_buy_num' => 1,
|
||||
];
|
||||
|
||||
// 生成 body 后不要再改,签名与 curl 必须用同一字符串
|
||||
$body = json_encode($payload, JSON_UNESCAPED_UNICODE);
|
||||
$timestamp = (string) time();
|
||||
$preSign = $appKey . $timestamp . $body;
|
||||
$sign = hash_hmac('sha256', $preSign, $appSecret);
|
||||
|
||||
$ch = curl_init($apiBase . '/api/open/v1/orders/store');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'X-App-Key: ' . $appKey,
|
||||
'X-Timestamp: ' . $timestamp,
|
||||
'X-Sign: ' . $sign,
|
||||
],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
#Java
|
||||
依赖 JDK 标准库(javax.crypto),无需第三方包。
|
||||
|
||||
java
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
|
||||
public class OpenApiSignExample {
|
||||
|
||||
private static String hmacSha256Hex(String secret, String message) throws Exception {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
|
||||
byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder hex = new StringBuilder(hash.length * 2);
|
||||
for (byte b : hash) {
|
||||
hex.append(String.format("%02x", b));
|
||||
}
|
||||
return hex.toString();
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String appKey = "your_app_key";
|
||||
String appSecret = "your_app_secret";
|
||||
String apiBase = "http://skin-exchange.yiquyou.icu";
|
||||
|
||||
// 与最终请求体一致;可用 Gson/Jackson 生成,但不要签名后再格式化
|
||||
String body = "{\"platform_order_no\":\"YOUR-ORDER-001\",\"product_code\":\"10000001\",\"platform_buy_num\":1}";
|
||||
String timestamp = String.valueOf(Instant.now().getEpochSecond());
|
||||
String preSign = appKey + timestamp + body;
|
||||
String sign = hmacSha256Hex(appSecret, preSign);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(apiBase + "/api/open/v1/orders/store"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("X-App-Key", appKey)
|
||||
.header("X-Timestamp", timestamp)
|
||||
.header("X-Sign", sign)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(body))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = HttpClient.newHttpClient()
|
||||
.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println(response.statusCode());
|
||||
System.out.println(response.body());
|
||||
}
|
||||
}
|
||||
#Python
|
||||
Python 3,hmac + hashlib 标准库。
|
||||
|
||||
python
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
APP_KEY = "your_app_key"
|
||||
APP_SECRET = "your_app_secret"
|
||||
API_BASE = "http://skin-exchange.yiquyou.icu"
|
||||
|
||||
payload = {
|
||||
"platform_order_no": "YOUR-ORDER-001",
|
||||
"product_code": "10000001",
|
||||
"platform_buy_num": 1,
|
||||
}
|
||||
|
||||
# separators 生成紧凑 JSON,与常见 PHP json_encode 一致;签名后勿再 dumps
|
||||
body = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
|
||||
timestamp = str(int(time.time()))
|
||||
pre_sign = APP_KEY + timestamp + body
|
||||
sign = hmac.new(
|
||||
APP_SECRET.encode("utf-8"),
|
||||
pre_sign.encode("utf-8"),
|
||||
hashlib.sha256,
|
||||
).hexdigest()
|
||||
|
||||
req = urllib.request.Request(
|
||||
API_BASE + "/api/open/v1/orders/store",
|
||||
data=body.encode("utf-8"),
|
||||
method="POST",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-App-Key": APP_KEY,
|
||||
"X-Timestamp": timestamp,
|
||||
"X-Sign": sign,
|
||||
},
|
||||
)
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
print(resp.status)
|
||||
print(resp.read().decode())
|
||||
使用 requests 时:
|
||||
|
||||
python
|
||||
import requests
|
||||
|
||||
resp = requests.post(
|
||||
API_BASE + "/api/open/v1/orders/store",
|
||||
data=body.encode("utf-8"), # 传 bytes,避免库再次序列化
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-App-Key": APP_KEY,
|
||||
"X-Timestamp": timestamp,
|
||||
"X-Sign": sign,
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
print(resp.status_code, resp.text)
|
||||
#Go
|
||||
go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func sign(appKey, appSecret, body string) (timestamp, sign string) {
|
||||
timestamp = fmt.Sprintf("%d", time.Now().Unix())
|
||||
mac := hmac.New(sha256.New, []byte(appSecret))
|
||||
mac.Write([]byte(appKey + timestamp + body))
|
||||
sign = hex.EncodeToString(mac.Sum(nil))
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
appKey := "your_app_key"
|
||||
appSecret := "your_app_secret"
|
||||
apiBase := "http://skin-exchange.yiquyou.icu"
|
||||
|
||||
// 建议用 struct + json.Marshal;保证签名用的 body 与 Post 发送的字节一致
|
||||
type orderReq struct {
|
||||
PlatformOrderNo string `json:"platform_order_no"`
|
||||
ProductCode string `json:"product_code"`
|
||||
PlatformBuyNum int `json:"platform_buy_num"`
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(orderReq{
|
||||
PlatformOrderNo: "YOUR-ORDER-001",
|
||||
ProductCode: "10000001",
|
||||
PlatformBuyNum: 1,
|
||||
})
|
||||
body := string(bodyBytes)
|
||||
|
||||
timestamp, sig := sign(appKey, appSecret, body)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodPost, apiBase+"/api/open/v1/orders/store", bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-App-Key", appKey)
|
||||
req.Header.Set("X-Timestamp", timestamp)
|
||||
req.Header.Set("X-Sign", sig)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
out, _ := io.ReadAll(resp.Body)
|
||||
fmt.Println(resp.Status, string(out))
|
||||
}
|
||||
#常见错误
|
||||
现象 原因
|
||||
签名校验失败 Body 与签名时不一致(多空格、字段顺序、Unicode 转义不同)
|
||||
timestamp 无效或已过期 服务器时间差超过 5 分钟
|
||||
缺少 app_key、timestamp 或 sign Header 名称或大小写错误(应为 X-App-Key 等)
|
||||
排查:用商户后台 调用调试 对比「待签名字符串」与己方代码输出是否一致。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user