HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 交易所技术完整体系

    • 交易所技术完整体系
    • 交易所技术架构总览
    • 交易基础概念
    • 撮合引擎原理
    • 撮合引擎实现-内存撮合
    • 撮合引擎优化 - 延迟与吞吐
    • 撮合引擎高可用
    • 清算系统设计
    • 风控系统设计
    • 资金管理系统
    • 行情系统设计
    • 去中心化交易所(DEX)设计
    • 合约交易系统
    • 数据库设计与优化
    • 缓存与消息队列
    • 用户系统与KYC
    • 交易所API设计
    • 监控与告警系统
    • 安全防护与攻防
    • 高可用架构设计
    • 压力测试与性能优化
    • 项目实战-完整交易所实现

交易所API设计

量化交易团队经常因为API响应慢、数据不一致而导致策略失效。API不仅是技术接口,更是交易所的核心竞争力。一个设计良好的API系统能够支撑从每日50万次到5000万次的调用量,将API延迟从平均200ms降到15ms以下。

本章参考Binance、Coinbase、OKEx等头部交易所的API设计,分享交易所API设计的最佳实践,包括REST API、WebSocket、签名认证、限流、错误处理等核心内容。

1. API架构设计

1.1 整体架构

用户
  ↓
API Gateway (Nginx/Kong)
  ↓
  ├─→ REST API 服务集群
  ├─→ WebSocket 服务集群
  └─→ 签名验证服务
      ↓
      ├─→ 限流服务 (Redis)
      ├─→ 业务服务 (订单、账户、行情)
      └─→ 数据库集群 (MySQL/Redis)

1.2 API分类

API类型用途认证要求示例
Public API公开数据无需认证获取K线、深度、交易记录
Private API用户数据需要签名查询余额、订单历史
Trade API交易操作需要签名下单、撤单
WebSocket实时数据部分需要实时行情、订单更新

2. REST API设计

2.1 URL设计规范

遵循RESTful风格:

# 好的设计
GET    /api/v1/markets           # 获取所有交易对
GET    /api/v1/markets/{symbol}  # 获取特定交易对
GET    /api/v1/orders            # 获取所有订单
GET    /api/v1/orders/{orderId}  # 获取特定订单
POST   /api/v1/orders            # 创建订单
DELETE /api/v1/orders/{orderId}  # 撤销订单

# 不好的设计
GET    /api/v1/getMarkets        # 动词冗余
POST   /api/v1/order/create      # 路径包含动词
GET    /api/v1/deleteOrder       # GET用于删除操作

2.2 Go实现示例

package api

import (
	"encoding/json"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/mux"
)

type APIServer struct {
	router      *mux.Router
	orderSvc    OrderService
	accountSvc  AccountService
	marketSvc   MarketService
	authSvc     AuthService
}

func NewAPIServer() *APIServer {
	s := &APIServer{
		router: mux.NewRouter(),
	}

	s.registerRoutes()
	return s
}

func (s *APIServer) registerRoutes() {
	// Public API
	public := s.router.PathPrefix("/api/v1").Subrouter()
	public.HandleFunc("/time", s.handleGetServerTime).Methods("GET")
	public.HandleFunc("/markets", s.handleGetMarkets).Methods("GET")
	public.HandleFunc("/markets/{symbol}", s.handleGetMarket).Methods("GET")
	public.HandleFunc("/ticker/{symbol}", s.handleGetTicker).Methods("GET")
	public.HandleFunc("/depth/{symbol}", s.handleGetDepth).Methods("GET")
	public.HandleFunc("/trades/{symbol}", s.handleGetTrades).Methods("GET")
	public.HandleFunc("/klines/{symbol}", s.handleGetKlines).Methods("GET")

	// Private API (需要认证)
	private := s.router.PathPrefix("/api/v1").Subrouter()
	private.Use(s.authMiddleware)
	private.HandleFunc("/account", s.handleGetAccount).Methods("GET")
	private.HandleFunc("/balance", s.handleGetBalance).Methods("GET")
	private.HandleFunc("/orders", s.handleGetOrders).Methods("GET")
	private.HandleFunc("/orders/{orderId}", s.handleGetOrder).Methods("GET")
	private.HandleFunc("/orders/open", s.handleGetOpenOrders).Methods("GET")

	// Trade API
	trade := s.router.PathPrefix("/api/v1").Subrouter()
	trade.Use(s.authMiddleware)
	trade.Use(s.rateLimitMiddleware)
	trade.HandleFunc("/orders", s.handleCreateOrder).Methods("POST")
	trade.HandleFunc("/orders/{orderId}", s.handleCancelOrder).Methods("DELETE")
	trade.HandleFunc("/orders/cancel-all", s.handleCancelAllOrders).Methods("POST")
}

