add binance sdk
This commit is contained in:
parent
46da669271
commit
cc8a2a6c3c
@ -114,6 +114,21 @@
|
|||||||
<artifactId>xxl-job-core</artifactId>
|
<artifactId>xxl-job-core</artifactId>
|
||||||
<version>2.2.0</version>
|
<version>2.2.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jfree</groupId>
|
||||||
|
<artifactId>jfreechart</artifactId>
|
||||||
|
<version>1.5.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.binance</groupId>
|
||||||
|
<artifactId>binance-connector-java</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<resources>
|
<resources>
|
||||||
|
|||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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("*");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,6 +15,6 @@ public class StaticController {
|
|||||||
@RequestMapping
|
@RequestMapping
|
||||||
public String viewJsonParseHtml() {
|
public String viewJsonParseHtml() {
|
||||||
// 这里返回的字符串是HTML文件名(不包括扩展名)
|
// 这里返回的字符串是HTML文件名(不包括扩展名)
|
||||||
return "json-parse";
|
return "coin";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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<List<String[]>> uiKline(@RequestParam(name = "symbol") String symbol,
|
||||||
|
@RequestParam(name = "interval") String interval) {
|
||||||
|
return Results.ok(virtualCoinService.uiKline(symbol, interval));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/uiKlineData")
|
||||||
|
public Results<List<MarketCandlesRes.KLineData>> uiKlineData(@RequestParam(name = "symbol") String symbol,
|
||||||
|
@RequestParam(name = "interval") String interval) {
|
||||||
|
List<MarketCandlesRes.KLineData> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<KLineData> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String, WebSocketSession> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String> 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<String, Object> parameters = BeanUtil.beanToMap(kLinesRequestParameters);
|
||||||
|
String resultStr = spotClient.createMarket().uiKlines(parameters);
|
||||||
|
if (StringUtils.isBlank(resultStr)) {
|
||||||
|
log.error(">>>>>>>>>>request result is null!<<<<<<<<<<");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String[]> kLineDataList = JacksonUtil.fromJson(resultStr, new TypeReference<List<String[]>>() {
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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<String[]> uiKline(String symbol, String interval);
|
||||||
|
|
||||||
|
// void startWebSocket(String symbol, String interval);
|
||||||
|
|
||||||
|
}
|
||||||
@ -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<String[]> uiKline(String symbol, String interval) {
|
||||||
|
OkCoinMarketCandlesHandler.KLinesRequestParameters kLinesRequestParameters = new OkCoinMarketCandlesHandler.KLinesRequestParameters(symbol, interval);
|
||||||
|
Map<String, Object> 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<List<String[]>>() {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -161,6 +161,30 @@ public class DingTalkMsgPushUtils {
|
|||||||
log.info(">>>>>>>>>>robot msg send request:{}, response:{}<<<<<<<<<<", JacksonUtil.toJson(request), JacksonUtil.toJson(response));
|
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) {
|
private String sign(Long timestamp) {
|
||||||
try {
|
try {
|
||||||
String stringToSign = timestamp + "\n" + secret;
|
String stringToSign = timestamp + "\n" + secret;
|
||||||
|
|||||||
@ -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<String[]> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -11,7 +11,7 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="localFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<file>${log.path}/app.log</file>
|
<file>${log.path}/app.log</file>
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
<fileNamePattern>${log.path}/%d{yyyy-MM-dd,aux}/app-%d{yyyy-MM-dd}.%i.log
|
<fileNamePattern>${log.path}/%d{yyyy-MM-dd,aux}/app-%d{yyyy-MM-dd}.%i.log
|
||||||
@ -27,11 +27,11 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<appender name="asyncFileAppender" class="ch.qos.logback.classic.AsyncAppender">
|
<!-- <appender name="asyncFileAppender" class="ch.qos.logback.classic.AsyncAppender">-->
|
||||||
<queueSize>10000</queueSize>
|
<!-- <queueSize>10000</queueSize>-->
|
||||||
<discardingThreshold>0</discardingThreshold>
|
<!-- <discardingThreshold>0</discardingThreshold>-->
|
||||||
<appender-ref ref="file"/>
|
<!-- <appender-ref ref="file"/>-->
|
||||||
</appender>
|
<!-- </appender>-->
|
||||||
|
|
||||||
<!-- 读取配置文件信息(交由各项目指定) -->
|
<!-- 读取配置文件信息(交由各项目指定) -->
|
||||||
<property name="active" value="${spring.profiles.active:-test}" />
|
<property name="active" value="${spring.profiles.active:-test}" />
|
||||||
@ -43,42 +43,6 @@
|
|||||||
<!--为了防止进程退出时,内存中的数据丢失,请加上此选项-->
|
<!--为了防止进程退出时,内存中的数据丢失,请加上此选项-->
|
||||||
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
|
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
|
||||||
|
|
||||||
<!-- 外网 -->
|
|
||||||
<appender name="ONLINE-OUT" class="com.aliyun.openservices.log.logback.LoghubAppender">
|
|
||||||
<!--必选项 -->
|
|
||||||
<endpoint>cn-shanghai.log.aliyuncs.com</endpoint>
|
|
||||||
<accessKeyId>LTAI5tRN9T5Tz1QExcSpUaBc</accessKeyId>
|
|
||||||
<accessKey>LniIMK15XEOc6Nn5mOrtX399FkVfQd</accessKey>
|
|
||||||
<projectName>baogutang</projectName>
|
|
||||||
<logstore>${appEnv}</logstore>
|
|
||||||
|
|
||||||
<!-- 可选项 -->
|
|
||||||
<topic>${appId}</topic>
|
|
||||||
<!-- <source>source1</source> -->
|
|
||||||
|
|
||||||
<!-- 可选项 详见 '参数说明' -->
|
|
||||||
<packageTimeoutInMS>3000</packageTimeoutInMS>
|
|
||||||
<logsCountPerPackage>4096</logsCountPerPackage>
|
|
||||||
<logsBytesPerPackage>3145728</logsBytesPerPackage>
|
|
||||||
<memPoolSizeInByte>104857600</memPoolSizeInByte>
|
|
||||||
<retryTimes>3</retryTimes>
|
|
||||||
<maxIOThreadSizeInPool>8</maxIOThreadSizeInPool>
|
|
||||||
|
|
||||||
<!-- 可选项 设置时区 -->
|
|
||||||
<timeZone>Asia/Shanghai</timeZone>
|
|
||||||
<!-- 可选项 设置时间格式 -->
|
|
||||||
<timeFormat>yyyy-MM-dd HH:mm:ss.SSS</timeFormat>
|
|
||||||
<!-- 可选项 通过配置 encoder 的 pattern 自定义 log 的格式 -->
|
|
||||||
<encoder>
|
|
||||||
<!-- <pattern>[${appId}] %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{0}: %msg</pattern> -->
|
|
||||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{X-Request-Id}] [%thread] %logger{0}: %msg</pattern>
|
|
||||||
</encoder>
|
|
||||||
|
|
||||||
<!-- 指定级别的日志(INFO,WARN,ERROR) -->
|
|
||||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
|
||||||
<level>INFO</level>
|
|
||||||
</filter>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<logger name="org.hibernate" level="ERROR" />
|
<logger name="org.hibernate" level="ERROR" />
|
||||||
<logger name="org.apache" level="ERROR" />
|
<logger name="org.apache" level="ERROR" />
|
||||||
@ -100,14 +64,14 @@
|
|||||||
<springProfile name="test,prod">
|
<springProfile name="test,prod">
|
||||||
<root level="DEBUG">
|
<root level="DEBUG">
|
||||||
<appender-ref ref="console"/>
|
<appender-ref ref="console"/>
|
||||||
<appender-ref ref="ONLINE-OUT"/>
|
<appender-ref ref="localFile"/>
|
||||||
</root>
|
</root>
|
||||||
</springProfile>
|
</springProfile>
|
||||||
|
|
||||||
<springProfile name="local">
|
<springProfile name="local">
|
||||||
<root level="DEBUG">
|
<root level="DEBUG">
|
||||||
<appender-ref ref="console"/>
|
<appender-ref ref="console"/>
|
||||||
<appender-ref ref="ONLINE-OUT"/>
|
<appender-ref ref="localFile"/>
|
||||||
</root>
|
</root>
|
||||||
</springProfile>
|
</springProfile>
|
||||||
|
|
||||||
|
|||||||
219
baogutang-admin/src/main/resources/templates/coin.html
Normal file
219
baogutang-admin/src/main/resources/templates/coin.html
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>K-LINE</title>
|
||||||
|
<style>
|
||||||
|
/* 样式美化 */
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #1b1b1b;
|
||||||
|
color: #fff;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 20px 0;
|
||||||
|
color: #f0b90b;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
margin: 0 10px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #2c2c2c;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controls {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#kLineChart {
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- 引入 Lightweight Charts 库 -->
|
||||||
|
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>K-LINE</h2>
|
||||||
|
|
||||||
|
<!-- 控制区 -->
|
||||||
|
<div id="controls">
|
||||||
|
<!-- 交易对选择 -->
|
||||||
|
<label for="symbol">选择交易对:</label>
|
||||||
|
<select id="symbol" onchange="updateChart()">
|
||||||
|
<option value="BTCUSDT">BTC/USDT</option>
|
||||||
|
<option value="ETHUSDT">ETH/USDT</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 时间间隔选择 -->
|
||||||
|
<label for="timeInterval">选择时间间隔:</label>
|
||||||
|
<select id="timeInterval" onchange="updateChart()">
|
||||||
|
<option value="1m">1 分钟</option>
|
||||||
|
<option value="3m">3 分钟</option>
|
||||||
|
<option value="5m">5 分钟</option>
|
||||||
|
<option value="15m">15 分钟</option>
|
||||||
|
<option value="1h">1 小时</option>
|
||||||
|
<option value="3h">3 小时</option>
|
||||||
|
<option value="1d">1 天</option>
|
||||||
|
<option value="3d">3 天</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- K线图容器 -->
|
||||||
|
<div id="kLineChart"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let chart, candlestickSeries, socket;
|
||||||
|
let lastKLineTime = 0; // 用于记录最后一个K线的时间
|
||||||
|
|
||||||
|
// 初始化 Lightweight Charts
|
||||||
|
function initChart() {
|
||||||
|
chart = LightweightCharts.createChart(document.getElementById('kLineChart'), {
|
||||||
|
width: document.getElementById('kLineChart').clientWidth,
|
||||||
|
height: 600,
|
||||||
|
layout: {
|
||||||
|
backgroundColor: '#131722',
|
||||||
|
textColor: '#d1d4dc',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
vertLines: {
|
||||||
|
color: '#2B2B43',
|
||||||
|
},
|
||||||
|
horzLines: {
|
||||||
|
color: '#363C4E',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
crosshair: {
|
||||||
|
mode: LightweightCharts.CrosshairMode.Normal,
|
||||||
|
},
|
||||||
|
priceScale: {
|
||||||
|
borderColor: '#485c7b',
|
||||||
|
},
|
||||||
|
timeScale: {
|
||||||
|
borderColor: '#485c7b',
|
||||||
|
timeVisible: true,
|
||||||
|
secondsVisible: true,
|
||||||
|
localization: {
|
||||||
|
dateFormat: 'yyyy-MM-dd HH:mm:ss',
|
||||||
|
timeZone: 'Asia/Shanghai',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
candlestickSeries = chart.addCandlestickSeries({
|
||||||
|
upColor: '#4CAF50',
|
||||||
|
downColor: '#F44336',
|
||||||
|
borderDownColor: '#F44336',
|
||||||
|
borderUpColor: '#4CAF50',
|
||||||
|
wickDownColor: '#F44336',
|
||||||
|
wickUpColor: '#4CAF50',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接 WebSocket
|
||||||
|
function connectWebSocket() {
|
||||||
|
socket = new WebSocket("ws://localhost:8102/ws");
|
||||||
|
|
||||||
|
socket.onopen = function (event) {
|
||||||
|
console.log('WebSocket connection established.');
|
||||||
|
// Send initial data request to WebSocket
|
||||||
|
updateWebSocket();
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onmessage = function (event) {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
const kline = data.k;
|
||||||
|
if (kline.t > lastKLineTime) {
|
||||||
|
// 如果是新K线,先更新 lastKLineTime
|
||||||
|
lastKLineTime = kline.t;
|
||||||
|
}
|
||||||
|
const formattedData = {
|
||||||
|
time: lastKLineTime / 1000, // 时间戳(秒)
|
||||||
|
open: parseFloat(kline.o), // 开盘价
|
||||||
|
high: parseFloat(kline.h), // 最高价
|
||||||
|
low: parseFloat(kline.l), // 最低价
|
||||||
|
close: parseFloat(kline.c) // 收盘价
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新图表
|
||||||
|
candlestickSeries.update(formattedData);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onclose = function (event) {
|
||||||
|
console.log('WebSocket is closed now.');
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onerror = function (error) {
|
||||||
|
console.error('WebSocket error observed:', error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 WebSocket 连接
|
||||||
|
function updateWebSocket() {
|
||||||
|
const symbol = document.getElementById('symbol').value;
|
||||||
|
const interval = document.getElementById('timeInterval').value;
|
||||||
|
|
||||||
|
const payload = JSON.stringify({symbol: symbol, interval: interval});
|
||||||
|
socket.send(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新图表
|
||||||
|
function updateChart() {
|
||||||
|
const symbol = document.getElementById('symbol').value;
|
||||||
|
const interval = document.getElementById('timeInterval').value;
|
||||||
|
|
||||||
|
// 获取接口数据并更新图表
|
||||||
|
fetch(`http://localhost:8102/api/v1/admin/virtualCoin/uiKline?symbol=${symbol}&interval=${interval}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const formattedData = data.data.map(item => ({
|
||||||
|
time: item[0] / 1000, // 时间戳(秒)
|
||||||
|
open: parseFloat(item[1]), // 开盘价
|
||||||
|
high: parseFloat(item[2]), // 最高价
|
||||||
|
low: parseFloat(item[3]), // 最低价
|
||||||
|
close: parseFloat(item[4]) // 收盘价
|
||||||
|
}));
|
||||||
|
// 对数据进行排序(按时间升序)
|
||||||
|
formattedData.sort((a, b) => a.time - b.time);
|
||||||
|
|
||||||
|
// 设置初始的 lastKLineTime 为最后一条数据的时间戳(毫秒)
|
||||||
|
lastKLineTime = formattedData[formattedData.length - 1].time * 1000;
|
||||||
|
|
||||||
|
// 更新图表
|
||||||
|
candlestickSeries.setData(formattedData);
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching data:', error));
|
||||||
|
|
||||||
|
// 更新 WebSocket 连接
|
||||||
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||||
|
updateWebSocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化图表和 WebSocket 连接
|
||||||
|
initChart();
|
||||||
|
connectWebSocket();
|
||||||
|
|
||||||
|
// 监听窗口大小变化,调整图表尺寸
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
chart.resize(document.getElementById('kLineChart').clientWidth, 600);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面加载时更新图表
|
||||||
|
updateChart();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -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<String> returnT = okCoinMarketCandlesHandler.execute("{\"onlyKey\":\"Bitcoin\",\"bar\":\"kline_1m\",\"sign\":\"marketCap\"}");
|
||||||
|
Assert.assertEquals(SUCCESS_CODE, returnT.getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,7 +11,7 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="localFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<file>${log.path}/app.log</file>
|
<file>${log.path}/app.log</file>
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
<fileNamePattern>${log.path}/%d{yyyy-MM-dd,aux}/app-%d{yyyy-MM-dd}.%i.log
|
<fileNamePattern>${log.path}/%d{yyyy-MM-dd,aux}/app-%d{yyyy-MM-dd}.%i.log
|
||||||
@ -43,43 +43,6 @@
|
|||||||
<!--为了防止进程退出时,内存中的数据丢失,请加上此选项-->
|
<!--为了防止进程退出时,内存中的数据丢失,请加上此选项-->
|
||||||
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
|
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
|
||||||
|
|
||||||
<!-- 外网 -->
|
|
||||||
<appender name="ONLINE-OUT" class="com.aliyun.openservices.log.logback.LoghubAppender">
|
|
||||||
<!--必选项 -->
|
|
||||||
<endpoint>cn-shanghai.log.aliyuncs.com</endpoint>
|
|
||||||
<accessKeyId>LTAI5tRN9T5Tz1QExcSpUaBc</accessKeyId>
|
|
||||||
<accessKey>LniIMK15XEOc6Nn5mOrtX399FkVfQd</accessKey>
|
|
||||||
<projectName>baogutang</projectName>
|
|
||||||
<logstore>${appEnv}</logstore>
|
|
||||||
|
|
||||||
<!-- 可选项 -->
|
|
||||||
<topic>${appId}</topic>
|
|
||||||
<!-- <source>source1</source> -->
|
|
||||||
|
|
||||||
<!-- 可选项 详见 '参数说明' -->
|
|
||||||
<packageTimeoutInMS>3000</packageTimeoutInMS>
|
|
||||||
<logsCountPerPackage>4096</logsCountPerPackage>
|
|
||||||
<logsBytesPerPackage>3145728</logsBytesPerPackage>
|
|
||||||
<memPoolSizeInByte>104857600</memPoolSizeInByte>
|
|
||||||
<retryTimes>3</retryTimes>
|
|
||||||
<maxIOThreadSizeInPool>8</maxIOThreadSizeInPool>
|
|
||||||
|
|
||||||
<!-- 可选项 设置时区 -->
|
|
||||||
<timeZone>Asia/Shanghai</timeZone>
|
|
||||||
<!-- 可选项 设置时间格式 -->
|
|
||||||
<timeFormat>yyyy-MM-dd HH:mm:ss.SSS</timeFormat>
|
|
||||||
<!-- 可选项 通过配置 encoder 的 pattern 自定义 log 的格式 -->
|
|
||||||
<encoder>
|
|
||||||
<!-- <pattern>[${appId}] %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{0}: %msg</pattern> -->
|
|
||||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{X-Request-Id}] [%thread] %logger{0}: %msg</pattern>
|
|
||||||
</encoder>
|
|
||||||
|
|
||||||
<!-- 指定级别的日志(INFO,WARN,ERROR) -->
|
|
||||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
|
||||||
<level>INFO</level>
|
|
||||||
</filter>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<logger name="org.hibernate" level="ERROR" />
|
<logger name="org.hibernate" level="ERROR" />
|
||||||
<logger name="org.apache" level="ERROR" />
|
<logger name="org.apache" level="ERROR" />
|
||||||
<logger name="ch.qos.logback" level="WARN" />
|
<logger name="ch.qos.logback" level="WARN" />
|
||||||
@ -100,14 +63,13 @@
|
|||||||
<springProfile name="test,prod">
|
<springProfile name="test,prod">
|
||||||
<root level="DEBUG">
|
<root level="DEBUG">
|
||||||
<appender-ref ref="console"/>
|
<appender-ref ref="console"/>
|
||||||
<appender-ref ref="ONLINE-OUT"/>
|
<appender-ref ref="localFile"/>
|
||||||
</root>
|
</root>
|
||||||
</springProfile>
|
</springProfile>
|
||||||
|
|
||||||
<springProfile name="local">
|
<springProfile name="local">
|
||||||
<root level="DEBUG">
|
<root level="DEBUG">
|
||||||
<appender-ref ref="console"/>
|
<appender-ref ref="console"/>
|
||||||
<appender-ref ref="ONLINE-OUT"/>
|
|
||||||
</root>
|
</root>
|
||||||
</springProfile>
|
</springProfile>
|
||||||
|
|
||||||
|
|||||||
@ -170,6 +170,9 @@
|
|||||||
<version>1.4.2</version>
|
<version>1.4.2</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@ -20,7 +20,7 @@ public class GlobalCorsConfig {
|
|||||||
//重写父类提供的跨域请求处理的接口
|
//重写父类提供的跨域请求处理的接口
|
||||||
public void addCorsMappings(CorsRegistry registry) {
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
//添加映射路径
|
//添加映射路径
|
||||||
registry.addMapping("/api")
|
registry.addMapping("/api/**")
|
||||||
//放行哪些原始域
|
//放行哪些原始域
|
||||||
.allowedOrigins("*")
|
.allowedOrigins("*")
|
||||||
//是否发送Cookie信息
|
//是否发送Cookie信息
|
||||||
|
|||||||
@ -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<T> implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 4314677378175006552L;
|
||||||
|
|
||||||
|
public static final int SUCCESS_CODE = 200;
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
private T result;
|
||||||
|
}
|
||||||
@ -1,8 +1,12 @@
|
|||||||
package top.baogutang.common.domain;
|
package top.baogutang.common.domain;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description:
|
* @description:
|
||||||
@ -18,6 +22,23 @@ public class PageParam implements Serializable {
|
|||||||
|
|
||||||
private Integer pageSize;
|
private Integer pageSize;
|
||||||
|
|
||||||
|
@ApiModelProperty("排序条件")
|
||||||
|
private List<OrderBy> 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() {
|
public Integer getPageNum() {
|
||||||
pageNum = pageNum == null ? 1 : pageNum;
|
pageNum = pageNum == null ? 1 : pageNum;
|
||||||
return pageNum;
|
return pageNum;
|
||||||
|
|||||||
@ -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";
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,5 +1,9 @@
|
|||||||
package top.baogutang.common.utils;
|
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 com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import top.baogutang.common.domain.PageParam;
|
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);
|
return new PageUtil<>(pageRes.getCurrent(), pageRes.getSize(), pageRes.getTotal(), pageRes.getPages(), rPageRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认排序
|
||||||
|
*
|
||||||
|
* @param orders 排序条件
|
||||||
|
* @param queryWrapper 查询条件
|
||||||
|
* @param <T> T
|
||||||
|
*/
|
||||||
|
public static <T> void defaultOrders(List<PageParam.OrderBy> orders, QueryWrapper<T> queryWrapper) {
|
||||||
|
defaultOrder(orders, queryWrapper, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认排序,可以指定默认排序字段
|
||||||
|
*
|
||||||
|
* @param orders 排序条件
|
||||||
|
* @param queryWrapper 查询条件
|
||||||
|
* @param defaultColumnName 指定默认排序字段列
|
||||||
|
* @param <T> T
|
||||||
|
*/
|
||||||
|
public static <T, R> void defaultOrder(List<PageParam.OrderBy> orders, QueryWrapper<T> queryWrapper, SFunction<T, R> 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> t
|
||||||
|
* @param <R> r
|
||||||
|
*/
|
||||||
|
private static <T, R> void defaultOrderProcess(QueryWrapper<T> queryWrapper, SFunction<T, R> defaultColumnName) {
|
||||||
|
if (defaultColumnName != null) {
|
||||||
|
queryWrapper.lambda().orderByDesc(defaultColumnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="localFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<file>${log.path}/app.log</file>
|
<file>${log.path}/app.log</file>
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
<fileNamePattern>${log.path}/%d{yyyy-MM-dd,aux}/app-%d{yyyy-MM-dd}.%i.log
|
<fileNamePattern>${log.path}/%d{yyyy-MM-dd,aux}/app-%d{yyyy-MM-dd}.%i.log
|
||||||
@ -43,43 +43,6 @@
|
|||||||
<!--为了防止进程退出时,内存中的数据丢失,请加上此选项-->
|
<!--为了防止进程退出时,内存中的数据丢失,请加上此选项-->
|
||||||
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
|
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
|
||||||
|
|
||||||
<!-- 外网 -->
|
|
||||||
<appender name="ONLINE-OUT" class="com.aliyun.openservices.log.logback.LoghubAppender">
|
|
||||||
<!--必选项 -->
|
|
||||||
<endpoint>cn-shanghai.log.aliyuncs.com</endpoint>
|
|
||||||
<accessKeyId>LTAI5tRN9T5Tz1QExcSpUaBc</accessKeyId>
|
|
||||||
<accessKey>LniIMK15XEOc6Nn5mOrtX399FkVfQd</accessKey>
|
|
||||||
<projectName>baogutang</projectName>
|
|
||||||
<logstore>${appEnv}</logstore>
|
|
||||||
|
|
||||||
<!-- 可选项 -->
|
|
||||||
<topic>${appId}</topic>
|
|
||||||
<!-- <source>source1</source> -->
|
|
||||||
|
|
||||||
<!-- 可选项 详见 '参数说明' -->
|
|
||||||
<packageTimeoutInMS>3000</packageTimeoutInMS>
|
|
||||||
<logsCountPerPackage>4096</logsCountPerPackage>
|
|
||||||
<logsBytesPerPackage>3145728</logsBytesPerPackage>
|
|
||||||
<memPoolSizeInByte>104857600</memPoolSizeInByte>
|
|
||||||
<retryTimes>3</retryTimes>
|
|
||||||
<maxIOThreadSizeInPool>8</maxIOThreadSizeInPool>
|
|
||||||
|
|
||||||
<!-- 可选项 设置时区 -->
|
|
||||||
<timeZone>Asia/Shanghai</timeZone>
|
|
||||||
<!-- 可选项 设置时间格式 -->
|
|
||||||
<timeFormat>yyyy-MM-dd HH:mm:ss.SSS</timeFormat>
|
|
||||||
<!-- 可选项 通过配置 encoder 的 pattern 自定义 log 的格式 -->
|
|
||||||
<encoder>
|
|
||||||
<!-- <pattern>[${appId}] %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{0}: %msg</pattern> -->
|
|
||||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{X-Request-Id}] [%thread] %logger{0}: %msg</pattern>
|
|
||||||
</encoder>
|
|
||||||
|
|
||||||
<!-- 指定级别的日志(INFO,WARN,ERROR) -->
|
|
||||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
|
||||||
<level>INFO</level>
|
|
||||||
</filter>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<logger name="org.hibernate" level="ERROR" />
|
<logger name="org.hibernate" level="ERROR" />
|
||||||
<logger name="org.apache" level="ERROR" />
|
<logger name="org.apache" level="ERROR" />
|
||||||
<logger name="ch.qos.logback" level="WARN" />
|
<logger name="ch.qos.logback" level="WARN" />
|
||||||
@ -100,14 +63,13 @@
|
|||||||
<springProfile name="test,prod">
|
<springProfile name="test,prod">
|
||||||
<root level="DEBUG">
|
<root level="DEBUG">
|
||||||
<appender-ref ref="console"/>
|
<appender-ref ref="console"/>
|
||||||
<appender-ref ref="ONLINE-OUT"/>
|
<appender-ref ref="localFile"/>
|
||||||
</root>
|
</root>
|
||||||
</springProfile>
|
</springProfile>
|
||||||
|
|
||||||
<springProfile name="local">
|
<springProfile name="local">
|
||||||
<root level="DEBUG">
|
<root level="DEBUG">
|
||||||
<appender-ref ref="console"/>
|
<appender-ref ref="console"/>
|
||||||
<appender-ref ref="ONLINE-OUT"/>
|
|
||||||
</root>
|
</root>
|
||||||
</springProfile>
|
</springProfile>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user