| | |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.xcong.excoin.modules.gateApi.GateGridTradeService; |
| | | import com.xcong.excoin.modules.gateApi.wsHandler.GateChannelHandler; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.java_websocket.client.WebSocketClient; |
| | | |
| | |
| | | import java.nio.charset.StandardCharsets; |
| | | |
| | | /** |
| | | * 私有频道的抽象基类,封装 HMAC-SHA512 签名和认证请求构建。 |
| | | * 私有频道处理器的抽象基类。 |
| | | * |
| | | * <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 |
| | | */ |
| | |
| | | this.gridTradeService = gridTradeService; |
| | | } |
| | | |
| | | /** @return 频道名称(如 "futures.positions") */ |
| | | @Override |
| | | public String getChannelName() { |
| | | return channelName; |
| | | } |
| | | public String getChannelName() { return channelName; } |
| | | |
| | | /** |
| | | * 发送带签名的订阅请求。 |
| | | * |
| | | * <h3>请求格式</h3> |
| | | * <pre> |
| | | * { |
| | | * "id": <唯一请求ID>, |
| | | * "time": <unix时间戳(秒)>, |
| | | * "channel":"futures.positions", |
| | | * "event": "subscribe", |
| | | * "payload":[userId, contract], |
| | | * "auth": {"method":"api_key", "KEY":<APIKEY>, "SIGN":<HMAC-SHA512签名>} |
| | | * } |
| | | * </pre> |
| | | */ |
| | | @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); |
| | | log.info("[{}] 订阅成功, 合约:{}", channelName, contract); |
| | | } |
| | | |
| | | /** |
| | | * 发送带签名的取消订阅请求,与 subscribe 结构一致。 |
| | | * payload: [contract],无 userId(取消订阅不需要用户ID)。 |
| | | */ |
| | | @Override |
| | | public void unsubscribe(WebSocketClient ws) { |
| | | JSONObject unsubscribeMsg = new JSONObject(); |
| | | unsubscribeMsg.put("time", System.currentTimeMillis() / 1000); |
| | | unsubscribeMsg.put("channel", channelName); |
| | | unsubscribeMsg.put("event", "unsubscribe"); |
| | | 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); |
| | | unsubscribeMsg.put("payload", payload); |
| | | ws.send(unsubscribeMsg.toJSONString()); |
| | | log.info("[{}] 已发送取消订阅请求,合约: {}", channelName, 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; |
| | | } |
| | | /** @return 网格交易服务实例 */ |
| | | protected GateGridTradeService getGridTradeService() { return gridTradeService; } |
| | | /** @return 当前订阅的合约名称 */ |
| | | protected String getContract() { return contract; } |
| | | |
| | | protected String getContract() { |
| | | return contract; |
| | | } |
| | | |
| | | /** |
| | | * 从策略服务获取用户 ID,用于私有频道订阅的 payload[0]。 |
| | | * |
| | | * @return 用户 ID 字符串,获取失败返回空字符串 |
| | | */ |
| | | private String buildUid() { |
| | | return gridTradeService != null && gridTradeService.getUserId() != null |
| | | ? String.valueOf(gridTradeService.getUserId()) : ""; |
| | | } |
| | | |
| | | /** |
| | | * 构建认证请求 JSON。 |
| | | * 包含 id、time、channel、event、payload[userId, contract]、auth 字段。 |
| | | * |
| | | * @param event 事件类型("subscribe" / "unsubscribe") |
| | | * @param uid 认证用户 ID |
| | | * @param timeSec unix 时间戳(秒) |
| | | * @return 完整的认证请求 JSONObject |
| | | */ |
| | | private JSONObject buildAuthRequest(String event, String uid, long timeSec) { |
| | | JSONObject msg = new JSONObject(); |
| | | msg.put("id", timeSec * 1000000 + (System.currentTimeMillis() % 1000)); |
| | |
| | | return msg; |
| | | } |
| | | |
| | | /** |
| | | * HMAC-SHA512 签名计算。 |
| | | * |
| | | * <h3>签名算法</h3> |
| | | * <pre> |
| | | * message = "channel={channelName}&event={event}&time={timeSec}" |
| | | * SIGN = Hex(HmacSHA512(apiSecret(UTF-8), message(UTF-8))) |
| | | * </pre> |
| | | * |
| | | * <h3>错误处理</h3> |
| | | * 签名计算失败时返回空字符串(日志记录错误),不抛异常, |
| | | * 避免阻塞 WebSocket 回调线程。 |
| | | * |
| | | * @param event 事件类型 |
| | | * @param timeSec unix 时间戳(秒) |
| | | * @return 十六进制签名字符串,失败返回 "" |
| | | */ |
| | | private String hs512Sign(String event, long timeSec) { |
| | | try { |
| | | String message = "channel=" + channelName + "&event=" + event + "&time=" + timeSec; |