// ============ Public API ============

// 获取服务器时间
func (s *APIServer) handleGetServerTime(w http.ResponseWriter, r *http.Request) {
	response := map[string]interface{}{
		"serverTime": time.Now().UnixMilli(),
	}
	s.writeJSON(w, http.StatusOK, response)
}

// 获取所有交易对
func (s *APIServer) handleGetMarkets(w http.ResponseWriter, r *http.Request) {
	markets := s.marketSvc.GetAllMarkets()
	s.writeJSON(w, http.StatusOK, markets)
}

// 获取特定交易对信息
func (s *APIServer) handleGetMarket(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	symbol := vars["symbol"]

	market, err := s.marketSvc.GetMarket(symbol)
	if err != nil {
		s.writeError(w, http.StatusNotFound, "MARKET_NOT_FOUND", "Market not found")
		return
	}

	s.writeJSON(w, http.StatusOK, market)
}

// 获取Ticker
func (s *APIServer) handleGetTicker(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	symbol := vars["symbol"]

	ticker, err := s.marketSvc.GetTicker(symbol)
	if err != nil {
		s.writeError(w, http.StatusNotFound, "SYMBOL_NOT_FOUND", "Symbol not found")
		return
	}

	s.writeJSON(w, http.StatusOK, ticker)
}

// 获取订单簿深度
func (s *APIServer) handleGetDepth(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	symbol := vars["symbol"]

	// 可选参数:limit (默认100)
	limitStr := r.URL.Query().Get("limit")
	limit := 100
	if limitStr != "" {
		if l, err := strconv.Atoi(limitStr); err == nil {
			limit = l
		}
	}

	depth, err := s.marketSvc.GetDepth(symbol, limit)
	if err != nil {
		s.writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
		return
	}

	s.writeJSON(w, http.StatusOK, depth)
}

// 获取最近成交记录
func (s *APIServer) handleGetTrades(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	symbol := vars["symbol"]

	limitStr := r.URL.Query().Get("limit")
	limit := 500
	if limitStr != "" {
		if l, err := strconv.Atoi(limitStr); err == nil && l <= 1000 {
			limit = l
		}
	}

	trades, err := s.marketSvc.GetRecentTrades(symbol, limit)
	if err != nil {
		s.writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
		return
	}

	s.writeJSON(w, http.StatusOK, trades)
}

// 获取K线数据
func (s *APIServer) handleGetKlines(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	symbol := vars["symbol"]

	interval := r.URL.Query().Get("interval") // 1m, 5m, 15m, 1h, 1d, etc.
	if interval == "" {
		interval = "1m"
	}

	limitStr := r.URL.Query().Get("limit")
	limit := 500
	if limitStr != "" {
		if l, err := strconv.Atoi(limitStr); err == nil {
			limit = l
		}
	}

	klines, err := s.marketSvc.GetKlines(symbol, interval, limit)
	if err != nil {
		s.writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
		return
	}

	s.writeJSON(w, http.StatusOK, klines)
}

// ============ Private API ============

// 获取账户信息
func (s *APIServer) handleGetAccount(w http.ResponseWriter, r *http.Request) {
	userID := r.Context().Value("userID").(string)

	account, err := s.accountSvc.GetAccount(userID)
	if err != nil {
		s.writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
		return
	}

	s.writeJSON(w, http.StatusOK, account)
}

// 获取余额
func (s *APIServer) handleGetBalance(w http.ResponseWriter, r *http.Request) {
	userID := r.Context().Value("userID").(string)

	balances, err := s.accountSvc.GetBalances(userID)
	if err != nil {
		s.writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
		return
	}

	s.writeJSON(w, http.StatusOK, balances)
}

