feat: retifive K线

This commit is contained in:
vercel
2024-04-30 17:11:45 +08:00
parent e6a65ec416
commit 6a4531803f
6 changed files with 317 additions and 162 deletions

View File

@@ -83,11 +83,11 @@
<scope>test</scope>
</dependency>
<!--<dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.3</version>
</dependency>-->
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>

View File

@@ -1,87 +1,87 @@
//package cn.stock.market.infrastructure.redis;
//
//import java.time.LocalDateTime;
//import java.time.format.DateTimeFormatter;
//import java.util.Map;
//import java.util.Map.Entry;
//import java.util.concurrent.TimeUnit;
//
//import org.redisson.api.RLock;
//import org.redisson.api.RedissonClient;
//
//import com.google.common.base.Stopwatch;
//import com.google.common.collect.Maps;
//
//import cn.qutaojing.common.aop.distributedlock.DistributedLockCallback;
//import cn.qutaojing.common.aop.distributedlock.DistributedLockInfo;
//import cn.qutaojing.common.aop.distributedlock.DistributedLockTemplate;
//import lombok.extern.slf4j.Slf4j;
//
///**
// *
// * title: SingleDistributedLockTemplate.java
// *
// * @author xlfd
// * @email xlfd@gmail.com
// * @version 1.0
// * @created Sep 1, 2020 5:03:20 PM
// */
//@Slf4j
//public class SingleDistributedLockTemplate implements DistributedLockTemplate {
// private RedissonClient redisson;
// DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS");
//
// public SingleDistributedLockTemplate() {
// }
//
// public SingleDistributedLockTemplate(RedissonClient redisson) {
// this.redisson = redisson;
// }
//
// @Override
// public <T> T lock(DistributedLockCallback<T> callback, DistributedLockInfo... lockInfoList) throws Throwable {
// Stopwatch stopwatch = Stopwatch.createStarted();
// Map<RLock, DistributedLockInfo> lockMap = Maps.newConcurrentMap();
//
// for (DistributedLockInfo info : lockInfoList) {
// RLock lock = getLock(info.getLockName(), info.getFairLock());
// lockMap.put(lock, info);
// }
//
// try {
// for (Entry<RLock, DistributedLockInfo> entry : lockMap.entrySet()) {
// RLock lock = entry.getKey();
// DistributedLockInfo info = entry.getValue();
// log.info("{}-准备获取{}分布式锁:{}", dateTimeFormatter.format(LocalDateTime.now()), info.getMessage(), info.getLockName());
// lock.lock(info.getLeaseTime(), info.getTimeUnit());
// log.info("{}-{}分布式锁:{}获取成功, 耗时:{} ms", dateTimeFormatter.format(LocalDateTime.now()), info.getMessage(), info.getLockName(), stopwatch.elapsed(TimeUnit.MILLISECONDS));
// }
//
// return callback.process();
// } finally {
// for (Entry<RLock, DistributedLockInfo> entry : lockMap.entrySet()) {
// RLock lock = entry.getKey();
// DistributedLockInfo info = entry.getValue();
// if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
// lock.unlock();
// log.info("{}-{}分布式锁:{}释放成功, 耗时:{} ms", dateTimeFormatter.format(LocalDateTime.now()), info.getMessage(), info.getLockName(), stopwatch.elapsed(TimeUnit.MILLISECONDS));
// }
// }
//
// }
// }
//
// private RLock getLock(String lockName, boolean fairLock) {
// RLock lock;
// if (fairLock) {
// lock = redisson.getFairLock(lockName);
// } else {
// lock = redisson.getLock(lockName);
// }
// return lock;
// }
//
// public void setRedisson(RedissonClient redisson) {
// this.redisson = redisson;
// }
//}
package cn.stock.market.infrastructure.redis;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Maps;
import cn.qutaojing.common.aop.distributedlock.DistributedLockCallback;
import cn.qutaojing.common.aop.distributedlock.DistributedLockInfo;
import cn.qutaojing.common.aop.distributedlock.DistributedLockTemplate;
import lombok.extern.slf4j.Slf4j;
/**
*
* title: SingleDistributedLockTemplate.java
*
* @author xlfd
* @email xlfd@gmail.com
* @version 1.0
* @created Sep 1, 2020 5:03:20 PM
*/
@Slf4j
public class SingleDistributedLockTemplate implements DistributedLockTemplate {
private RedissonClient redisson;
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS");
public SingleDistributedLockTemplate() {
}
public SingleDistributedLockTemplate(RedissonClient redisson) {
this.redisson = redisson;
}
@Override
public <T> T lock(DistributedLockCallback<T> callback, DistributedLockInfo... lockInfoList) throws Throwable {
Stopwatch stopwatch = Stopwatch.createStarted();
Map<RLock, DistributedLockInfo> lockMap = Maps.newConcurrentMap();
for (DistributedLockInfo info : lockInfoList) {
RLock lock = getLock(info.getLockName(), info.getFairLock());
lockMap.put(lock, info);
}
try {
for (Entry<RLock, DistributedLockInfo> entry : lockMap.entrySet()) {
RLock lock = entry.getKey();
DistributedLockInfo info = entry.getValue();
log.info("{}-准备获取{}分布式锁:{}", dateTimeFormatter.format(LocalDateTime.now()), info.getMessage(), info.getLockName());
lock.lock(info.getLeaseTime(), info.getTimeUnit());
log.info("{}-{}分布式锁:{}获取成功, 耗时:{} ms", dateTimeFormatter.format(LocalDateTime.now()), info.getMessage(), info.getLockName(), stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
return callback.process();
} finally {
for (Entry<RLock, DistributedLockInfo> entry : lockMap.entrySet()) {
RLock lock = entry.getKey();
DistributedLockInfo info = entry.getValue();
if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
log.info("{}-{}分布式锁:{}释放成功, 耗时:{} ms", dateTimeFormatter.format(LocalDateTime.now()), info.getMessage(), info.getLockName(), stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
}
}
}
private RLock getLock(String lockName, boolean fairLock) {
RLock lock;
if (fairLock) {
lock = redisson.getFairLock(lockName);
} else {
lock = redisson.getLock(lockName);
}
return lock;
}
public void setRedisson(RedissonClient redisson) {
this.redisson = redisson;
}
}

View File

@@ -1,70 +1,71 @@
//package cn.stock.market.infrastructure.redis.config;
//
//import java.time.Duration;
//
//import org.redisson.api.RedissonClient;
//import org.springframework.cache.CacheManager;
//import org.springframework.cache.annotation.EnableCaching;
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.data.redis.cache.RedisCacheConfiguration;
//import org.springframework.data.redis.cache.RedisCacheManager;
//import org.springframework.data.redis.connection.RedisConnectionFactory;
//import org.springframework.data.redis.core.RedisTemplate;
//import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
//import org.springframework.data.redis.serializer.RedisSerializationContext;
//import org.springframework.data.redis.serializer.StringRedisSerializer;
//
//import cn.qutaojing.common.aop.distributedlock.DistributedLockTemplate;
//import cn.stock.market.infrastructure.redis.SingleDistributedLockTemplate;
//
///**
// *
// * @author xlfd
// * @email xlfd@gmail.com
// * @version 1.0
// * @created Jun 3, 2021 4:56:28 PM
// */
//@Configuration
//@EnableCaching
//public class RedisConfig {
//
// @Bean
// public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
// redisTemplate.setConnectionFactory(redisConnectionFactory);
// redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
// redisTemplate.setKeySerializer(new StringRedisSerializer());
//
// redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//
// return redisTemplate;
// }
//
// @Bean
// public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// // 配置序列化
// RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
// config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class)));
//
// // 设置缓存的默认过期时间 ,30分钟
// config.entryTtl(Duration.ofMinutes(30));
// // 不缓存空值
// config.disableCachingNullValues();
// RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config)
// .build();
// return cacheManager;
// }
//
// /**
// * 分布式锁实现
// * @param redissonClient
// * @return
// */
// @Bean
// public DistributedLockTemplate distributedLockTemplate(RedissonClient redissonClient) {
// return new SingleDistributedLockTemplate(redissonClient);
// }
//}
package cn.stock.market.infrastructure.redis.config;
import java.time.Duration;
import org.redisson.api.RedissonClient;
import org.redisson.api.RedissonClient;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import cn.qutaojing.common.aop.distributedlock.DistributedLockTemplate;
import cn.stock.market.infrastructure.redis.SingleDistributedLockTemplate;
/**
*
* @author xlfd
* @email xlfd@gmail.com
* @version 1.0
* @created Jun 3, 2021 4:56:28 PM
*/
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class)));
// 设置缓存的默认过期时间 ,30分钟
config.entryTtl(Duration.ofMinutes(30));
// 不缓存空值
config.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config)
.build();
return cacheManager;
}
/**
* 分布式锁实现
* @param redissonClient
* @return
*/
@Bean
public DistributedLockTemplate distributedLockTemplate(RedissonClient redissonClient) {
return new SingleDistributedLockTemplate(redissonClient);
}
}

