diff --git a/pom.xml b/pom.xml index 12f8665..646b817 100644 --- a/pom.xml +++ b/pom.xml @@ -83,11 +83,11 @@ test - + com.github.pagehelper diff --git a/src/main/java/cn/stock/market/infrastructure/redis/SingleDistributedLockTemplate.java b/src/main/java/cn/stock/market/infrastructure/redis/SingleDistributedLockTemplate.java index 8469160..36242b6 100644 --- a/src/main/java/cn/stock/market/infrastructure/redis/SingleDistributedLockTemplate.java +++ b/src/main/java/cn/stock/market/infrastructure/redis/SingleDistributedLockTemplate.java @@ -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 lock(DistributedLockCallback callback, DistributedLockInfo... lockInfoList) throws Throwable { -// Stopwatch stopwatch = Stopwatch.createStarted(); -// Map lockMap = Maps.newConcurrentMap(); -// -// for (DistributedLockInfo info : lockInfoList) { -// RLock lock = getLock(info.getLockName(), info.getFairLock()); -// lockMap.put(lock, info); -// } -// -// try { -// for (Entry 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 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 lock(DistributedLockCallback callback, DistributedLockInfo... lockInfoList) throws Throwable { + Stopwatch stopwatch = Stopwatch.createStarted(); + Map lockMap = Maps.newConcurrentMap(); + + for (DistributedLockInfo info : lockInfoList) { + RLock lock = getLock(info.getLockName(), info.getFairLock()); + lockMap.put(lock, info); + } + + try { + for (Entry 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 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; + } +} diff --git a/src/main/java/cn/stock/market/infrastructure/redis/config/RedisConfig.java b/src/main/java/cn/stock/market/infrastructure/redis/config/RedisConfig.java index 2bab268..2630086 100644 --- a/src/main/java/cn/stock/market/infrastructure/redis/config/RedisConfig.java +++ b/src/main/java/cn/stock/market/infrastructure/redis/config/RedisConfig.java @@ -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 redisTemplate(RedisConnectionFactory redisConnectionFactory) { -// RedisTemplate 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 redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate 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); + } +} diff --git a/src/main/java/cn/stock/market/lesg/RefinitivConsumer.java b/src/main/java/cn/stock/market/lesg/RefinitivConsumer.java index 4d050ea..ba307f0 100644 --- a/src/main/java/cn/stock/market/lesg/RefinitivConsumer.java +++ b/src/main/java/cn/stock/market/lesg/RefinitivConsumer.java @@ -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 getTopStockList(String symbol) throws Exception { + List list = new ArrayList<>(); InstrumentData[] result = getDataBySymbol(symbol); if (result.length == 0 || isInvalidResult(result[0])) { - return new ArrayList<>(); + return list; } List 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; + } } diff --git a/src/main/java/cn/stock/market/web/RefinitivApiController.java b/src/main/java/cn/stock/market/web/RefinitivApiController.java index bb39ec0..91b42fd 100644 --- a/src/main/java/cn/stock/market/web/RefinitivApiController.java +++ b/src/main/java/cn/stock/market/web/RefinitivApiController.java @@ -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)); + } } diff --git a/src/main/resources/application-base-alpha.yml b/src/main/resources/application-base-alpha.yml index aacd179..6cd57fd 100644 --- a/src/main/resources/application-base-alpha.yml +++ b/src/main/resources/application-base-alpha.yml @@ -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 \ No newline at end of file