Files
goldenChart/backend/src/main/java/com/goldenchart/service/IndicatorSettingsService.java
T
2026-05-23 15:11:48 +09:00

198 lines
8.2 KiB
Java

package com.goldenchart.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.goldenchart.entity.GcIndicatorSettings;
import com.goldenchart.repository.GcIndicatorSettingsRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* 전역 지표 파라미터 설정 서비스.
*
* <p>프론트엔드 indicatorRegistry.ts 의 defaultParams 를 완전히 대체하며,
* 사용자가 변경한 파라미터를 DB 에 저장하고 백엔드 지표 계산 시 우선 적용한다.</p>
*/
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class IndicatorSettingsService {
private final GcIndicatorSettingsRepository repo;
private final ObjectMapper mapper;
// ── 공개 API ─────────────────────────────────────────────────────────────
/**
* 장치/사용자 식별자로 전체 지표 파라미터 맵을 조회한다.
* 없으면 빈 맵 반환.
*
* @return Map&lt;indicatorType, Map&lt;paramKey, paramValue&gt;&gt;
*/
@Transactional(readOnly = true)
public Map<String, Map<String, Object>> getAll(Long userId, String deviceId) {
return findEntity(userId, deviceId)
.map(s -> parseParamsJson(s.getParamsJson()))
.orElse(Collections.emptyMap());
}
/**
* 특정 지표의 파라미터를 조회한다.
* 없으면 빈 맵 반환.
*/
@Transactional(readOnly = true)
public Map<String, Object> getForType(Long userId, String deviceId, String indicatorType) {
Map<String, Map<String, Object>> all = getAll(userId, deviceId);
return all.getOrDefault(indicatorType, Collections.emptyMap());
}
/**
* 전체 지표 파라미터를 저장(덮어쓰기)한다.
* 프론트엔드에서 전체 설정을 한 번에 저장할 때 사용.
*
* @param allParams Map&lt;indicatorType, Map&lt;paramKey, paramValue&gt;&gt;
*/
public void saveAll(Long userId, String deviceId,
Map<String, Map<String, Object>> allParams) {
GcIndicatorSettings entity = findOrCreate(userId, deviceId);
entity.setParamsJson(toJson(allParams));
repo.save(entity);
log.debug("[IndicatorSettings] saved {} indicator types for device={}", allParams.size(), deviceId);
}
/**
* 특정 지표의 파라미터만 병합(upsert)한다.
* 프론트엔드에서 단일 지표 파라미터를 변경할 때 사용.
*
* @param indicatorType 지표 타입 (e.g. "RSI", "MACD")
* @param params 파라미터 맵 (e.g. {"length": 9, "src": "close"})
*/
public void saveForType(Long userId, String deviceId,
String indicatorType, Map<String, Object> params) {
GcIndicatorSettings entity = findOrCreate(userId, deviceId);
Map<String, Map<String, Object>> all = new HashMap<>(parseParamsJson(entity.getParamsJson()));
all.put(indicatorType, params);
entity.setParamsJson(toJson(all));
repo.save(entity);
log.debug("[IndicatorSettings] saved params for {}:{} device={}", indicatorType, params, deviceId);
}
// ── 시각 설정 (색상·선굵기·수평선) ──────────────────────────────────────
/**
* 전체 지표의 시각 설정을 조회한다.
*
* @return Map&lt;indicatorType, Map{plots, hlines}&gt;
*/
@Transactional(readOnly = true)
public Map<String, Map<String, Object>> getAllVisual(Long userId, String deviceId) {
return findEntity(userId, deviceId)
.map(s -> parseVisualJson(s.getVisualConfigJson()))
.orElse(Collections.emptyMap());
}
/**
* 특정 지표의 시각 설정(색상·선굵기·수평선)을 저장(병합)한다.
*
* @param indicatorType 지표 타입 (e.g. "RSI")
* @param visual {@code {plots:[...], hlines:[...]}} 구조의 맵
*/
public void saveVisualForType(Long userId, String deviceId,
String indicatorType, Map<String, Object> visual) {
GcIndicatorSettings entity = findOrCreate(userId, deviceId);
Map<String, Map<String, Object>> all = new HashMap<>(parseVisualJson(entity.getVisualConfigJson()));
all.put(indicatorType, visual);
entity.setVisualConfigJson(toJson(all));
repo.save(entity);
log.debug("[IndicatorSettings] saved visual for {}:{} device={}", indicatorType, visual, deviceId);
}
/**
* 요청 params 에 없는 키를 DB 설정값으로 채운다.
* IndicatorController 에서 호출 — 하드코딩 기본값 제거.
*
* @param requestParams 프론트엔드가 보낸 파라미터 (null 허용)
* @param indicatorType 지표 타입
* @return 병합된 파라미터 맵 (DB 값 우선, 없으면 요청값)
*/
@Transactional(readOnly = true)
public Map<String, Object> mergeWithDb(Long userId, String deviceId,
Map<String, Object> requestParams,
String indicatorType) {
Map<String, Object> dbParams = getForType(userId, deviceId, indicatorType);
if (dbParams.isEmpty() && (requestParams == null || requestParams.isEmpty())) {
return Map.of(); // 둘 다 없으면 빈 맵 반환 (IndicatorService 내부 기본값 사용)
}
// DB 값 기반 + 요청 파라미터로 덮어쓰기 (요청이 명시적으로 보낸 값 우선)
Map<String, Object> merged = new HashMap<>(dbParams);
if (requestParams != null) {
merged.putAll(requestParams);
}
return merged;
}
// ── Private helpers ───────────────────────────────────────────────────────
private Optional<GcIndicatorSettings> findEntity(Long userId, String deviceId) {
if (userId != null) return repo.findByUserId(userId);
if (deviceId != null) return repo.findByDeviceId(deviceId);
return Optional.empty();
}
private GcIndicatorSettings findOrCreate(Long userId, String deviceId) {
return findEntity(userId, deviceId).orElseGet(() ->
repo.save(GcIndicatorSettings.builder()
.userId(userId)
.deviceId(deviceId)
.paramsJson("{}")
.build()));
}
@SuppressWarnings("unchecked")
private Map<String, Map<String, Object>> parseParamsJson(String json) {
if (json == null || json.isBlank() || "{}".equals(json.trim())) {
return new HashMap<>();
}
try {
return mapper.readValue(json,
new TypeReference<Map<String, Map<String, Object>>>() {});
} catch (JsonProcessingException e) {
log.warn("[IndicatorSettings] JSON 파싱 실패: {}", e.getMessage());
return new HashMap<>();
}
}
@SuppressWarnings("unchecked")
private Map<String, Map<String, Object>> parseVisualJson(String json) {
if (json == null || json.isBlank() || "{}".equals(json.trim())) {
return new HashMap<>();
}
try {
return mapper.readValue(json,
new TypeReference<Map<String, Map<String, Object>>>() {});
} catch (JsonProcessingException e) {
log.warn("[IndicatorSettings] visual JSON 파싱 실패: {}", e.getMessage());
return new HashMap<>();
}
}
private String toJson(Object obj) {
try {
return mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.error("[IndicatorSettings] JSON 직렬화 실패", e);
return "{}";
}
}
}