鉴权
每个请求在 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 等）
排查：用商户后台 调用调试 对比「待签名字符串」与己方代码输出是否一致。


