Administrator
4 days ago 86392fa6e18fa7dac20d3c03864cecf2abe4f7b3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package com.xcong.excoin.modules.gateApi.wsHandler;
 
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xcong.excoin.modules.gateApi.GateGridTradeService;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
 
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
 
/**
 * 私有频道处理器的抽象基类。
 *
 * <h3>封装内容</h3>
 * <ul>
 *   <li>HMAC-SHA512 签名计算(UTF-8 编码)</li>
 *   <li>认证请求 JSON 构建(id/time/channel/payload/auth)</li>
 *   <li>subscribe / unsubscribe 的默认实现(含签名)</li>
 *   <li>用户 ID 获取(从 {@link GateGridTradeService#getUserId()})</li>
 * </ul>
 *
 * <h3>签名算法</h3>
 * {@code SIGN = Hex(HmacSHA512(secret_utf8, "channel={channel}&event={event}&time={timeSec}"_utf8))}
 *
 * <h3>子类</h3>
 * {@link com.xcong.excoin.modules.gateApi.wsHandler.handler.PositionsChannelHandler}、
 * {@link com.xcong.excoin.modules.gateApi.wsHandler.handler.PositionClosesChannelHandler}
 *
 * @author Administrator
 */
@Slf4j
public abstract class AbstractPrivateChannelHandler implements GateChannelHandler {
 
    private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
 
    private final String channelName;
    private final String apiKey;
    private final String apiSecret;
    private final String contract;
    private final GateGridTradeService gridTradeService;
 
    public AbstractPrivateChannelHandler(String channelName,
                                          String apiKey, String apiSecret,
                                          String contract,
                                          GateGridTradeService gridTradeService) {
        this.channelName = channelName;
        this.apiKey = apiKey;
        this.apiSecret = apiSecret;
        this.contract = contract;
        this.gridTradeService = gridTradeService;
    }
 
    @Override
    public String getChannelName() { return channelName; }
 
    /**
     * 发送带签名的订阅请求。
     * payload: [userId, contract],auth: {method:"api_key", KEY, SIGN}
     */
    @Override
    public void subscribe(WebSocketClient ws) {
        long timeSec = System.currentTimeMillis() / 1000;
        JSONObject msg = buildAuthRequest("subscribe", buildUid(), timeSec);
        ws.send(msg.toJSONString());
        log.info("[{}] 订阅成功, 合约:{}", channelName, contract);
    }
 
    /**
     * 发送带签名的取消订阅请求,与 subscribe 对称。
     */
    @Override
    public void unsubscribe(WebSocketClient ws) {
        long timeSec = System.currentTimeMillis() / 1000;
        JSONObject msg = new JSONObject();
        msg.put("id", timeSec * 1000000 + (System.currentTimeMillis() % 1000));
        msg.put("time", timeSec);
        msg.put("channel", channelName);
        msg.put("event", "unsubscribe");
        JSONArray payload = new JSONArray();
        payload.add(contract);
        msg.put("payload", payload);
        JSONObject auth = new JSONObject();
        auth.put("method", "api_key");
        auth.put("KEY", apiKey);
        auth.put("SIGN", hs512Sign("unsubscribe", timeSec));
        msg.put("auth", auth);
        ws.send(msg.toJSONString());
        log.info("[{}] 取消订阅成功, 合约:{}", channelName, contract);
    }
 
    protected GateGridTradeService getGridTradeService() { return gridTradeService; }
    protected String getContract() { return contract; }
 
    /**
     * 从策略服务获取用户 ID,用于私有频道订阅的 payload[0]。
     */
    private String buildUid() {
        return gridTradeService != null && gridTradeService.getUserId() != null
                ? String.valueOf(gridTradeService.getUserId()) : "";
    }
 
    /**
     * 构建认证请求 JSON。包含 id、time、channel、event、payload[auth_user_id, contract]、auth 字段。
     */
    private JSONObject buildAuthRequest(String event, String uid, long timeSec) {
        JSONObject msg = new JSONObject();
        msg.put("id", timeSec * 1000000 + (System.currentTimeMillis() % 1000));
        msg.put("time", timeSec);
        msg.put("channel", channelName);
        msg.put("event", event);
        JSONArray payload = new JSONArray();
        payload.add(uid);
        payload.add(contract);
        msg.put("payload", payload);
        JSONObject auth = new JSONObject();
        auth.put("method", "api_key");
        auth.put("KEY", apiKey);
        auth.put("SIGN", hs512Sign(event, timeSec));
        msg.put("auth", auth);
        return msg;
    }
 
    /**
     * HMAC-SHA512 签名,使用 UTF-8 编码。
     */
    private String hs512Sign(String event, long timeSec) {
        try {
            String message = "channel=" + channelName + "&event=" + event + "&time=" + timeSec;
            Mac mac = Mac.getInstance("HmacSHA512");
            SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA512");
            mac.init(spec);
            byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
            StringBuilder hex = new StringBuilder(hash.length * 2);
            for (byte b : hash) {
                hex.append(HEX_ARRAY[(b >> 4) & 0xF]);
                hex.append(HEX_ARRAY[b & 0xF]);
            }
            return hex.toString();
        } catch (Exception e) {
            log.error("[{}] 签名计算失败", channelName, e);
            return "";
        }
    }
}