package com.xcong.excoin.utils.api; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.SerializationFeature; import com.xcong.excoin.utils.api.request.CreateOrderRequest; import com.xcong.excoin.utils.api.request.IntrustOrdersDetailRequest; import com.xcong.excoin.utils.api.response.*; import okhttp3.*; import okhttp3.OkHttpClient.Builder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * API client. * * @Date 2018/1/14 * @Time 16:02 */ public class ApiClient { static final int CONN_TIMEOUT = 50; static final int READ_TIMEOUT = 50; static final int WRITE_TIMEOUT = 50; // static final String API_URL = "https://api.huobi.pro:443"; // static final String API_URL = "https://api.huobi.pro"; // static final String API_URL = "https://api.hbdm.com"; // static final String API_URL = "https://api.huobi.br.com"; // static final String API_URL = "https://api.btcgateway.pro"; static final String API_URL = "https://api.hadax.com"; static final String API_HOST = getHost(); static final MediaType JSON = MediaType.parse("application/json"); static final OkHttpClient client = createOkHttpClient(); final String accessKeyId; final String accessKeySecret; final String assetPassword; /** * 创建一个ApiClient实例 * * @param accessKeyId AccessKeyId * @param accessKeySecret AccessKeySecret */ public ApiClient(String accessKeyId, String accessKeySecret) { this.accessKeyId = accessKeyId; this.accessKeySecret = accessKeySecret; this.assetPassword = null; } /** * 创建一个ApiClient实例 * * @param accessKeyId AccessKeyId * @param accessKeySecret AccessKeySecret * @param assetPassword AssetPassword */ public ApiClient(String accessKeyId, String accessKeySecret, String assetPassword) { this.accessKeyId = accessKeyId; this.accessKeySecret = accessKeySecret; this.assetPassword = assetPassword; } /** * 查询交易对 * * @return List of symbols. */ public List getSymbols() { ApiResponse> resp = get("/v1/common/symbols", null, new TypeReference>>() { }); return resp.checkAndReturn(); } /** * 查询所有账户信息 * * @return List of accounts. */ public List getAccounts() { ApiResponse> resp = get("/v1/account/accounts", null, new TypeReference>>() { }); return resp.checkAndReturn(); } /** * 创建订单 * * @param request CreateOrderRequest object. * @return Order id. */ public Long createOrder(CreateOrderRequest request) { ApiResponse resp = post("/v1/order/orders/place", request, new TypeReference>() { }); return resp.checkAndReturn(); } /** * 执行订单 * * @param orderId The id of created order. * @return Order id. */ public String placeOrder(long orderId) { ApiResponse resp = post("/v1/order/orders/" + orderId + "/place", null, new TypeReference>() { }); return resp.checkAndReturn(); } // ----------------------------------------行情API------------------------------------------- /** * GET /market/history/kline 获取K线数据 * * @param symbol * @param period * @param size * @return */ @SuppressWarnings({"unchecked", "rawtypes"}) public KlineResponse kline(String symbol, String period, String size) { HashMap map = new HashMap(); map.put("symbol", symbol); map.put("period", period); map.put("size", size); KlineResponse resp = get("/market/history/kline", map, new TypeReference>>() { }); return resp; } /** * GET /market/detail/merged 获取聚合行情(Ticker) * * @param symbol * @return */ @SuppressWarnings({"rawtypes", "unchecked"}) public MergedResponse merged(String symbol) { HashMap map = new HashMap(); map.put("symbol", symbol); MergedResponse resp = get("/market/detail/merged", map, new TypeReference>>() { }); return resp; } /** * GET /market/depth 获取 Market Depth 数据 * * @return */ @SuppressWarnings({"rawtypes", "unchecked"}) public DepthResponse depth(String symbol, String type, String depth) { HashMap map = new HashMap(); map.put("symbol", symbol); map.put("type", type); map.put("depth", depth); DepthResponse resp = get("/market/depth", map, new TypeReference>>() { }); return resp; } /** * GET /market/trade 获取 Trade Detail 数据 * * @param symbol * @return */ @SuppressWarnings({"rawtypes", "unchecked"}) public TradeResponse trade(String symbol) { HashMap map = new HashMap(); map.put("symbol", symbol); TradeResponse resp = get("/market/trade", map, new TypeReference() { }); return resp; } /** * GET /market/history/trade 批量获取最近的交易记录 * * @param symbol * @param size * @return */ @SuppressWarnings({"rawtypes", "unchecked"}) public HistoryTradeResponse historyTrade(String symbol, String size) { // System.out.println("symbol = "+symbol+" size = "+size); HashMap map = new HashMap(); map.put("symbol", symbol); map.put("size", size); HistoryTradeResponse resp = get("/market/history/trade", map, new TypeReference() { }); return resp; } /** * GET /market/detail 获取 Market Detail 24小时成交量数据 * * @param symbol * @return */ @SuppressWarnings({"unchecked", "rawtypes"}) public DetailResponse detail(String symbol) { HashMap map = new HashMap(); map.put("symbol", symbol); DetailResponse resp = get("/market/detail", map, new TypeReference>() { }); return resp; } /** * GET /v1/common/symbols 查询系统支持的所有交易对及精度 * * @param symbol * @return */ @SuppressWarnings({"rawtypes", "unchecked"}) public SymbolsResponse symbols(String symbol) { HashMap map = new HashMap(); map.put("symbol", symbol); SymbolsResponse resp = get("/v1/common/symbols", map, new TypeReference>() { }); return resp; } /** * GET /v1/common/currencys 查询系统支持的所有币种 * * @param symbol * @return */ @SuppressWarnings({"rawtypes", "unchecked"}) public CurrencysResponse currencys(String symbol) { HashMap map = new HashMap(); map.put("symbol", symbol); CurrencysResponse resp = get("/v1/common/currencys", map, new TypeReference() { }); return resp; } /** * GET /v1/common/timestamp 查询系统当前时间 * * @return */ public TimestampResponse timestamp() { TimestampResponse resp = get("/v1/common/timestamp", null, new TypeReference() { }); return resp; } /** * GET /v1/account/accounts 查询当前用户的所有账户(即account-id) * * @return */ @SuppressWarnings({"rawtypes"}) public AccountsResponse accounts() { AccountsResponse resp = get("/v1/account/accounts", null, new TypeReference>>() { }); return resp; } /** * GET /v1/account/accounts/{account-id}/balance 查询指定账户的余额 * * @param accountId * @return */ @SuppressWarnings({"rawtypes"}) public BalanceResponse balance(String accountId) { BalanceResponse resp = get("/v1/account/accounts/" + accountId + "/balance", null, new TypeReference>() { }); return resp; } /** * POST /v1/order/orders/{order-id}/submitcancel 申请撤销一个订单请求 * * @param orderId * @return */ public SubmitcancelResponse submitcancel(String orderId) { SubmitcancelResponse resp = post("/v1/order/orders/" + orderId + "/submitcancel", null, new TypeReference() { }); return resp; } /** * POST /v1/order/orders/batchcancel 批量撤销订单 * * @param orderList * @return */ @SuppressWarnings({"rawtypes", "unchecked"}) public BatchcancelResponse submitcancels(List orderList) { Map parameterMap = new HashMap(); parameterMap.put("order-ids", orderList); BatchcancelResponse resp = post("/v1/order/orders/batchcancel", parameterMap, new TypeReference>>>() { }); return resp; } /** * GET /v1/order/orders/{order-id} 查询某个订单详情 * * @param orderId * @return */ @SuppressWarnings({"rawtypes"}) public OrdersDetailResponse ordersDetail(String orderId) { OrdersDetailResponse resp = get("/v1/order/orders/" + orderId, null, new TypeReference() { }); return resp; } /** * GET /v1/order/orders/{order-id}/matchresults 查询某个订单的成交明细 * * @param orderId * @return */ @SuppressWarnings({"rawtypes"}) public MatchresultsOrdersDetailResponse matchresults(String orderId) { MatchresultsOrdersDetailResponse resp = get("/v1/order/orders/" + orderId + "/matchresults", null, new TypeReference() { }); return resp; } @SuppressWarnings({"rawtypes", "unchecked"}) public IntrustDetailResponse intrustOrdersDetail(IntrustOrdersDetailRequest req) { HashMap map = new HashMap(); map.put("symbol", req.symbol); map.put("states", req.states); if (req.startDate != null) { map.put("startDate", req.startDate); } if (req.startDate != null) { map.put("start-date", req.startDate); } if (req.endDate != null) { map.put("end-date", req.endDate); } if (req.types != null) { map.put("types", req.types); } if (req.from != null) { map.put("from", req.from); } if (req.direct != null) { map.put("direct", req.direct); } if (req.size != null) { map.put("size", req.size); } IntrustDetailResponse resp = get("/v1/order/orders/", map, new TypeReference>>() { }); return resp; } // public IntrustDetailResponse getALlOrdersDetail(String orderId) { // IntrustDetailResponse resp = get("/v1/order/orders/"+orderId, null,new TypeReference() {}); // return resp; // } // send a GET request. T get(String uri, Map params, TypeReference ref) { if (params == null) { params = new HashMap<>(); } return call("GET", uri, null, params, ref); } // send a POST request. T post(String uri, Object object, TypeReference ref) { return call("POST", uri, object, new HashMap(), ref); } // call api by endpoint. T call(String method, String uri, Object object, Map params, TypeReference ref) { ApiSignature sign = new ApiSignature(); sign.createSignature(this.accessKeyId, this.accessKeySecret, method, API_HOST, uri, params); try { Request.Builder builder = null; if ("POST".equals(method)) { RequestBody body = RequestBody.create(JSON, JsonUtil.writeValue(object)); builder = new Request.Builder().url(API_URL + uri + "?" + toQueryString(params)).post(body); } else { builder = new Request.Builder().url(API_URL + uri + "?" + toQueryString(params)).get(); } if (this.assetPassword != null) { builder.addHeader("AuthData", authData()); } Request request = builder.build(); Response response = client.newCall(request).execute(); String s = response.body().string(); //System.out.println("-----s:"+JsonUtil.writeValue(s)); return JsonUtil.readValue(s, ref); } catch (IOException e) { // throw new ApiException(e); e.printStackTrace(); return null; } } String authData() { MessageDigest md = null; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } md.update(this.assetPassword.getBytes(StandardCharsets.UTF_8)); md.update("hello, moto".getBytes(StandardCharsets.UTF_8)); Map map = new HashMap<>(); map.put("assetPwd", DatatypeConverter.printHexBinary(md.digest()).toLowerCase()); try { return ApiSignature.urlEncode(JsonUtil.writeValue(map)); } catch (IOException e) { throw new RuntimeException("Get json failed: " + e.getMessage()); } } // Encode as "a=1&b=%20&c=&d=AAA" String toQueryString(Map params) { return String.join("&", params.entrySet().stream().map((entry) -> { return entry.getKey() + "=" + ApiSignature.urlEncode(entry.getValue()); }).collect(Collectors.toList())); } // create OkHttpClient: static OkHttpClient createOkHttpClient() { return new Builder().connectTimeout(CONN_TIMEOUT, TimeUnit.SECONDS) .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS).writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) .build(); } static String getHost() { String host = null; try { host = new URL(API_URL).getHost(); } catch (MalformedURLException e) { System.err.println("parse API_URL error,system exit!,please check API_URL:" + API_URL); System.exit(0); } return host; } } /** * API签名,签名规范: *

* http://docs.aws.amazon.com/zh_cn/general/latest/gr/signature-version-2.html * * @Date 2018/1/14 * @Time 16:02 */ class ApiSignature { final Logger log = LoggerFactory.getLogger(getClass()); static final DateTimeFormatter DT_FORMAT = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss"); static final ZoneId ZONE_GMT = ZoneId.of("Z"); /** * 创建一个有效的签名。该方法为客户端调用,将在传入的params中添加AccessKeyId、Timestamp、SignatureVersion、SignatureMethod、Signature参数。 * * @param appKey AppKeyId. * @param appSecretKey AppKeySecret. * @param method 请求方法,"GET"或"POST" * @param host 请求域名,例如"be.huobi.com" * @param uri 请求路径,注意不含?以及后的参数,例如"/v1/api/info" * @param params 原始请求参数,以Key-Value存储,注意Value不要编码 */ public void createSignature(String appKey, String appSecretKey, String method, String host, String uri, Map params) { StringBuilder sb = new StringBuilder(1024); sb.append(method.toUpperCase()).append('\n') // GET .append(host.toLowerCase()).append('\n') // Host .append(uri).append('\n'); // /path params.remove("Signature"); params.put("AccessKeyId", appKey); params.put("SignatureVersion", "2"); params.put("SignatureMethod", "HmacSHA256"); params.put("Timestamp", gmtNow()); // build signature: SortedMap map = new TreeMap<>(params); for (Map.Entry entry : map.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); sb.append(key).append('=').append(urlEncode(value)).append('&'); } // remove last '&': sb.deleteCharAt(sb.length() - 1); // sign: Mac hmacSha256 = null; try { hmacSha256 = Mac.getInstance("HmacSHA256"); SecretKeySpec secKey = new SecretKeySpec(appSecretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); hmacSha256.init(secKey); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("No such algorithm: " + e.getMessage()); } catch (InvalidKeyException e) { throw new RuntimeException("Invalid key: " + e.getMessage()); } String payload = sb.toString(); byte[] hash = hmacSha256.doFinal(payload.getBytes(StandardCharsets.UTF_8)); String actualSign = Base64.getEncoder().encodeToString(hash); params.put("Signature", actualSign); if (log.isDebugEnabled()) { log.debug("Dump parameters:"); for (Map.Entry entry : params.entrySet()) { log.debug(" key: " + entry.getKey() + ", value: " + entry.getValue()); } } } /** * 使用标准URL Encode编码。注意和JDK默认的不同,空格被编码为%20而不是+。 * * @param s String字符串 * @return URL编码后的字符串 */ public static String urlEncode(String s) { try { return URLEncoder.encode(s, "UTF-8").replaceAll("\\+", "%20"); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException("UTF-8 encoding not supported!"); } } /** * Return epoch seconds */ long epochNow() { return Instant.now().getEpochSecond(); } String gmtNow() { return Instant.ofEpochSecond(epochNow()).atZone(ZONE_GMT).format(DT_FORMAT); } } class JsonUtil { public static String writeValue(Object obj) throws IOException { return objectMapper.writeValueAsString(obj); } public static T readValue(String s, TypeReference ref) throws IOException { return objectMapper.readValue(s, ref); } static final ObjectMapper objectMapper = createObjectMapper(); static ObjectMapper createObjectMapper() { final ObjectMapper mapper = new ObjectMapper(); mapper.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE); mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); // disabled features: mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return mapper; } }