From cc8a2a6c3c6cbb533d17249be0156807dba39aae Mon Sep 17 00:00:00 2001 From: JiyangTang Date: Wed, 28 Aug 2024 10:15:08 +0800 Subject: [PATCH] add binance sdk --- baogutang-admin/pom.xml | 15 ++ .../admin/config/SpotClientConfiguration.java | 34 +++ .../admin/config/WebSocketConfig.java | 24 ++ .../admin/controller/StaticController.java | 2 +- .../controller/VirtualCoinController.java | 48 ++++ .../admin/domain/res/MarketCandlesRes.java | 68 ++++++ .../admin/handlers/KlineWebSocketHandler.java | 98 ++++++++ .../schedule/OkCoinMarketCandlesHandler.java | 122 ++++++++++ .../admin/services/IVirtualCoinService.java | 16 ++ .../services/impl/VirtualCoinServiceImpl.java | 48 ++++ .../admin/utils/DingTalkMsgPushUtils.java | 24 ++ .../admin/utils/OkCoinKLineUtil.java | 49 ++++ .../src/main/resources/logback-spring.xml | 52 +---- .../src/main/resources/templates/coin.html | 219 ++++++++++++++++++ .../business/admin/job/MarketCandlesTest.java | 28 +++ .../src/main/resources/logback-spring.xml | 42 +--- baogutang-common/pom.xml | 3 + .../common/config/GlobalCorsConfig.java | 2 +- .../common/domain/BinanceResults.java | 24 ++ .../baogutang/common/domain/PageParam.java | 21 ++ .../properties/MarketCandlersProperties.java | 25 ++ .../common/utils/MyBatisPlusPageUtil.java | 53 +++++ .../src/main/resources/logback-spring.xml | 42 +--- 23 files changed, 933 insertions(+), 126 deletions(-) create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/config/SpotClientConfiguration.java create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/config/WebSocketConfig.java create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/controller/VirtualCoinController.java create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/domain/res/MarketCandlesRes.java create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/handlers/KlineWebSocketHandler.java create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/schedule/OkCoinMarketCandlesHandler.java create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/services/IVirtualCoinService.java create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/services/impl/VirtualCoinServiceImpl.java create mode 100644 baogutang-admin/src/main/java/top/baogutang/admin/utils/OkCoinKLineUtil.java create mode 100644 baogutang-admin/src/main/resources/templates/coin.html create mode 100644 baogutang-admin/src/test/java/top/baogutang/business/admin/job/MarketCandlesTest.java create mode 100644 baogutang-common/src/main/java/top/baogutang/common/domain/BinanceResults.java create mode 100644 baogutang-common/src/main/java/top/baogutang/common/properties/MarketCandlersProperties.java diff --git a/baogutang-admin/pom.xml b/baogutang-admin/pom.xml index a4aca72..e756754 100644 --- a/baogutang-admin/pom.xml +++ b/baogutang-admin/pom.xml @@ -114,6 +114,21 @@ xxl-job-core 2.2.0 + + org.jfree + jfreechart + 1.5.3 + + + + io.github.binance + binance-connector-java + 3.2.0 + + + org.springframework.boot + spring-boot-starter-websocket + diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/config/SpotClientConfiguration.java b/baogutang-admin/src/main/java/top/baogutang/admin/config/SpotClientConfiguration.java new file mode 100644 index 0000000..288d156 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/config/SpotClientConfiguration.java @@ -0,0 +1,34 @@ +package top.baogutang.admin.config; + +import com.binance.connector.client.SpotClient; +import com.binance.connector.client.WebSocketStreamClient; +import com.binance.connector.client.impl.SpotClientImpl; +import com.binance.connector.client.impl.WebSocketStreamClientImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import top.baogutang.common.properties.MarketCandlersProperties; + +import javax.annotation.Resource; + +/** + * @description: + * @author: nikooh + * @date: 2024/08/23 : 15:45 + */ +@Configuration +public class SpotClientConfiguration { + + @Resource + private MarketCandlersProperties marketCandlersProperties; + + @Bean(name = "spotClient") + public SpotClient spotClient() { + return new SpotClientImpl(marketCandlersProperties.getApiKey(), + marketCandlersProperties.getApiSecret()); + } + + @Bean(name = "webSocketStreamClient") + public WebSocketStreamClient webSocketStreamClient() { + return new WebSocketStreamClientImpl(); + } +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/config/WebSocketConfig.java b/baogutang-admin/src/main/java/top/baogutang/admin/config/WebSocketConfig.java new file mode 100644 index 0000000..d53b317 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/config/WebSocketConfig.java @@ -0,0 +1,24 @@ +package top.baogutang.admin.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import top.baogutang.admin.handlers.KlineWebSocketHandler; + +import javax.annotation.Resource; + +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + + @Resource + private KlineWebSocketHandler klineWebSocketHandler; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(klineWebSocketHandler, "/ws") + // 配置 WebSocket 端点,并允许跨域 + .setAllowedOrigins("*"); + } +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/controller/StaticController.java b/baogutang-admin/src/main/java/top/baogutang/admin/controller/StaticController.java index 30ad6b8..acc9b28 100644 --- a/baogutang-admin/src/main/java/top/baogutang/admin/controller/StaticController.java +++ b/baogutang-admin/src/main/java/top/baogutang/admin/controller/StaticController.java @@ -15,6 +15,6 @@ public class StaticController { @RequestMapping public String viewJsonParseHtml() { // 这里返回的字符串是HTML文件名(不包括扩展名) - return "json-parse"; + return "coin"; } } diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/controller/VirtualCoinController.java b/baogutang-admin/src/main/java/top/baogutang/admin/controller/VirtualCoinController.java new file mode 100644 index 0000000..727ada2 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/controller/VirtualCoinController.java @@ -0,0 +1,48 @@ +package top.baogutang.admin.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import top.baogutang.admin.domain.res.MarketCandlesRes; +import top.baogutang.admin.services.IVirtualCoinService; +import top.baogutang.common.domain.Results; + +import javax.annotation.Resource; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @description: + * @author: nikooh + * @date: 2024/08/23 : 17:29 + */ +@RestController +@RequestMapping("/api/v1/admin/virtualCoin") +public class VirtualCoinController { + + @Resource + private IVirtualCoinService virtualCoinService; + + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + @GetMapping("/uiKline") + public Results> uiKline(@RequestParam(name = "symbol") String symbol, + @RequestParam(name = "interval") String interval) { + return Results.ok(virtualCoinService.uiKline(symbol, interval)); + } + + @GetMapping("/uiKlineData") + public Results> uiKlineData(@RequestParam(name = "symbol") String symbol, + @RequestParam(name = "interval") String interval) { + List kLineDataList = virtualCoinService.uiKline(symbol, interval).stream() + .sorted(Collections.reverseOrder(Comparator.comparingLong(data -> Long.parseLong(data[0])))) + .limit(5) + .map(data -> MarketCandlesRes.KLineData.newInstance(data, DATE_FORMAT)) + .collect(Collectors.toList()); + return Results.ok(kLineDataList); + } +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/domain/res/MarketCandlesRes.java b/baogutang-admin/src/main/java/top/baogutang/admin/domain/res/MarketCandlesRes.java new file mode 100644 index 0000000..ab5703d --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/domain/res/MarketCandlesRes.java @@ -0,0 +1,68 @@ +package top.baogutang.admin.domain.res; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +/** + * @description: + * @author: nikooh + * @date: 2024/08/22 : 10:06 + */ +@Data +public class MarketCandlesRes implements Serializable { + + private static final long serialVersionUID = -3375737475097909682L; + + @JsonProperty("from") + private String from; + + @JsonProperty("klines") + private List kLineDataList; + + + @Data + public static class KLineData implements Serializable { + + private static final long serialVersionUID = 522558542163537197L; + + + // 开盘价格 + @JsonProperty("开盘") + private BigDecimal openPrice; + + // 最高价格 + @JsonProperty("最高") + private BigDecimal highestPrice; + + // 最低价格 + @JsonProperty("最低") + private BigDecimal lowestPrice; + + // 收盘价格 + @JsonProperty("收盘") + private BigDecimal closePrice; + + // K线 + @JsonProperty("timestamp") + private Long timestamp; + + @JsonProperty("时间") + private String date; + + public static KLineData newInstance(String[] data, SimpleDateFormat dateFormat) { + KLineData kLineData = new KLineData(); + kLineData.setOpenPrice(new BigDecimal(data[1])); + kLineData.setHighestPrice(new BigDecimal(data[2])); + kLineData.setLowestPrice(new BigDecimal(data[3])); + kLineData.setClosePrice(new BigDecimal(data[4])); + kLineData.setDate(dateFormat.format(new Date(Long.parseLong(data[0])))); + return kLineData; + } + } +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/handlers/KlineWebSocketHandler.java b/baogutang-admin/src/main/java/top/baogutang/admin/handlers/KlineWebSocketHandler.java new file mode 100644 index 0000000..bebcf8f --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/handlers/KlineWebSocketHandler.java @@ -0,0 +1,98 @@ +package top.baogutang.admin.handlers; + +import com.binance.connector.client.WebSocketStreamClient; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.web.socket.*; +import top.baogutang.common.utils.JacksonUtil; + +import javax.annotation.Resource; +import java.io.IOException; +import java.io.Serializable; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +public class KlineWebSocketHandler implements WebSocketHandler { + + @Resource + private WebSocketStreamClient webSocketStreamClient; + + private final Map webSocketMap = new ConcurrentHashMap<>(); + + private volatile Integer connectionId; + + @Override + public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception { + //连接成功时调用该方法 + log.info(">>>>>>>>>>WebSocket connected:{}<<<<<<<<<<", webSocketSession.getId()); + webSocketMap.put(webSocketSession.getId(), webSocketSession); + } + + @Override + public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage webSocketMessage) throws Exception { + // 获取客户端发送的消息 + log.info(">>>>>>>>>>客户端ID:{} 发送消息:{}<<<<<<<<<<", webSocketSession.getId(), webSocketMessage.getPayload()); + KlineMessage message = JacksonUtil.fromJson(webSocketMessage.getPayload().toString(), KlineMessage.class); + if (Objects.nonNull(connectionId)) { + webSocketStreamClient.closeConnection(connectionId); + } + // 使用 Binance Connector 开启 WebSocket 连接 + connectionId = webSocketStreamClient.klineStream(message.getSymbol(), message.getInterval(), event -> { + log.info(">>>>>>>>>>binance event:{}<<<<<<<<<<", event); + // 将从 Binance 接收到的数据转发给前端 + TextMessage textMessage = new TextMessage(event); + + // 推送消息给所有连接的客户端 + webSocketMap.entrySet() + .stream() + .filter(entry -> entry.getValue().isOpen()) + .forEach(entry -> { + try { + entry.getValue().sendMessage(textMessage); + } catch (IOException e) { + log.error(">>>>>>>>>>消息发送错误:{}<<<<<<<<<<", e.getMessage(), e); + } + }); + }); + log.info(">>>>>>>>>启动了 Binance WebSocket 连接: {} , 监控交易对: {} 时间间隔: {}<<<<<<<<", connectionId, message.getSymbol(), message.getInterval()); + } + + @Override + public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception { + //发生错误时调用该方法 + log.error(">>>>>>>>>>WebSocket error: {}<<<<<<<<<<", throwable.getMessage(), throwable); + webSocketSession.close(CloseStatus.SERVER_ERROR); + webSocketMap.remove(webSocketSession.getId()); + } + + @Override + public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception { + //连接关闭时调用该方法 + log.info(">>>>>>>>>>WebSocket closed:{}<<<<<<<<<<", webSocketSession.getId()); + webSocketMap.remove(webSocketSession.getId()); + if (CollectionUtils.isEmpty(webSocketMap)) { + log.info(">>>>>>>>>>client session all closed!<<<<<<<<<<"); + webSocketStreamClient.closeAllConnections(); + } + } + + @Override + public boolean supportsPartialMessages() { + return false; + } + + @Data + public static class KlineMessage implements Serializable { + + private static final long serialVersionUID = -3288071423561766084L; + + private String symbol; + + private String interval; + } +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/schedule/OkCoinMarketCandlesHandler.java b/baogutang-admin/src/main/java/top/baogutang/admin/schedule/OkCoinMarketCandlesHandler.java new file mode 100644 index 0000000..8f274e5 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/schedule/OkCoinMarketCandlesHandler.java @@ -0,0 +1,122 @@ +package top.baogutang.admin.schedule; + +import cn.hutool.core.bean.BeanUtil; +import com.binance.connector.client.SpotClient; +import com.fasterxml.jackson.core.type.TypeReference; +import com.xxl.job.core.biz.model.ReturnT; +import com.xxl.job.core.handler.IJobHandler; +import com.xxl.job.core.handler.annotation.XxlJob; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.stereotype.Component; +import top.baogutang.admin.utils.DingTalkMsgPushUtils; +import top.baogutang.admin.utils.OkCoinKLineUtil; +import top.baogutang.common.domain.BinanceResults; +import top.baogutang.common.utils.JacksonUtil; + +import javax.annotation.Resource; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @description: OKCOIN K线数据 + * @author: nikooh + * @date: 2024/08/22 : 09:52 + */ +@Slf4j +@Component +@RefreshScope +public class OkCoinMarketCandlesHandler extends IJobHandler { + + @Resource + private DingTalkMsgPushUtils dingTalkMsgPushUtils; + + @Resource + private SpotClient spotClient; + + public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS"); + + @Override + @XxlJob("marketCandlesHandler") + public ReturnT execute(String params) throws Exception { + + + if (StringUtils.isBlank(params)) { + log.error(">>>>>>>>>>job params is null!<<<<<<<<<<"); + return ReturnT.FAIL; + } + JobParams jobParams = JacksonUtil.fromJson(params, JobParams.class); + if (Objects.isNull(jobParams)) { + log.error(">>>>>>>>>>job params is null!<<<<<<<<<<"); + return ReturnT.FAIL; + } + String interval = jobParams.getInterval(); + Integer limit = jobParams.getLimit(); + Arrays.stream(jobParams.getSymbolList().split(",")) + .forEach(symbol -> { + KLinesRequestParameters kLinesRequestParameters = new KLinesRequestParameters(symbol, interval, limit); + Map parameters = BeanUtil.beanToMap(kLinesRequestParameters); + String resultStr = spotClient.createMarket().uiKlines(parameters); + if (StringUtils.isBlank(resultStr)) { + log.error(">>>>>>>>>>request result is null!<<<<<<<<<<"); + return; + } + List kLineDataList = JacksonUtil.fromJson(resultStr, new TypeReference>() { + }); + String[] kLineData = kLineDataList.get(0); + log.info(">>>>>>>>>>当前:【{}】 开盘价:【{}】 最高价:【{}】最低价:【{}】收盘价:【{}】<<<<<<<<<<", DATE_FORMAT.format(new Date(Long.parseLong(kLineData[0]))), kLineData[1], kLineData[2], kLineData[3], kLineData[4]); + kLineDataList = kLineDataList.stream() + .sorted(Comparator.comparing(data -> data[0], Comparator.reverseOrder())) + .limit(5) + .collect(Collectors.toList()); + String markdownContent = OkCoinKLineUtil.getCoinMarkdownContent(symbol, kLineDataList); + dingTalkMsgPushUtils.robotMarkdownMsg("交易产品K线数据", markdownContent); + + }); + + + return ReturnT.SUCCESS; + } + + @Data + public static class JobParams { + + // BTCUSDT,ETHUSDT + private String symbolList; + + // 时间粒度 + // 1s, 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M + private String interval; + + // 默认 500; 最大值 1000 + private Integer limit; + } + + @Data + public static class KLinesRequestParameters { + + // BTCUSDT,ETHUSDT + private String symbol; + + // 1s, 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M + private String interval; + + // 默认 500; 最大值 1000 + private Integer limit; + + public KLinesRequestParameters(String symbol, String interval, Integer limit) { + this.symbol = symbol; + this.interval = interval; + this.limit = limit; + } + + public KLinesRequestParameters(String symbol, String interval) { + this.symbol = symbol; + this.interval = interval; + } + } + +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/services/IVirtualCoinService.java b/baogutang-admin/src/main/java/top/baogutang/admin/services/IVirtualCoinService.java new file mode 100644 index 0000000..a66219e --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/services/IVirtualCoinService.java @@ -0,0 +1,16 @@ +package top.baogutang.admin.services; + +import java.util.List; + +/** + * @description: + * @author: nikooh + * @date: 2024/08/23 : 17:50 + */ +public interface IVirtualCoinService { + + List uiKline(String symbol, String interval); + +// void startWebSocket(String symbol, String interval); + +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/services/impl/VirtualCoinServiceImpl.java b/baogutang-admin/src/main/java/top/baogutang/admin/services/impl/VirtualCoinServiceImpl.java new file mode 100644 index 0000000..1f456d5 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/services/impl/VirtualCoinServiceImpl.java @@ -0,0 +1,48 @@ +package top.baogutang.admin.services.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.binance.connector.client.SpotClient; +import com.binance.connector.client.WebSocketStreamClient; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import top.baogutang.admin.schedule.OkCoinMarketCandlesHandler; +import top.baogutang.admin.services.IVirtualCoinService; +import top.baogutang.common.utils.JacksonUtil; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * @description: + * @author: nikooh + * @date: 2024/08/23 : 17:50 + */ +@Slf4j +@Service +public class VirtualCoinServiceImpl implements IVirtualCoinService { + + @Resource + private SpotClient spotClient; + + @Resource + private WebSocketStreamClient webSocketStreamClient; + + + @Override + public List uiKline(String symbol, String interval) { + OkCoinMarketCandlesHandler.KLinesRequestParameters kLinesRequestParameters = new OkCoinMarketCandlesHandler.KLinesRequestParameters(symbol, interval); + Map parameters = BeanUtil.beanToMap(kLinesRequestParameters, Boolean.FALSE, Boolean.TRUE); + String resultStr = spotClient.createMarket().uiKlines(parameters); + if (StringUtils.isBlank(resultStr)) { + log.error(">>>>>>>>>>request result is null!<<<<<<<<<<"); + return Collections.emptyList(); + } + return JacksonUtil.fromJson(resultStr, new TypeReference>() { + }); + } + +} diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/utils/DingTalkMsgPushUtils.java b/baogutang-admin/src/main/java/top/baogutang/admin/utils/DingTalkMsgPushUtils.java index 4bd6f8c..c46af9d 100644 --- a/baogutang-admin/src/main/java/top/baogutang/admin/utils/DingTalkMsgPushUtils.java +++ b/baogutang-admin/src/main/java/top/baogutang/admin/utils/DingTalkMsgPushUtils.java @@ -161,6 +161,30 @@ public class DingTalkMsgPushUtils { log.info(">>>>>>>>>>robot msg send request:{}, response:{}<<<<<<<<<<", JacksonUtil.toJson(request), JacksonUtil.toJson(response)); } + public void robotMarkdownMsg(String markdownTitle, String markdownContent) { + // 计算签名 + long timestamp = System.currentTimeMillis(); + String sign = this.sign(timestamp); + String url = robotWebhookUrl + "×tamp=" + timestamp + "&sign=" + sign; + DingTalkClient client = new DefaultDingTalkClient(url); + OapiRobotSendRequest request = new OapiRobotSendRequest(); + OapiRobotSendRequest.At at = new OapiRobotSendRequest.At(); + at.setIsAtAll(false); + request.setAt(at); + request.setMsgtype(DingTalkMsgTypeEnum.MARKDOWN.getType()); + OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); + markdown.setTitle(markdownTitle); + markdown.setText(markdownContent); + request.setMarkdown(markdown); + OapiRobotSendResponse response = null; + try { + response = client.execute(request); + } catch (Exception e) { + log.error(">>>>>>>>>>robot msg send error:{}<<<<<<<<<<", e.getMessage(), e); + } + log.info(">>>>>>>>>>robot msg send request:{}, response:{}<<<<<<<<<<", JacksonUtil.toJson(request), JacksonUtil.toJson(response)); + } + private String sign(Long timestamp) { try { String stringToSign = timestamp + "\n" + secret; diff --git a/baogutang-admin/src/main/java/top/baogutang/admin/utils/OkCoinKLineUtil.java b/baogutang-admin/src/main/java/top/baogutang/admin/utils/OkCoinKLineUtil.java new file mode 100644 index 0000000..653fbd6 --- /dev/null +++ b/baogutang-admin/src/main/java/top/baogutang/admin/utils/OkCoinKLineUtil.java @@ -0,0 +1,49 @@ +package top.baogutang.admin.utils; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +/** + * @description: + * @author: nikooh + * @date: 2024/08/22 : 10:42 + */ +@Slf4j +public class OkCoinKLineUtil { + + public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + public static String getCoinMarkdownContent(String instId, List candleDataList) { + if (CollectionUtils.isEmpty(candleDataList)) { + return null; + } + StringBuilder markdownBuilder = new StringBuilder(); + + // 添加标题 + markdownBuilder.append("## ") + .append(instId) + .append("市场价格数据\n\n"); + + // 添加表头 + markdownBuilder.append("| 时间 | 开盘价 | 最高价 | 最低价 | 收盘价 |\n"); + markdownBuilder.append("|--------------------------------|----------------------------|---------------------------|---------------------------|---------------------------|\n"); + + // 添加每一行数据 + for (String[] candle : candleDataList) { + markdownBuilder.append("| ") + .append(DATE_FORMAT.format(new Date(Long.parseLong(candle[0])))).append(" | ") + .append(String.format("%-30f", Double.parseDouble(candle[1]))).append(" | ") + .append(String.format("%-24f", Double.parseDouble(candle[2]))).append(" | ") + .append(String.format("%-24f", Double.parseDouble(candle[3]))).append(" | ") + .append(String.format("%-24f", Double.parseDouble(candle[4]))).append(" |\n"); + } + + return markdownBuilder.toString(); + } + + +} diff --git a/baogutang-admin/src/main/resources/logback-spring.xml b/baogutang-admin/src/main/resources/logback-spring.xml index e040eb0..6acc9e5 100644 --- a/baogutang-admin/src/main/resources/logback-spring.xml +++ b/baogutang-admin/src/main/resources/logback-spring.xml @@ -11,7 +11,7 @@ - + ${log.path}/app.log ${log.path}/%d{yyyy-MM-dd,aux}/app-%d{yyyy-MM-dd}.%i.log @@ -27,11 +27,11 @@ - - 10000 - 0 - - + + + + + @@ -43,42 +43,6 @@ - - - - cn-shanghai.log.aliyuncs.com - LTAI5tRN9T5Tz1QExcSpUaBc - LniIMK15XEOc6Nn5mOrtX399FkVfQd - baogutang - ${appEnv} - - - ${appId} - - - - 3000 - 4096 - 3145728 - 104857600 - 3 - 8 - - - Asia/Shanghai - - yyyy-MM-dd HH:mm:ss.SSS - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{X-Request-Id}] [%thread] %logger{0}: %msg - - - - - INFO - - @@ -100,14 +64,14 @@ - + - + diff --git a/baogutang-admin/src/main/resources/templates/coin.html b/baogutang-admin/src/main/resources/templates/coin.html new file mode 100644 index 0000000..a7b1fb9 --- /dev/null +++ b/baogutang-admin/src/main/resources/templates/coin.html @@ -0,0 +1,219 @@ + + + + + + K-LINE + + + + + +

K-LINE

+ + +
+ + + + + + + +
+ + +
+ + + + diff --git a/baogutang-admin/src/test/java/top/baogutang/business/admin/job/MarketCandlesTest.java b/baogutang-admin/src/test/java/top/baogutang/business/admin/job/MarketCandlesTest.java new file mode 100644 index 0000000..f1fabb6 --- /dev/null +++ b/baogutang-admin/src/test/java/top/baogutang/business/admin/job/MarketCandlesTest.java @@ -0,0 +1,28 @@ +package top.baogutang.business.admin.job; + +import com.xxl.job.core.biz.model.ReturnT; +import org.junit.Assert; +import org.junit.Test; +import top.baogutang.admin.schedule.OkCoinMarketCandlesHandler; +import top.baogutang.business.admin.BaoGuTangAdminAbstractTest; + +import javax.annotation.Resource; + +import static com.xxl.job.core.biz.model.ReturnT.SUCCESS_CODE; + +/** + * @description: + * @author: nikooh + * @date: 2024/08/22 : 11:18 + */ +public class MarketCandlesTest extends BaoGuTangAdminAbstractTest { + + @Resource + private OkCoinMarketCandlesHandler okCoinMarketCandlesHandler; + + @Test + public void testMarketCandles() throws Exception { + ReturnT returnT = okCoinMarketCandlesHandler.execute("{\"onlyKey\":\"Bitcoin\",\"bar\":\"kline_1m\",\"sign\":\"marketCap\"}"); + Assert.assertEquals(SUCCESS_CODE, returnT.getCode()); + } +} diff --git a/baogutang-business/src/main/resources/logback-spring.xml b/baogutang-business/src/main/resources/logback-spring.xml index e040eb0..9ce5441 100644 --- a/baogutang-business/src/main/resources/logback-spring.xml +++ b/baogutang-business/src/main/resources/logback-spring.xml @@ -11,7 +11,7 @@
- + ${log.path}/app.log ${log.path}/%d{yyyy-MM-dd,aux}/app-%d{yyyy-MM-dd}.%i.log @@ -43,43 +43,6 @@ - - - - cn-shanghai.log.aliyuncs.com - LTAI5tRN9T5Tz1QExcSpUaBc - LniIMK15XEOc6Nn5mOrtX399FkVfQd - baogutang - ${appEnv} - - - ${appId} - - - - 3000 - 4096 - 3145728 - 104857600 - 3 - 8 - - - Asia/Shanghai - - yyyy-MM-dd HH:mm:ss.SSS - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{X-Request-Id}] [%thread] %logger{0}: %msg - - - - - INFO - - - @@ -100,14 +63,13 @@ - + - diff --git a/baogutang-common/pom.xml b/baogutang-common/pom.xml index cf97776..b44fdb6 100644 --- a/baogutang-common/pom.xml +++ b/baogutang-common/pom.xml @@ -170,6 +170,9 @@ 1.4.2 compile + + + \ No newline at end of file diff --git a/baogutang-common/src/main/java/top/baogutang/common/config/GlobalCorsConfig.java b/baogutang-common/src/main/java/top/baogutang/common/config/GlobalCorsConfig.java index b6966f7..7ff71ea 100644 --- a/baogutang-common/src/main/java/top/baogutang/common/config/GlobalCorsConfig.java +++ b/baogutang-common/src/main/java/top/baogutang/common/config/GlobalCorsConfig.java @@ -20,7 +20,7 @@ public class GlobalCorsConfig { //重写父类提供的跨域请求处理的接口 public void addCorsMappings(CorsRegistry registry) { //添加映射路径 - registry.addMapping("/api") + registry.addMapping("/api/**") //放行哪些原始域 .allowedOrigins("*") //是否发送Cookie信息 diff --git a/baogutang-common/src/main/java/top/baogutang/common/domain/BinanceResults.java b/baogutang-common/src/main/java/top/baogutang/common/domain/BinanceResults.java new file mode 100644 index 0000000..9928c32 --- /dev/null +++ b/baogutang-common/src/main/java/top/baogutang/common/domain/BinanceResults.java @@ -0,0 +1,24 @@ +package top.baogutang.common.domain; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @description: + * @author: nikooh + * @date: 2024/08/23 : 16:00 + */ +@Data +public class BinanceResults implements Serializable { + + private static final long serialVersionUID = 4314677378175006552L; + + public static final int SUCCESS_CODE = 200; + + private String id; + + private Integer status; + + private T result; +} diff --git a/baogutang-common/src/main/java/top/baogutang/common/domain/PageParam.java b/baogutang-common/src/main/java/top/baogutang/common/domain/PageParam.java index d9f2616..a4b6c1a 100644 --- a/baogutang-common/src/main/java/top/baogutang/common/domain/PageParam.java +++ b/baogutang-common/src/main/java/top/baogutang/common/domain/PageParam.java @@ -1,8 +1,12 @@ package top.baogutang.common.domain; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; /** * @description: @@ -18,6 +22,23 @@ public class PageParam implements Serializable { private Integer pageSize; + @ApiModelProperty("排序条件") + private List orders = new ArrayList<>(); + + @ApiModel + @Data + public static class OrderBy implements Serializable { + + private static final long serialVersionUID = -2936335557980068706L; + + @ApiModelProperty("排序列名") + private String columnName; + + @ApiModelProperty("是否降序") + private boolean desc; + + } + public Integer getPageNum() { pageNum = pageNum == null ? 1 : pageNum; return pageNum; diff --git a/baogutang-common/src/main/java/top/baogutang/common/properties/MarketCandlersProperties.java b/baogutang-common/src/main/java/top/baogutang/common/properties/MarketCandlersProperties.java new file mode 100644 index 0000000..b942645 --- /dev/null +++ b/baogutang-common/src/main/java/top/baogutang/common/properties/MarketCandlersProperties.java @@ -0,0 +1,25 @@ +package top.baogutang.common.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.stereotype.Component; + +/** + * @description: + * @author: nikooh + * @date: 2024/08/22 : 09:57 + */ +@Data +@Component +@RefreshScope +@ConfigurationProperties(prefix = "baogutang.coin") +public class MarketCandlersProperties { + + private String okCoinMarketCandlesUrl = "https://i.bicoin.com.cn/data/getKlineByOnlyKey"; + + private String apiKey = "xQIw2QFYG817KN9QiRQ76E1ThrBS2vAKJZTyGE5O3acX47Dr21I6QFHNMZbHWzE9"; + + private String apiSecret = "mJvcuTz57Qu4hHjZQLwMJhXASInosPXM4181x6UnofCdLlS3ROrFCRw9GLNucSTt"; + +} diff --git a/baogutang-common/src/main/java/top/baogutang/common/utils/MyBatisPlusPageUtil.java b/baogutang-common/src/main/java/top/baogutang/common/utils/MyBatisPlusPageUtil.java index 12bd305..b245137 100644 --- a/baogutang-common/src/main/java/top/baogutang/common/utils/MyBatisPlusPageUtil.java +++ b/baogutang-common/src/main/java/top/baogutang/common/utils/MyBatisPlusPageUtil.java @@ -1,5 +1,9 @@ package top.baogutang.common.utils; +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.extern.slf4j.Slf4j; import top.baogutang.common.domain.PageParam; @@ -42,4 +46,53 @@ public class MyBatisPlusPageUtil { return new PageUtil<>(pageRes.getCurrent(), pageRes.getSize(), pageRes.getTotal(), pageRes.getPages(), rPageRes); } + /** + * 默认排序 + * + * @param orders 排序条件 + * @param queryWrapper 查询条件 + * @param T + */ + public static void defaultOrders(List orders, QueryWrapper queryWrapper) { + defaultOrder(orders, queryWrapper, null); + } + + /** + * 默认排序,可以指定默认排序字段 + * + * @param orders 排序条件 + * @param queryWrapper 查询条件 + * @param defaultColumnName 指定默认排序字段列 + * @param T + */ + public static void defaultOrder(List orders, QueryWrapper queryWrapper, SFunction defaultColumnName) { + if (CollUtil.isEmpty(orders)) { + defaultOrderProcess(queryWrapper, defaultColumnName); + return; + } + orders.forEach(orderBy -> { + //默认时间倒序 + if (StringUtils.isEmpty(orderBy.getColumnName())) { + defaultOrderProcess(queryWrapper, defaultColumnName); + } else { + if (orderBy.isDesc()) { + queryWrapper.orderByDesc(orderBy.getColumnName()); + } else { + queryWrapper.orderByAsc(orderBy.getColumnName()); + } + } + }); + } + + /** + * @param queryWrapper 查询条件 + * @param defaultColumnName 默认排序列名 + * @param t + * @param r + */ + private static void defaultOrderProcess(QueryWrapper queryWrapper, SFunction defaultColumnName) { + if (defaultColumnName != null) { + queryWrapper.lambda().orderByDesc(defaultColumnName); + } + } } diff --git a/baogutang-generate/src/main/resources/logback-spring.xml b/baogutang-generate/src/main/resources/logback-spring.xml index e040eb0..9ce5441 100644 --- a/baogutang-generate/src/main/resources/logback-spring.xml +++ b/baogutang-generate/src/main/resources/logback-spring.xml @@ -11,7 +11,7 @@ - + ${log.path}/app.log ${log.path}/%d{yyyy-MM-dd,aux}/app-%d{yyyy-MM-dd}.%i.log @@ -43,43 +43,6 @@ - - - - cn-shanghai.log.aliyuncs.com - LTAI5tRN9T5Tz1QExcSpUaBc - LniIMK15XEOc6Nn5mOrtX399FkVfQd - baogutang - ${appEnv} - - - ${appId} - - - - 3000 - 4096 - 3145728 - 104857600 - 3 - 8 - - - Asia/Shanghai - - yyyy-MM-dd HH:mm:ss.SSS - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{X-Request-Id}] [%thread] %logger{0}: %msg - - - - - INFO - - - @@ -100,14 +63,13 @@ - + -