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; /** * 私有频道处理器的抽象基类。 * *

封装内容

* * *

签名算法

* {@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 ""; } } }