Skip to content

Commit

Permalink
[feat] 실시간 시세 조회 시, SSE 의 키값으로 멤버의 id + 현재 시간으로 설정
Browse files Browse the repository at this point in the history
- 만약 시세가 레디스에 없으면, DB 에서 조회하여 레디스에 저장

#11
  • Loading branch information
pagh2322 committed Mar 23, 2024
1 parent 4849689 commit 46e7d41
Show file tree
Hide file tree
Showing 22 changed files with 282 additions and 109 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.mockInvestment.balance.controller;

import org.mockInvestment.auth.dto.AuthInfo;
import org.mockInvestment.balance.dto.CurrentBalanceResponse;
import org.mockInvestment.balance.service.BalanceService;
import org.mockInvestment.support.auth.Login;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BalanceController {

private final BalanceService balanceService;


public BalanceController(BalanceService balanceService) {
this.balanceService = balanceService;
}

@GetMapping("/balance/me")
public ResponseEntity<CurrentBalanceResponse> findBalance(@Login AuthInfo authInfo) {
CurrentBalanceResponse response = balanceService.findBalance(authInfo);
return ResponseEntity.ok(response);
}

}
5 changes: 5 additions & 0 deletions src/main/java/org/mockInvestment/balance/domain/Balance.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,9 @@ public void purchase(Double price) {
public void cancelPayment(Double price) {
balance += price;
}

public double getBalance() {
return balance;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.mockInvestment.balance.dto;

public record CurrentBalanceResponse(double balance) {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package org.mockInvestment.balance.repository;

import org.mockInvestment.balance.domain.Balance;
import org.mockInvestment.member.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface BalanceRepository extends JpaRepository<Balance, Long> {

Optional<Balance> findByMember(Member member);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.mockInvestment.balance.service;

import org.mockInvestment.advice.exception.MemberNotFoundException;
import org.mockInvestment.auth.dto.AuthInfo;
import org.mockInvestment.balance.domain.Balance;
import org.mockInvestment.balance.dto.CurrentBalanceResponse;
import org.mockInvestment.balance.repository.BalanceRepository;
import org.mockInvestment.member.domain.Member;
import org.mockInvestment.member.repository.MemberRepository;
import org.springframework.stereotype.Service;

@Service
public class BalanceService {

private final BalanceRepository balanceRepository;

private final MemberRepository memberRepository;


public BalanceService(BalanceRepository balanceRepository, MemberRepository memberRepository) {
this.balanceRepository = balanceRepository;
this.memberRepository = memberRepository;
}


public CurrentBalanceResponse findBalance(AuthInfo authInfo) {
Member member = memberRepository.findById(authInfo.getId())
.orElseThrow(MemberNotFoundException::new);
// Balance balance = balanceRepository.findByMember(member)
// .orElseThrow();
return new CurrentBalanceResponse(member.getBalance().getBalance());
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.mockInvestment.stock.controller;

import lombok.extern.slf4j.Slf4j;
import org.mockInvestment.auth.dto.AuthInfo;
import org.mockInvestment.stock.dto.StockPriceCandlesResponse;
import org.mockInvestment.stock.dto.StockPricesResponse;
Expand All @@ -14,6 +15,7 @@

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/stock-prices")
public class StockPriceController {
Expand All @@ -29,7 +31,8 @@ public class StockPriceController {

private final PeriodExtractor fiveYearsPeriodExtractor;

public StockPriceController(StockPriceService stockPriceService, @Qualifier("oneWeekPeriodExtractor") PeriodExtractor oneWeekPeriodExtractor,
public StockPriceController(StockPriceService stockPriceService,
@Qualifier("oneWeekPeriodExtractor") PeriodExtractor oneWeekPeriodExtractor,
@Qualifier("threeMonthsPeriodExtractor") PeriodExtractor threeMonthsPeriodExtractor,
@Qualifier("oneYearPeriodExtractor") PeriodExtractor oneYearPeriodExtractor,
@Qualifier("fiveYearsPeriodExtractor") PeriodExtractor fiveYearsPeriodExtractor) {
Expand All @@ -48,6 +51,7 @@ public ResponseEntity<StockPricesResponse> findStockPrices(@RequestParam("code")

@GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public ResponseEntity<SseEmitter> subscribe(@Login AuthInfo authInfo, @RequestParam("code") List<String> stockCodes) {
log.info("StockPriceController - subscribe");
SseEmitter sseEmitter = stockPriceService.subscribeStockPrices(authInfo, stockCodes);
return ResponseEntity.ok(sseEmitter);
}
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/org/mockInvestment/stock/domain/RecentStockInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.mockInvestment.stock.domain;


import org.mockInvestment.stock.domain.Stock;
import org.mockInvestment.stock.domain.StockPriceCandle;

public record RecentStockInfo(String symbol, String name, double base, double close, double curr, double high, double low, double open, long volume) {

public RecentStockInfo(double base, Stock stock, StockPriceCandle recentPriceCandle) {
this(stock.getSymbol(), stock.getName(), base,
recentPriceCandle.getClose(),
recentPriceCandle.getCurr(),
recentPriceCandle.getHigh(),
recentPriceCandle.getLow(),
recentPriceCandle.getOpen(),
recentPriceCandle.getVolume());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ public StockPrice(Double open, Double high, Double low, Double close, Double cur
this.close = close;
this.curr = curr;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,25 @@ public StockPriceCandle(StockPrice price, long volume) {
this.price = price;
this.volume = volume;
}

public Double getClose() {
return price.getClose();
}

public Double getCurr() {
return price.getCurr();
}

public Double getOpen() {
return price.getOpen();
}

public Double getHigh() {
return price.getHigh();
}

public Double getLow() {
return price.getLow();
}

}
5 changes: 0 additions & 5 deletions src/main/java/org/mockInvestment/stock/dto/LastStockInfo.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.mockInvestment.stock.dto;

import org.mockInvestment.stock.domain.RecentStockInfo;

public record StockPriceResponse(String code, String name, double base, double curr) {

public static StockPriceResponse of(String code, LastStockInfo lastStockInfo) {
return new StockPriceResponse(code, lastStockInfo.name(), lastStockInfo.base(), lastStockInfo.curr());
public static StockPriceResponse of(String code, RecentStockInfo recentStockInfo) {
return new StockPriceResponse(code, recentStockInfo.name(), recentStockInfo.base(), recentStockInfo.curr());
}
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,51 @@
package org.mockInvestment.stock.repository;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Repository
public class EmitterRepository {

private static final Long DEFAULT_TIMEOUT = 60L * 1000;
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
private final Map<Long, Set<Long>> stockIdMappingMemberIds = new ConcurrentHashMap<>();

public void createSubscription(long memberId, long stockId) {
SseEmitter emitter = getOrCreateEmitter(memberId);
emitters.put(memberId, emitter);
Set<Long> memberIds = stockIdMappingMemberIds.getOrDefault(stockId, new HashSet<>());
memberIds.add(memberId);
stockIdMappingMemberIds.put(stockId, memberIds);
private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
private final Map<Long, Set<String>> stockIdMappingKeys = new ConcurrentHashMap<>();

public void createSubscription(String key, long stockId) {
SseEmitter emitter = getOrCreateEmitter(key);
emitters.put(key, emitter);
Set<String> memberIds = stockIdMappingKeys.getOrDefault(stockId, new HashSet<>());
memberIds.add(key);
stockIdMappingKeys.put(stockId, memberIds);
}

public void deleteSseEmitterByMemberId(long memberId) {
emitters.remove(memberId);
for (Long stockId : stockIdMappingMemberIds.keySet()) {
Set<Long> memberIds = stockIdMappingMemberIds.get(stockId);
memberIds.remove(stockId);
public void deleteSseEmitterByKey(String key) {
emitters.remove(key);
for (Long stockId : stockIdMappingKeys.keySet()) {
Set<String> memberIds = stockIdMappingKeys.get(stockId);
memberIds.remove(key);
}
}

public Optional<SseEmitter> getSseEmitterByMemberId(long memberId) {
return Optional.ofNullable(emitters.get(memberId));
public Optional<SseEmitter> getSseEmitterByKey(String key) {
return Optional.ofNullable(emitters.get(key));
}

public Set<Long> getMemberIdsByStockId(long stockId) {
return stockIdMappingMemberIds.getOrDefault(stockId, new HashSet<>());
public Set<String> getMemberIdsByStockId(long stockId) {
return stockIdMappingKeys.getOrDefault(stockId, new HashSet<>());
}

private SseEmitter getOrCreateEmitter(long memberId) {
SseEmitter emitter = emitters.get(memberId);
private SseEmitter getOrCreateEmitter(String key) {
SseEmitter emitter = emitters.get(key);
if (emitter == null) {
emitter = new SseEmitter(DEFAULT_TIMEOUT);
emitter.onCompletion(() -> deleteSseEmitterByMemberId(memberId));
emitter.onTimeout(() -> deleteSseEmitterByMemberId(memberId));
emitter.onError((error) -> deleteSseEmitterByMemberId(memberId));
emitter.onCompletion(() -> deleteSseEmitterByKey(key));
emitter.onTimeout(() -> deleteSseEmitterByKey(key));
emitter.onError((error) -> deleteSseEmitterByKey(key));
}
return emitter;
}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.mockInvestment.stock.repository;

import org.mockInvestment.stock.domain.RecentStockInfo;

import java.util.Optional;

public interface RecentStockInfoCacheRepository {

Optional<RecentStockInfo> findByStockCode(String code);

void saveByCode(String code, RecentStockInfo entity);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.mockInvestment.stock.repository;

import org.mockInvestment.advice.exception.JsonStringDeserializationFailureException;
import org.mockInvestment.common.JsonStringMapper;
import org.mockInvestment.stock.domain.RecentStockInfo;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;

import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Repository
public class RecentStockInfoRedisRepository implements RecentStockInfoCacheRepository {

private final RedisTemplate<String, String> redisTemplate;


public RecentStockInfoRedisRepository(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}

@Override
public Optional<RecentStockInfo> findByStockCode(String code) {
String jsonString = redisTemplate.opsForValue().get(code);
if (jsonString == null)
return Optional.empty();
return JsonStringMapper.parseJsonString(jsonString, RecentStockInfo.class);
}

@Override
public void saveByCode(String code, RecentStockInfo entity) {
String jsonString = JsonStringMapper.toJsonString(entity)
.orElseThrow(JsonStringDeserializationFailureException::new);
redisTemplate.opsForValue().set(code, jsonString, 60, TimeUnit.SECONDS);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

public interface StockPriceCandleRepository extends JpaRepository<StockPriceCandle, Long> {

List<StockPriceCandle> findTop2ByStockOrderByDateDesc(Stock stock);

List<StockPriceCandle> findAllByStockAndDateBetween(Stock stock, LocalDate startDate, LocalDate endDate);

}
Loading

0 comments on commit 46e7d41

Please sign in to comment.