// 获取订单列表
func (s *APIServer) handleGetOrders(w http.ResponseWriter, r *http.Request) {
	userID := r.Context().Value("userID").(string)

	symbol := r.URL.Query().Get("symbol")
	limitStr := r.URL.Query().Get("limit")
	limit := 100
	if limitStr != "" {
		if l, err := strconv.Atoi(limitStr); err == nil {
			limit = l
		}
	}

	orders, err := s.orderSvc.GetOrders(userID, symbol, limit)
	if err != nil {
		s.writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
		return
	}

	s.writeJSON(w, http.StatusOK, orders)
}

// 获取未完成订单
func (s *APIServer) handleGetOpenOrders(w http.ResponseWriter, r *http.Request) {
	userID := r.Context().Value("userID").(string)

	symbol := r.URL.Query().Get("symbol")

	orders, err := s.orderSvc.GetOpenOrders(userID, symbol)
	if err != nil {
		s.writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
		return
	}

	s.writeJSON(w, http.StatusOK, orders)
}

// ============ Trade API ============

// 创建订单
func (s *APIServer) handleCreateOrder(w http.ResponseWriter, r *http.Request) {
	userID := r.Context().Value("userID").(string)

	var req CreateOrderRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		s.writeError(w, http.StatusBadRequest, "INVALID_REQUEST", "Invalid request body")
		return
	}

	// 验证参数
	if err := s.validateOrderRequest(&req); err != nil {
		s.writeError(w, http.StatusBadRequest, "INVALID_PARAMS", err.Error())
		return
	}

	// 创建订单
	order, err := s.orderSvc.CreateOrder(userID, &req)
	if err != nil {
		s.writeError(w, http.StatusInternalServerError, "ORDER_FAILED", err.Error())
		return
	}

	s.writeJSON(w, http.StatusOK, order)
}

// 撤销订单
func (s *APIServer) handleCancelOrder(w http.ResponseWriter, r *http.Request) {
	userID := r.Context().Value("userID").(string)
	vars := mux.Vars(r)
	orderID := vars["orderId"]

	err := s.orderSvc.CancelOrder(userID, orderID)
	if err != nil {
		s.writeError(w, http.StatusInternalServerError, "CANCEL_FAILED", err.Error())
		return
	}

	s.writeJSON(w, http.StatusOK, map[string]string{
		"orderId": orderID,
		"status":  "canceled",
	})
}

// 撤销所有订单
func (s *APIServer) handleCancelAllOrders(w http.ResponseWriter, r *http.Request) {
	userID := r.Context().Value("userID").(string)

	var req struct {
		Symbol string `json:"symbol"`
	}
	json.NewDecoder(r.Body).Decode(&req)

	count, err := s.orderSvc.CancelAllOrders(userID, req.Symbol)
	if err != nil {
		s.writeError(w, http.StatusInternalServerError, "CANCEL_FAILED", err.Error())
		return
	}

	s.writeJSON(w, http.StatusOK, map[string]interface{}{
		"canceled": count,
	})
}

// ============ 辅助函数 ============

type CreateOrderRequest struct {
	Symbol      string  `json:"symbol"`
	Side        string  `json:"side"`        // "buy" or "sell"
	Type        string  `json:"type"`        // "limit" or "market"
	Price       float64 `json:"price"`
	Quantity    float64 `json:"quantity"`
	TimeInForce string  `json:"timeInForce"` // "GTC", "IOC", "FOK"
}

func (s *APIServer) validateOrderRequest(req *CreateOrderRequest) error {
	if req.Symbol == "" {
		return fmt.Errorf("symbol is required")
	}
	if req.Side != "buy" && req.Side != "sell" {
		return fmt.Errorf("invalid side")
	}
	if req.Type != "limit" && req.Type != "market" {
		return fmt.Errorf("invalid type")
	}
	if req.Type == "limit" && req.Price <= 0 {
		return fmt.Errorf("price must be positive for limit orders")
	}
	if req.Quantity <= 0 {
		return fmt.Errorf("quantity must be positive")
	}
	return nil
}

