198 lines
8.2 KiB
Java
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<indicatorType, Map<paramKey, paramValue>>
|
|
*/
|
|
@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<indicatorType, Map<paramKey, paramValue>>
|
|
*/
|
|
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<indicatorType, Map{plots, hlines}>
|
|
*/
|
|
@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 "{}";
|
|
}
|
|
}
|
|
}
|