View File

@@ -1,20 +1,34 @@
package cn.stock.market.lesg;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.stock.market.domain.basic.entity.RetifiveStock;
import cn.stock.market.dto.RetifiveStockInfo;
import cn.stock.market.dto.StockHistoryResponse;
import cn.stock.market.utils.RefinitivUtil;
import com.ag.exception.SysTipsException;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.refinitiv.ema.access.*;
import com.refinitiv.ema.rdm.EmaRdm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.SECONDS;
/**
* RefinitivConsumer
@@ -25,6 +39,9 @@ import java.util.List;
@Service
@Slf4j
public class RefinitivConsumer implements ApplicationRunner {
@Resource
private RedisTemplate redisTemplate;
@Value("${refinitiv.market-data.service-name}")
private String serviceName;
@@ -40,12 +57,29 @@ public class RefinitivConsumer implements ApplicationRunner {
@Value("${refinitiv.market-data.batch-request-timeout}")
private long timeout;
@Value("${refinitiv.token.username}")
private String tokenUsername;
@Value("${refinitiv.token.password}")
private String tokenPassword;
@Value("${refinitiv.token.appKey}")
private String appKey;
@Value("${refinitiv.token.url}")
private String tokenUrl;
private OmmConsumer consumer = null;
public static final String TOP_ACTIVES_SYMBOL = ".AV.BO";
public static final String TOP_GAINERS_SYMBOL = ".PG.BO";
public static final String TOP_LOSERS_SYMBOL = ".PL.BO";
public static final String SCOPE = "trapi";
public static final String GRANT_TYPE = "password";
public InstrumentData[] getDataBySymbol(String symbol) throws Exception {
String[] items = symbol.split(",");
log.info("Quote request for: {}", java.util.Arrays.toString(items));
@@ -164,13 +198,19 @@ public class RefinitivConsumer implements ApplicationRunner {
* @throws Exception e
*/
public List<RetifiveStockInfo> getTopStockList(String symbol) throws Exception {
List<RetifiveStockInfo> list = new ArrayList<>();
InstrumentData[] result = getDataBySymbol(symbol);
if (result.length == 0 || isInvalidResult(result[0])) {
return new ArrayList<>();
return list;
}
List<String> topSymbols = RefinitivUtil.decodeTopData(result[0]);
return getStockList(String.join(",", topSymbols));
for (String topSymbol : topSymbols) {
RetifiveStockInfo stockInfo = getDetail(topSymbol);
list.add(stockInfo);
}
return list;
}
/**
@@ -214,4 +254,100 @@ public class RefinitivConsumer implements ApplicationRunner {
consumer.uninitialize();
}
}
public JSONArray getKLink(String symbol,String resolution){
String token = getToken();
if(StringUtils.isBlank(token)){
throw new SysTipsException("token为空");
}
JSONObject data = JSONObject.parseObject(token);
token = data.getString("access_token");
String baseURL = "https://api.refinitiv.com/data/historical-pricing/v1/views/intraday-summaries/";
int countback = 5;
String interval = null;
if(StringUtils.equals("H",resolution)){
countback = 30;
interval = "PT1H";
}else if(StringUtils.equals("D",resolution)){
interval ="PT1D";
}else if(StringUtils.equals("W",resolution)){
countback = 30;
interval ="PT1W";
}else if(StringUtils.equals("M",resolution)){
countback = 30;
interval ="PT1M";
}
String resourceEndpoint = baseURL + symbol;
HttpResponse response = HttpUtil.createGet(resourceEndpoint)
.form("eventTypes","trade,quote")
.form("start","2023-05-11T18:30:12.000000000Z")
.form("count",countback)
.form("interval",interval)
.header("Authorization","Bearer " + token)
.execute();
if(response.getStatus() != 200){
throw new SysTipsException("获取股票K线失败");
}
return JSONArray.parseArray(response.body());
}
public String getToken(){
String toekn = null;
if(ObjectUtil.isNotNull(redisTemplate.opsForValue().get("token"))){
toekn = redisTemplate.opsForValue().get("token").toString();
}
if(StringUtils.isNotBlank(toekn)){
JSONObject data = JSONObject.parseObject(toekn);
if(Long.valueOf(data.get("expiry_tm").toString()) > System.currentTimeMillis()){
return toekn;
}
toekn = requestNewToken(data.get("refresh_token").toString());
return toekn;
}
toekn = requestNewToken("");
return toekn;
}
public String requestNewToken(String refreshToken){
if(StringUtils.isNotBlank(refreshToken)){
HttpResponse response = HttpUtil.createPost(tokenUrl)
.form("refresh_token",refreshToken)
.form("grant_type","refresh_token")
.header("Content-Type", "application/x-www-form-urlencoded")
.basicAuth(appKey,"")
.execute();
if(response.getStatus() != 200){
throw new SysTipsException("获取token失败");
}
JSONObject data = JSONObject.parseObject(response.body());
data.put("expiry_tm",System.currentTimeMillis() + (Long.valueOf(data.get("expires_in").toString())-10));
String toekn = data.toString();
redisTemplate.opsForValue().set("token",toekn);
redisTemplate.expire("token",1, TimeUnit.DAYS);
return toekn;
}
HttpResponse response = HttpUtil.createPost(tokenUrl)
.form("username",tokenUsername)
.form("password",tokenPassword)
.form("grant_type","password")
.form("scope",SCOPE)
.form("takeExclusiveSignOnControl","true")
.header("Content-Type", "application/x-www-form-urlencoded")
.basicAuth(appKey,"")
.execute();
if(response.getStatus() != 200){
throw new SysTipsException("获取token失败");
}
JSONObject data = JSONObject.parseObject(response.body());
data.put("expiry_tm",System.currentTimeMillis() + (Long.valueOf(data.get("expires_in").toString())-10));
String toekn = data.toString();
redisTemplate.opsForValue().set("token",toekn);
redisTemplate.expire("token",1, TimeUnit.DAYS);
return toekn;
}
}

View File

@@ -131,4 +131,17 @@ public class RefinitivApiController {
return ServerResponse.createByError();
}
@ApiOperation(value = "查询股票K线", httpMethod = "GET", response = RetifiveStockInfo.class)
@ApiImplicitParams({
@ApiImplicitParam(name = "symbol",value = "股票对应代码symbol",dataType ="String",required = true, paramType = "query"),
@ApiImplicitParam(name = "symbol",value = "股票对应代码symbol",dataType ="String",required = true, paramType = "query"),
@ApiImplicitParam(name = "resolution", value = "单位:60 1D 1W 1D 对应H,D,W,Y", required = true, dataType = "String", paramType = "query"),
})
@GetMapping("/getKlink")
@EncryptFilter(decryptRequest = false)
public ServerResponse<?> getKlink(@RequestParam("symbol") String symbol, @RequestParam String resolution) {
return ServerResponse.createBySuccess(refinitivConsumer.getKLink(symbol,resolution));
}
}

View File

@@ -3,7 +3,7 @@ spring:
show-sql: true
# Redis配置
redis:
host: 43.132.212.180
host: 149.88.86.7
password: ruTZ9J3gaDhknJ
port: 36379
database: 1
@@ -67,3 +67,8 @@ refinitiv:
password: 8EyCWDQ%XITcGRM@RhKokxX05FZJe1
clientId: 662b37b071144c9f8f2507d93037a019a010ae0e
batch-request-timeout: 60000
token:
username: GE-A-10288435-3-16229
password: Pyc^U)D9k-u))N8n+M3zYa=,+CoQujkvjizR
appKey: 87aa0e9fcb034b2aa63344bc41cc3a3f8aeadd6c
url: https://api.refinitiv.com/auth/oauth2/v1/token