diff --git a/src/main/doc/db/jcms_vpp_0.0.5.exerd b/src/main/doc/db/jcms_vpp_0.0.5.exerd new file mode 100644 index 0000000..2841edd Binary files /dev/null and b/src/main/doc/db/jcms_vpp_0.0.5.exerd differ diff --git a/src/main/java/net/jwsi/jcms/vpp/api/ApiClient.java b/src/main/java/net/jwsi/jcms/vpp/api/ApiClient.java index 2ac3376..8451d0d 100644 --- a/src/main/java/net/jwsi/jcms/vpp/api/ApiClient.java +++ b/src/main/java/net/jwsi/jcms/vpp/api/ApiClient.java @@ -8,9 +8,7 @@ import net.jwsi.jcms.vpp.station.StationDto; import org.springframework.core.ParameterizedTypeReference; import org.springframework.stereotype.Component; import org.springframework.web.client.RestClient; -import org.springframework.web.util.UriComponentsBuilder; -import java.net.URI; import java.util.Collections; import java.util.List; import java.util.Map; @@ -19,62 +17,39 @@ import java.util.Map; @RequiredArgsConstructor public class ApiClient { - private final RestClient restClient; + private final RestClient externalApiClient; - public ApiResponse> fetchStations(String baseUrl, String token) { - URI uri = UriComponentsBuilder.fromUriString(baseUrl) - .path("/api/st/list") - .build() - .toUri(); - - return restClient.post() - .uri(uri) - .header("Authorization", "Bearer " + token) + public ApiResponse> fetchStations() { + return externalApiClient.post() + .uri("/api/st/list") .body(Collections.emptyMap()) .retrieve() .body(new ParameterizedTypeReference>>() {}); } - public ApiResponse> fetchProviders(String baseUrl, String token) { - URI uri = UriComponentsBuilder.fromUriString(baseUrl) - .path("/api/provider/list") - .build() - .toUri(); - - return restClient.get() - .uri(uri) - .header("Authorization", "Bearer " + token) + public ApiResponse> fetchProviders() { + return externalApiClient.get() + .uri("/api/provider/list") .retrieve() .body(new ParameterizedTypeReference>>() {}); } - public ApiResponse> fetchChgr(String baseUrl, String token) { - URI uri = UriComponentsBuilder.fromUriString(baseUrl) - .path("/api/charger/list") - .build() - .toUri(); - - return restClient.post() - .uri(uri) - .header("Authorization", "Bearer " + token) + public ApiResponse> fetchChgr() { + return externalApiClient.post() + .uri("/api/charger/list") .body(Collections.emptyMap()) .retrieve() .body(new ParameterizedTypeReference>>() {}); } - public ApiResponse> fetchChgrHist(String baseUrl, String token) { - URI uri = UriComponentsBuilder.fromUriString(baseUrl) - .path("/api/charge/hist") - .build() - .toUri(); + public ApiResponse> fetchChgrHist(String startDt, String pageSize) { Map requestBody = Map.of( - "startDt", "2026-07-29", - "pageSize","50" + "startDt", startDt, + "pageSize", pageSize ); - return restClient.post() - .uri(uri) - .header("Authorization", "Bearer " + token) + return externalApiClient.post() + .uri("/api/charge/hist") .body(requestBody) .retrieve() .body(new ParameterizedTypeReference>>() {}); diff --git a/src/main/java/net/jwsi/jcms/vpp/api/ApiToken.java b/src/main/java/net/jwsi/jcms/vpp/api/ApiToken.java new file mode 100644 index 0000000..0025f57 --- /dev/null +++ b/src/main/java/net/jwsi/jcms/vpp/api/ApiToken.java @@ -0,0 +1,13 @@ +package net.jwsi.jcms.vpp.api; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@NoArgsConstructor +@Getter +public class ApiToken { + private String jwt; + private List roles; +} diff --git a/src/main/java/net/jwsi/jcms/vpp/api/ExternalApiService.java b/src/main/java/net/jwsi/jcms/vpp/api/ExternalApiService.java new file mode 100644 index 0000000..6773d2c --- /dev/null +++ b/src/main/java/net/jwsi/jcms/vpp/api/ExternalApiService.java @@ -0,0 +1,62 @@ +package net.jwsi.jcms.vpp.api; + +import net.jwsi.jcms.vpp.utils.JwtUtil; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; +import java.time.LocalDateTime; +import java.util.Map; + +@Service +public class ExternalApiService { + + private final RestClient authRestClient = RestClient.create(); + + @Value("${jcms.api.base-url}") + private String baseUrl; + + @Value("${jcms.api.user-id}") + private String apiUserId; + + @Value("${jcms.api.user-pwd}") + private String apiUserPwd; + + private String cachedToken; + private LocalDateTime tokenExpiryTime; + + public synchronized String getAccessToken() { + if (cachedToken != null && tokenExpiryTime != null && LocalDateTime.now().plusMinutes(1).isBefore(tokenExpiryTime)) { + return cachedToken; + } + + URI uri = UriComponentsBuilder.fromUriString(baseUrl) + .path("/api/auth/token") + .build() + .toUri(); + + Map requestBody = Map.of( + "userId", apiUserId, + "pwd", apiUserPwd + ); + + ApiResponse response = authRestClient.post() + .uri(uri) + .body(requestBody) + .retrieve() + .body(new ParameterizedTypeReference>() {}); + + if (response != null && response.getData() != null) { + ApiToken tokenDto = response.getData(); + this.cachedToken = tokenDto.getJwt(); + this.tokenExpiryTime = JwtUtil.getExpirationTime(this.cachedToken); + + return this.cachedToken; + } + + throw new IllegalStateException("외부 API 토큰 발급에 실패했습니다."); + } +} \ No newline at end of file diff --git a/src/main/java/net/jwsi/jcms/vpp/api/RestClientConfig.java b/src/main/java/net/jwsi/jcms/vpp/api/RestClientConfig.java index e131163..25aadf5 100644 --- a/src/main/java/net/jwsi/jcms/vpp/api/RestClientConfig.java +++ b/src/main/java/net/jwsi/jcms/vpp/api/RestClientConfig.java @@ -1,23 +1,25 @@ package net.jwsi.jcms.vpp.api; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.web.client.RestClient; -import java.time.Duration; - @Configuration public class RestClientConfig { - @Bean - public RestClient restClient() { - SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); - factory.setConnectTimeout((int) Duration.ofSeconds(5).toMillis()); - factory.setReadTimeout((int) Duration.ofSeconds(30).toMillis()); + @Value("${jcms.api.base-url}") + private String baseUrl; + @Bean + public RestClient externalApiClient(ExternalApiService externalApiService) { return RestClient.builder() - .requestFactory(factory) + .baseUrl(baseUrl) + .requestInterceptor((request, body, execution) -> { + String token = externalApiService.getAccessToken(); + request.getHeaders().set("Authorization", "Bearer " + token); + return execution.execute(request, body); + }) .build(); } } \ No newline at end of file diff --git a/src/main/java/net/jwsi/jcms/vpp/chgr/ChgrService.java b/src/main/java/net/jwsi/jcms/vpp/chgr/ChgrService.java index 4294408..071fcde 100644 --- a/src/main/java/net/jwsi/jcms/vpp/chgr/ChgrService.java +++ b/src/main/java/net/jwsi/jcms/vpp/chgr/ChgrService.java @@ -8,7 +8,6 @@ import net.jwsi.jcms.utils.HttpUtil; import net.jwsi.jcms.utils.LoginUtil; import net.jwsi.jcms.vpp.api.ApiClient; import net.jwsi.jcms.vpp.api.ApiResponse; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.jpa.domain.Specification; import org.springframework.scheduling.annotation.Scheduled; @@ -22,11 +21,6 @@ import java.util.stream.Collectors; public class ChgrService extends BaseService { private final ApiClient apiClient; - @Value("${jcms.api.base-url}") - private String baseUrl; - - @Value("${jcms.api.token}") - private String apiToken; public ChgrService(ChgrRepository repository, ApiClient apiClient) { super(repository); this.apiClient = apiClient; @@ -34,7 +28,7 @@ public class ChgrService extends BaseService { @Transactional @Scheduled(cron = "0 0 0 * * *") public void getCharger() { - ApiResponse> response = apiClient.fetchChgr(baseUrl, apiToken); + ApiResponse> response = apiClient.fetchChgr(); if (response == null || !Integer.valueOf(200).equals(response.getCode())) { String msg = (response != null) ? response.getMessage() : "응답 없음"; diff --git a/src/main/java/net/jwsi/jcms/vpp/chgrHist/ChgrHist.java b/src/main/java/net/jwsi/jcms/vpp/chgrHist/ChgrHist.java index a2796ea..e157e98 100644 --- a/src/main/java/net/jwsi/jcms/vpp/chgrHist/ChgrHist.java +++ b/src/main/java/net/jwsi/jcms/vpp/chgrHist/ChgrHist.java @@ -40,7 +40,7 @@ public class ChgrHist extends BaseStEntity { @Column(name = "RECHG_TIME") private String rechgTime; // 충전시간 - @Column(name = "RECH_GWH") + @Column(name = "RECHG_WH") private Integer rechGwh; // 충전량 @Column(name = "RECHG_AMT") diff --git a/src/main/java/net/jwsi/jcms/vpp/chgrHist/ChgrHistService.java b/src/main/java/net/jwsi/jcms/vpp/chgrHist/ChgrHistService.java index abd745e..4ef0b8c 100644 --- a/src/main/java/net/jwsi/jcms/vpp/chgrHist/ChgrHistService.java +++ b/src/main/java/net/jwsi/jcms/vpp/chgrHist/ChgrHistService.java @@ -8,12 +8,12 @@ import net.jwsi.jcms.utils.HttpUtil; import net.jwsi.jcms.utils.LoginUtil; import net.jwsi.jcms.vpp.api.ApiClient; import net.jwsi.jcms.vpp.api.ApiResponse; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.jpa.domain.Specification; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -23,11 +23,6 @@ public class ChgrHistService extends BaseService> response = apiClient.fetchChgrHist(baseUrl, apiToken); + + ApiResponse> response = apiClient.fetchChgrHist(LocalDateTime.now().toString(), "50"); if (response == null || !Integer.valueOf(200).equals(response.getCode())) { String msg = (response != null) ? response.getMessage() : "응답 없음"; diff --git a/src/main/java/net/jwsi/jcms/vpp/provider/ProviderService.java b/src/main/java/net/jwsi/jcms/vpp/provider/ProviderService.java index db0648e..9fe9517 100644 --- a/src/main/java/net/jwsi/jcms/vpp/provider/ProviderService.java +++ b/src/main/java/net/jwsi/jcms/vpp/provider/ProviderService.java @@ -8,15 +8,11 @@ import net.jwsi.jcms.utils.HttpUtil; import net.jwsi.jcms.utils.LoginUtil; import net.jwsi.jcms.vpp.api.ApiClient; import net.jwsi.jcms.vpp.api.ApiResponse; -import net.jwsi.jcms.vpp.station.Station; -import net.jwsi.jcms.vpp.station.StationDto; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.jpa.domain.Specification; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -26,12 +22,6 @@ public class ProviderService extends BaseService> response = apiClient.fetchProviders(baseUrl, apiToken); + ApiResponse> response = apiClient.fetchProviders(); if (response == null || !Integer.valueOf(200).equals(response.getCode())) { String msg = (response != null) ? response.getMessage() : "응답 없음"; diff --git a/src/main/java/net/jwsi/jcms/vpp/station/StationService.java b/src/main/java/net/jwsi/jcms/vpp/station/StationService.java index fdf4eee..772240e 100644 --- a/src/main/java/net/jwsi/jcms/vpp/station/StationService.java +++ b/src/main/java/net/jwsi/jcms/vpp/station/StationService.java @@ -6,15 +6,13 @@ import net.jwsi.jcms.base.BaseService; import net.jwsi.jcms.base.EnumSqlType; import net.jwsi.jcms.utils.HttpUtil; import net.jwsi.jcms.utils.LoginUtil; -import net.jwsi.jcms.vpp.api.ApiResponse; import net.jwsi.jcms.vpp.api.ApiClient; -import org.springframework.beans.factory.annotation.Value; +import net.jwsi.jcms.vpp.api.ApiResponse; import org.springframework.data.domain.Page; import org.springframework.data.jpa.domain.Specification; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -23,11 +21,7 @@ import java.util.stream.Collectors; public class StationService extends BaseService { private final ApiClient apiClient; - @Value("${jcms.api.base-url}") - private String baseUrl; - @Value("${jcms.api.token}") - private String apiToken; public StationService(StationRepository repository, ApiClient apiClient) { super(repository); this.apiClient = apiClient; @@ -36,7 +30,7 @@ public class StationService extends BaseService> response = apiClient.fetchStations(baseUrl, apiToken); + ApiResponse> response = apiClient.fetchStations(); if (response == null || !Integer.valueOf(200).equals(response.getCode())) { String msg = (response != null) ? response.getMessage() : "응답 없음"; diff --git a/src/main/java/net/jwsi/jcms/vpp/utils/JwtUtil.java b/src/main/java/net/jwsi/jcms/vpp/utils/JwtUtil.java new file mode 100644 index 0000000..ef68d2f --- /dev/null +++ b/src/main/java/net/jwsi/jcms/vpp/utils/JwtUtil.java @@ -0,0 +1,38 @@ +package net.jwsi.jcms.vpp.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Base64; + +public class JwtUtil { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + public static LocalDateTime getExpirationTime(String jwtToken) { + try { + String[] parts = jwtToken.split("\\."); + if (parts.length < 2) { + throw new IllegalArgumentException("올바르지 않은 JWT 토큰 형식입니다."); + } + + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); + + JsonNode payloadNode = objectMapper.readTree(payloadJson); + if (!payloadNode.has("exp")) { + throw new IllegalStateException("토큰에 exp(만료시간) 필드가 존재하지 않습니다."); + } + + long expSeconds = payloadNode.get("exp").asLong(); + + return LocalDateTime.ofInstant(Instant.ofEpochSecond(expSeconds), ZoneId.systemDefault()); + + } catch (Exception e) { + throw new RuntimeException("토큰 만료 시간 추출 실패", e); + } + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b8d99f0..775bf15 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -19,12 +19,12 @@ jcms: manager-path: cms attr: main-url: /cms/main - new-user-st: 9 # 1: ??, 9:???? + new-user-st: 9 cms-name: VPP cms-title: VPP cms-bg-color: '#499' cms-image-url: /public/images/logo_jcms3.png - copyright: Copyright 2026 ??????????? Inc. + copyright: Copyright 2026 주식회사 진우시스템 Inc. authenticated-patten: /cms/**, /system/**, /api/** pass-url: /cms/main public-url: '' @@ -33,4 +33,5 @@ jcms: days: 30 api: base-url: ${API_BASE_URL:http://dev.jinwoosi.co.kr:8072} - token: ${API_TOKEN} \ No newline at end of file + user-id: ${API_USER_ID:apiuser} + user-pwd: ${API_USER_PWD:apiuser} \ No newline at end of file