> For the complete documentation index, see [llms.txt](https://docs.catfee.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.catfee.io/getting-started/buy-energy-via-api-on-catfee/go.md).

# Go 调用示例

Go 调用 CatFee.IO Rest API 示例

### **前提条件**

1. [您需要一个有效的 API Key 和 API Secret](/getting-started/buy-energy-via-api-on-catfee/api-overview.md#apply-api-info)。
2. 使用 Go 1.20 及以上版本。本示例仅使用 Go 标准库，无需安装第三方依赖。

### **示例代码**

```go
package main

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	APIKey        = "your_api_key"    // 请替换为您的 API Key
	APISecret     = "your_api_secret" // 请替换为您的 API Secret
	BaseURL       = "https://api.catfee.io"
	RequestTimeout = 15 * time.Second
)

type APIResponse struct {
	Code   int          `json:"code"`
	Msg    string       `json:"msg"`
	SubMsg string       `json:"sub_msg"`
	Data   *OrderPayload `json:"data"`
}

type OrderPayload struct {
	ID string `json:"id"`
}

// generateTimestamp 生成带毫秒的 ISO 8601 UTC 时间戳。
func generateTimestamp() string {
	return time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
}

// buildRequestPath 构建参与签名并实际发送的完整请求路径。
func buildRequestPath(path string, queryParams url.Values) string {
	if len(queryParams) == 0 {
		return path
	}

	return path + "?" + queryParams.Encode()
}

// generateSignature 使用 HMAC-SHA256 生成 Base64 编码的请求签名。
func generateSignature(timestamp, method, requestPath string) string {
	signString := timestamp + strings.ToUpper(method) + requestPath
	mac := hmac.New(sha256.New, []byte(APISecret))
	_, _ = mac.Write([]byte(signString))

	return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

// generateClientOrderID 生成 32 个字符的幂等订单号。
func generateClientOrderID() (string, error) {
	value := make([]byte, 16)
	if _, err := rand.Read(value); err != nil {
		return "", err
	}

	return hex.EncodeToString(value), nil
}

// sendRequest 签名并发送 CatFee API 请求。
func sendRequest(
	method string,
	path string,
	queryParams url.Values,
) (int, []byte, error) {
	method = strings.ToUpper(method)
	requestPath := buildRequestPath(path, queryParams)
	timestamp := generateTimestamp()
	signature := generateSignature(timestamp, method, requestPath)

	// requestPath 必须同时用于签名和实际请求，避免参数编码或顺序不一致。
	req, err := http.NewRequest(method, BaseURL+requestPath, nil)
	if err != nil {
		return 0, nil, fmt.Errorf("create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("CF-ACCESS-KEY", APIKey)
	req.Header.Set("CF-ACCESS-SIGN", signature)
	req.Header.Set("CF-ACCESS-TIMESTAMP", timestamp)

	client := &http.Client{Timeout: RequestTimeout}
	resp, err := client.Do(req)
	if err != nil {
		return 0, nil, fmt.Errorf("send request: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return resp.StatusCode, nil, fmt.Errorf("read response: %w", err)
	}

	return resp.StatusCode, body, nil
}

func main() {
	clientOrderID, err := generateClientOrderID()
	if err != nil {
		fmt.Println("Generate Client Order ID failed:", err)
		return
	}

	// 示例：创建一笔能量订单。
	queryParams := url.Values{}
	queryParams.Set("quantity", "65000")
	queryParams.Set("receiver", "TRON_ADDRESS")
	queryParams.Set("duration", "1h")
	queryParams.Set("client_order_id", clientOrderID)
	queryParams.Set("activate", "true")

	fmt.Println("Client Order ID:", clientOrderID)
	statusCode, body, err := sendRequest("POST", "/v1/order", queryParams)
	if err != nil {
		fmt.Println("Request failed:", err)
		fmt.Println("如需重试，请复用 Client Order ID:", clientOrderID)
		return
	}

	fmt.Println("HTTP Status:", statusCode)
	fmt.Println("Response Body:", string(body))

	// HTTP 层失败（例如 4xx、5xx）。
	if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
		fmt.Printf("HTTP request failed with status %d\n", statusCode)
		return
	}

	// CatFee API 通常返回 HTTP 200，仍需检查响应体中的业务 code。
	var result APIResponse
	if err := json.Unmarshal(body, &result); err != nil {
		fmt.Println("Response is not valid JSON:", err)
		return
	}

	if result.Code != 0 {
		message := result.Msg
		if message == "" {
			message = result.SubMsg
		}
		fmt.Printf("API request failed: code=%d, message=%s\n", result.Code, message)
		return
	}

	if result.Data != nil {
		fmt.Println("Order ID:", result.Data.ID)
	}
}
```

### **代码解析**

1. **`generateTimestamp()`**：\
   生成带毫秒的 ISO 8601 UTC 时间戳，用于 `CF-ACCESS-TIMESTAMP` 请求头。
2. **`buildRequestPath()`**：\
   使用 `url.Values.Encode()` 对查询参数进行 URL 编码，并按键名排序。返回结果同时用于签名和实际请求，确保两者完全一致。
3. **`generateSignature()`**：\
   按照 `timestamp + method + requestPath` 拼接签名原文，使用 API Secret 计算 HMAC-SHA256，并对结果进行 Base64 编码。
4. **`sendRequest()`**：\
   构造三个 CatFee 鉴权请求头，通过 `net/http` 发送请求，设置 15 秒超时，并确保响应体被关闭。
5. **`main()`**：\
   调用 `POST /v1/order` 创建能量订单，并同时检查 HTTP 状态码和响应体中的业务 `code`。

### **注意事项**

* **API Key 和 Secret**：\
  请确保将 `APIKey` 和 `APISecret` 替换为您从 CatFee.IO 获取的实际值。
* **签名路径必须与请求路径一致**：\
  查询参数的内容、URL 编码和顺序都必须一致。本示例仅调用一次 `buildRequestPath()`，其结果同时用于签名和请求。
* **订单参数**：\
  `quantity` 当前不得小于 `65000`，`duration` 当前仅支持 `1h`；`receiver` 必须是有效的 TRON 地址。
* **幂等重试**：\
  每笔新订单使用唯一且不超过 64 个字符的 `client_order_id`。发生超时或连接中断时，应保存并复用原值重试，不能重新生成。
* **地址激活**：\
  `activate=true` 表示必要时允许激活接收地址；如果设为 `false` 且地址未激活，接口将返回错误。
* **响应判断**：\
  HTTP `200` 不一定表示业务成功，还必须确认响应 JSON 中的 `code` 为 `0`。

### **总结**

此示例展示了如何使用 Go 调用 `POST /v1/order` 购买能量，并正确处理请求签名、幂等订单号、超时和 API 响应。其他接口也可以复用 `sendRequest()`，只需传入对应的 HTTP 方法、路径和查询参数。
