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;
/**
* 私有频道处理器的抽象基类。
*
*
封装内容
*
* - HMAC-SHA512 签名计算(UTF-8 编码)
* - 认证请求 JSON 构建(id/time/channel/payload/auth)
* - subscribe / unsubscribe 的默认实现(含签名)
* - 用户 ID 获取(从 {@link GateGridTradeService#getUserId()})
*
*
* 签名算法
* {@code SIGN = Hex(HmacSHA512(secret_utf8, "channel={channel}&event={event}&time={timeSec}"_utf8))}
*
* 子类
* {@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("[{}] subscribed, contract:{}", 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("[{}] unsubscribed, contract:{}", 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("[{}] sign fail", channelName, e);
return "";
}
}
}