// 统一响应格式
type APIResponse struct {
	Success bool        `json:"success"`
	Data    interface{} `json:"data,omitempty"`
	Error   *APIError   `json:"error,omitempty"`
}

type APIError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

func (s *APIServer) writeJSON(w http.ResponseWriter, status int, data interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteStatus(status)

	response := APIResponse{
		Success: true,
		Data:    data,
	}

	json.NewEncoder(w).Encode(response)
}

func (s *APIServer) writeError(w http.ResponseWriter, status int, code, message string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)

	response := APIResponse{
		Success: false,
		Error: &APIError{
			Code:    code,
			Message: message,
		},
	}

	json.NewEncoder(w).Encode(response)
}

3. 签名认证

3.1 HMAC-SHA256签名

package auth

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"net/url"
	"sort"
	"strings"
	"time"
)

type Signer struct {
	apiKey    string
	secretKey string
}

func NewSigner(apiKey, secretKey string) *Signer {
	return &Signer{
		apiKey:    apiKey,
		secretKey: secretKey,
	}
}

// 生成签名
func (s *Signer) Sign(method, path string, params map[string]string) string {
	// 1. 添加时间戳和API Key
	params["timestamp"] = fmt.Sprintf("%d", time.Now().UnixMilli())
	params["apiKey"] = s.apiKey

	// 2. 按字母顺序排序参数
	keys := make([]string, 0, len(params))
	for k := range params {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	// 3. 拼接查询字符串
	var parts []string
	for _, k := range keys {
		parts = append(parts, fmt.Sprintf("%s=%s", k, url.QueryEscape(params[k])))
	}
	queryString := strings.Join(parts, "&")

	// 4. 构造待签名字符串
	// 格式:METHOD\nPATH\nQUERY_STRING
	signString := fmt.Sprintf("%s\n%s\n%s", method, path, queryString)

	// 5. HMAC-SHA256签名
	h := hmac.New(sha256.New, []byte(s.secretKey))
	h.Write([]byte(signString))
	signature := hex.EncodeToString(h.Sum(nil))

	return signature
}

// 验证签名
func (s *Signer) Verify(method, path string, params map[string]string, signature string) bool {
	expectedSignature := s.Sign(method, path, params)
	return hmac.Equal([]byte(signature), []byte(expectedSignature))
}

// 检查时间戳(防重放攻击)
func (s *Signer) ValidateTimestamp(timestamp int64) bool {
	now := time.Now().UnixMilli()
	// 允许5分钟的时间差
	if abs(now-timestamp) > 5*60*1000 {
		return false
	}
	return true
}

func abs(n int64) int64 {
	if n < 0 {
		return -n
	}
	return n
}

3.2 认证中间件

func (s *APIServer) authMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// 1. 获取签名和时间戳
		signature := r.Header.Get("X-Signature")
		timestampStr := r.Header.Get("X-Timestamp")
		apiKey := r.Header.Get("X-API-Key")

		if signature == "" || timestampStr == "" || apiKey == "" {
			s.writeError(w, http.StatusUnauthorized, "MISSING_AUTH", "Missing authentication headers")
			return
		}

		// 2. 验证时间戳
		timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
		if err != nil || !s.authSvc.ValidateTimestamp(timestamp) {
			s.writeError(w, http.StatusUnauthorized, "INVALID_TIMESTAMP", "Invalid or expired timestamp")
			return
		}

		// 3. 获取用户密钥
		secretKey, err := s.authSvc.GetSecretKey(apiKey)
		if err != nil {
			s.writeError(w, http.StatusUnauthorized, "INVALID_API_KEY", "Invalid API key")
			return
		}

		// 4. 验证签名
		params := extractParams(r)
		signer := auth.NewSigner(apiKey, secretKey)
		if !signer.Verify(r.Method, r.URL.Path, params, signature) {
			s.writeError(w, http.StatusUnauthorized, "INVALID_SIGNATURE", "Invalid signature")
			return
		}

		// 5. 获取用户ID
		userID, err := s.authSvc.GetUserID(apiKey)
		if err != nil {
			s.writeError(w, http.StatusUnauthorized, "INVALID_USER", "User not found")
			return
		}

		// 6. 将用户ID放入上下文
		ctx := context.WithValue(r.Context(), "userID", userID)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

func extractParams(r *http.Request) map[string]string {
	params := make(map[string]string)

	// 从URL query参数提取
	for k, v := range r.URL.Query() {
		if len(v) > 0 {
			params[k] = v[0]
		}
	}

	// 从body提取(POST请求)
	if r.Method == "POST" || r.Method == "PUT" {
		r.ParseForm()
		for k, v := range r.PostForm {
			if len(v) > 0 {
				params[k] = v[0]
			}
		}
	}

	return params
}

3.3 客户端使用示例

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

type ExchangeClient struct {
	apiKey    string
	secretKey string
	baseURL   string
	signer    *auth.Signer
}

func NewExchangeClient(apiKey, secretKey, baseURL string) *ExchangeClient {
	return &ExchangeClient{
		apiKey:    apiKey,
		secretKey: secretKey,
		baseURL:   baseURL,
		signer:    auth.NewSigner(apiKey, secretKey),
	}
}

// 创建订单
func (c *ExchangeClient) CreateOrder(symbol, side, orderType string, price, quantity float64) (*Order, error) {
	params := map[string]string{
		"symbol":   symbol,
		"side":     side,
		"type":     orderType,
		"price":    fmt.Sprintf("%.8f", price),
		"quantity": fmt.Sprintf("%.8f", quantity),
	}

	return c.signedRequest("POST", "/api/v1/orders", params)
}

func (c *ExchangeClient) signedRequest(method, path string, params map[string]string) (*Order, error) {
	// 1. 生成签名
	signature := c.signer.Sign(method, path, params)
	timestamp := params["timestamp"]

	// 2. 构造请求
	url := c.baseURL + path
	var body io.Reader
	if method == "POST" {
		jsonData, _ := json.Marshal(params)
		body = bytes.NewBuffer(jsonData)
	}

	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, err
	}

	// 3. 添加认证头
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-API-Key", c.apiKey)
	req.Header.Set("X-Timestamp", timestamp)
	req.Header.Set("X-Signature", signature)

	// 4. 发送请求
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// 5. 解析响应
	var result Order
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, err
	}

	return &result, nil
}

4. WebSocket API

4.1 服务端实现

package websocket

import (
	"encoding/json"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

type WSServer struct {
	clients    map[*Client]bool
	broadcast  chan []byte
	register   chan *Client
	unregister chan *Client
	mu         sync.RWMutex
}

type Client struct {
	conn   *websocket.Conn
	send   chan []byte
	topics map[string]bool // 订阅的主题
	mu     sync.RWMutex
}

func NewWSServer() *WSServer {
	return &WSServer{
		clients:    make(map[*Client]bool),
		broadcast:  make(chan []byte, 1000),
		register:   make(chan *Client),
		unregister: make(chan *Client),
	}
}

func (s *WSServer) Run() {
	for {
		select {
		case client := <-s.register:
			s.mu.Lock()
			s.clients[client] = true
			s.mu.Unlock()

		case client := <-s.unregister:
			s.mu.Lock()
			if _, ok := s.clients[client]; ok {
				delete(s.clients, client)
				close(client.send)
			}
			s.mu.Unlock()

		case message := <-s.broadcast:
			s.mu.RLock()
			for client := range s.clients {
				select {
				case client.send <- message:
				default:
					close(client.send)
					delete(s.clients, client)
				}
			}
			s.mu.RUnlock()
		}
	}
}

// 处理客户端连接
func (s *WSServer) HandleClient(w http.ResponseWriter, r *http.Request) {
	upgrader := websocket.Upgrader{
		CheckOrigin: func(r *http.Request) bool {
			return true // 生产环境应检查origin
		},
	}

	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}

	client := &Client{
		conn:   conn,
		send:   make(chan []byte, 256),
		topics: make(map[string]bool),
	}

	s.register <- client

	// 启动读写goroutine
	go client.writePump()
	go client.readPump(s)
}

// 读取客户端消息
func (c *Client) readPump(server *WSServer) {
	defer func() {
		server.unregister <- c
		c.conn.Close()
	}()

	c.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
	c.conn.SetPongHandler(func(string) error {
		c.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
		return nil
	})

	for {
		_, message, err := c.conn.ReadMessage()
		if err != nil {
			break
		}

		// 处理订阅/取消订阅
		var msg WSMessage
		if err := json.Unmarshal(message, &msg); err != nil {
			continue
		}

		switch msg.Method {
		case "subscribe":
			c.subscribe(msg.Params...)
		case "unsubscribe":
			c.unsubscribe(msg.Params...)
		}
	}
}

// 向客户端发送消息
func (c *Client) writePump() {
	ticker := time.NewTicker(54 * time.Second)
	defer func() {
		ticker.Stop()
		c.conn.Close()
	}()

	for {
		select {
		case message, ok := <-c.send:
			c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
			if !ok {
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}

			w, err := c.conn.NextWriter(websocket.TextMessage)
			if err != nil {
				return
			}
			w.Write(message)

			// 批量发送队列中的消息
			n := len(c.send)
			for i := 0; i < n; i++ {
				w.Write([]byte{'\n'})
				w.Write(<-c.send)
			}

			if err := w.Close(); err != nil {
				return
			}

		case <-ticker.C:
			c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

// 订阅主题
func (c *Client) subscribe(topics ...string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	for _, topic := range topics {
		c.topics[topic] = true
	}
}

// 取消订阅
func (c *Client) unsubscribe(topics ...string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	for _, topic := range topics {
		delete(c.topics, topic)
	}
}

// WebSocket消息格式
type WSMessage struct {
	Method string   `json:"method"` // subscribe, unsubscribe
	Params []string `json:"params"` // 主题列表
}

// 推送消息到订阅了特定主题的客户端
func (s *WSServer) PublishToTopic(topic string, data interface{}) {
	message, _ := json.Marshal(map[string]interface{}{
		"topic": topic,
		"data":  data,
	})

	s.mu.RLock()
	defer s.mu.RUnlock()

	for client := range s.clients {
		client.mu.RLock()
		subscribed := client.topics[topic]
		client.mu.RUnlock()

		if subscribed {
			select {
			case client.send <- message:
			default:
				close(client.send)
				delete(s.clients, client)
			}
		}
	}
}

4.2 客户端使用示例

// JavaScript客户端
const ws = new WebSocket('wss://api.example.com/ws');

ws.onopen = () => {
  console.log('Connected');

  // 订阅ticker
  ws.send(JSON.stringify({
    method: 'subscribe',
    params: ['ticker.BTCUSDT', 'depth.BTCUSDT']
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.topic === 'ticker.BTCUSDT') {
    console.log('Ticker:', data.data);
  } else if (data.topic === 'depth.BTCUSDT') {
    console.log('Depth:', data.data);
  }
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = () => {
  console.log('Disconnected');
};

5. API文档

推荐使用OpenAPI (Swagger)规范:

openapi: 3.0.0
info:
  title: Exchange API
  version: 1.0.0
  description: Cryptocurrency Exchange REST API

servers:
  - url: https://api.example.com
    description: Production server

paths:
  /api/v1/markets:
    get:
      summary: Get all trading pairs
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Market'

  /api/v1/orders:
    post:
      summary: Create a new order
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
      responses:
        '200':
          description: Order created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'

components:
  schemas:
    Market:
      type: object
      properties:
        symbol:
          type: string
          example: "BTCUSDT"
        baseAsset:
          type: string
          example: "BTC"
        quoteAsset:
          type: string
          example: "USDT"

    CreateOrderRequest:
      type: object
      required:
        - symbol
        - side
        - type
        - quantity
      properties:
        symbol:
          type: string
        side:
          type: string
          enum: [buy, sell]
        type:
          type: string
          enum: [limit, market]
        price:
          type: number
        quantity:
          type: number

    Order:
      type: object
      properties:
        orderId:
          type: string
        symbol:
          type: string
        side:
          type: string
        price:
          type: number
        quantity:
          type: number
        status:
          type: string

  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key

小结

交易所API设计的核心要点:

  1. RESTful API设计规范
  2. HMAC-SHA256签名认证
  3. WebSocket实时推送
  4. 完善的错误处理
  5. OpenAPI文档规范

下一章将讨论交易所的性能优化和高可用架构设计。

Prev
用户系统与KYC
Next
监控与告警系统