Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcfdfe52a9 | ||
|
|
14f96f037c |
Binary file not shown.
@@ -2,15 +2,14 @@ package net.jwsi.jcms.vpp.api;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.jwsi.jcms.vpp.chgr.ChgrResponseDto;
|
||||
import net.jwsi.jcms.vpp.chgrCurrState.ChgrCurrState;
|
||||
import net.jwsi.jcms.vpp.chgrHist.ChgrHistResponseDto;
|
||||
import net.jwsi.jcms.vpp.provider.ProviderDto;
|
||||
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,64 +18,50 @@ import java.util.Map;
|
||||
@RequiredArgsConstructor
|
||||
public class ApiClient {
|
||||
|
||||
private final RestClient restClient;
|
||||
private final RestClient externalApiClient;
|
||||
|
||||
public ApiResponse<List<StationDto>> 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<List<StationDto>> fetchStations() {
|
||||
return externalApiClient.post()
|
||||
.uri("/api/st/list")
|
||||
.body(Collections.emptyMap())
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<ApiResponse<List<StationDto>>>() {});
|
||||
}
|
||||
|
||||
public ApiResponse<List<ProviderDto>> 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<List<ProviderDto>> fetchProviders() {
|
||||
return externalApiClient.get()
|
||||
.uri("/api/provider/list")
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<ApiResponse<List<ProviderDto>>>() {});
|
||||
}
|
||||
|
||||
public ApiResponse<List<ChgrResponseDto>> 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<List<ChgrResponseDto>> fetchChgr() {
|
||||
return externalApiClient.post()
|
||||
.uri("/api/charger/list")
|
||||
.body(Collections.emptyMap())
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<ApiResponse<List<ChgrResponseDto>>>() {});
|
||||
}
|
||||
public ApiResponse<List<ChgrHistResponseDto>> fetchChgrHist(String baseUrl, String token) {
|
||||
URI uri = UriComponentsBuilder.fromUriString(baseUrl)
|
||||
.path("/api/charge/hist")
|
||||
.build()
|
||||
.toUri();
|
||||
|
||||
public ApiResponse<List<ChgrHistResponseDto>> fetchChgrHist(String startDt, String pageSize) {
|
||||
Map<String, String> 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<ApiResponse<List<ChgrHistResponseDto>>>() {});
|
||||
}
|
||||
|
||||
public ApiResponse<List<ChgrCurrState>> fetchChgrCurrState() {
|
||||
|
||||
return externalApiClient.post()
|
||||
.uri("/api/charge/stateInfo")
|
||||
.body(Collections.emptyMap())
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<ApiResponse<List<ChgrCurrState>>>() {});
|
||||
}
|
||||
}
|
||||
@@ -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<String> roles;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
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<String, String> requestBody = Map.of(
|
||||
"userId", apiUserId,
|
||||
"pwd", apiUserPwd
|
||||
);
|
||||
|
||||
ApiResponse<ApiToken> response = authRestClient.post()
|
||||
.uri(uri)
|
||||
.body(requestBody)
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<ApiResponse<ApiToken>>() {});
|
||||
|
||||
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 토큰 발급에 실패했습니다.");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<Chgr,String,ChgrRepository> {
|
||||
|
||||
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<Chgr,String,ChgrRepository> {
|
||||
@Transactional
|
||||
@Scheduled(cron = "0 0 0 * * *")
|
||||
public void getCharger() {
|
||||
ApiResponse<List<ChgrResponseDto>> response = apiClient.fetchChgr(baseUrl, apiToken);
|
||||
ApiResponse<List<ChgrResponseDto>> response = apiClient.fetchChgr();
|
||||
|
||||
if (response == null || !Integer.valueOf(200).equals(response.getCode())) {
|
||||
String msg = (response != null) ? response.getMessage() : "응답 없음";
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package net.jwsi.jcms.vpp.chgrCurrState;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import net.jwsi.jcms.base.BaseStEntity;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@Entity(name = "vpp_chgr_curr_state")
|
||||
@IdClass(ChgrCurrStateId.class)
|
||||
public class ChgrCurrState extends BaseStEntity {
|
||||
|
||||
@Transient
|
||||
private ChgrCurrStateId oldId__;
|
||||
|
||||
@Id
|
||||
@Column(name = "provider_id")
|
||||
private String providerId; // provider_id (PK)
|
||||
|
||||
@Id
|
||||
@Column(name = "st_id")
|
||||
private String stId; // st_id (PK)
|
||||
|
||||
@Id
|
||||
@Column(name = "chgr_id")
|
||||
private String chgrId; // chgr_id (PK)
|
||||
|
||||
@Column(name = "state_dt")
|
||||
private String stateDt;
|
||||
|
||||
@Column(name = "op_mode")
|
||||
private String opMode;
|
||||
|
||||
@Column(name = "ch1_rechg_state_cd")
|
||||
private String ch1RechgStateCd;
|
||||
|
||||
@Column(name = "ch1_door_state_cd")
|
||||
private String ch1DoorStateCd;
|
||||
|
||||
@Column(name = "ch1_plug_state_cd")
|
||||
private String ch1PlugStateCd;
|
||||
|
||||
@Column(name = "ch2_rechg_state_cd")
|
||||
private String ch2RechgStateCd;
|
||||
|
||||
@Column(name = "ch2_door_state_cd")
|
||||
private String ch2DoorStateCd;
|
||||
|
||||
@Column(name = "ch2_plug_state_cd")
|
||||
private String ch2PlugStateCd;
|
||||
|
||||
@Column(name = "ch3_rechg_state_cd")
|
||||
private String ch3RechgStateCd;
|
||||
|
||||
@Column(name = "ch3_door_state_cd")
|
||||
private String ch3DoorStateCd;
|
||||
|
||||
@Column(name = "ch3_plug_state_cd")
|
||||
private String ch3PlugStateCd;
|
||||
|
||||
@Column(name = "integrated_kwh")
|
||||
private Double integratedKwh;
|
||||
|
||||
@Column(name = "dc_power_wh")
|
||||
private Double dcPowerWh;
|
||||
|
||||
@Column(name = "ac_power_wh")
|
||||
private Double acPowerWh;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package net.jwsi.jcms.vpp.chgrCurrState;
|
||||
|
||||
import net.jwsi.jcms.base.BaseController;
|
||||
import net.jwsi.jcms.exception.JsonDataException;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/cms/chgrCurrState")
|
||||
public class ChgrCurrStateController extends BaseController<ChgrCurrState,ChgrCurrStateId,ChgrCurrStateRepository,ChgrCurrStateService> {
|
||||
|
||||
private final ChgrCurrStateService chgrCurrStateService;
|
||||
private final String path = "/system/chgrCurrState/";
|
||||
|
||||
public ChgrCurrStateController(ChgrCurrStateService service, ChgrCurrStateService chgrCurrStateService) {
|
||||
super(service);
|
||||
this.chgrCurrStateService = chgrCurrStateService;
|
||||
}
|
||||
|
||||
@GetMapping("list")
|
||||
public String list(){
|
||||
chgrCurrStateService.getChgrCurrState();
|
||||
return path+"list";
|
||||
}
|
||||
@PostMapping("list.json")
|
||||
@ResponseBody
|
||||
public Map<String,Object> list(ChgrCurrState chgrCurrState){
|
||||
return svcSelectList(chgrCurrState);
|
||||
}
|
||||
@PostMapping("view.json")
|
||||
@ResponseBody
|
||||
public Map<String, Object> viewData(ChgrCurrState chgrCurrState) throws JsonDataException {
|
||||
return svcSelectOne(chgrCurrState);
|
||||
}
|
||||
|
||||
@PostMapping("insert.json")
|
||||
@ResponseBody
|
||||
public Map<String, Object> insert(ChgrCurrState chgrCurrState) throws JsonDataException {
|
||||
return svcInsert(chgrCurrState);
|
||||
}
|
||||
|
||||
@PostMapping("update.json")
|
||||
@ResponseBody
|
||||
public Map<String, Object> update(ChgrCurrState chgrCurrState) throws JsonDataException {
|
||||
return svcUpdate(chgrCurrState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void chkSelectList(List<ChgrCurrState> list) throws JsonDataException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void chkSelect(ChgrCurrState data) throws JsonDataException {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.jwsi.jcms.vpp.chgrCurrState;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class ChgrCurrStateId implements Serializable {
|
||||
|
||||
private String providerId;
|
||||
private String stId;
|
||||
private String chgrId;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.jwsi.jcms.vpp.chgrCurrState;
|
||||
|
||||
import net.jwsi.jcms.base.BaseRecStRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface ChgrCurrStateRepository extends BaseRecStRepository<ChgrCurrState,ChgrCurrStateId> {
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package net.jwsi.jcms.vpp.chgrCurrState;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.ApiClient;
|
||||
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.List;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ChgrCurrStateService extends BaseService<ChgrCurrState,ChgrCurrStateId,ChgrCurrStateRepository> {
|
||||
|
||||
private final ApiClient apiClient;
|
||||
|
||||
public ChgrCurrStateService(ChgrCurrStateRepository repository, ApiClient apiClient) {
|
||||
super(repository);
|
||||
this.apiClient = apiClient;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Scheduled(cron = "0 0 0 * * *")
|
||||
public void getChgrCurrState() {
|
||||
ApiResponse<List<ChgrCurrState>> response = apiClient.fetchChgrCurrState();
|
||||
|
||||
if (response == null || !Integer.valueOf(200).equals(response.getCode())) {
|
||||
String msg = (response != null) ? response.getMessage() : "응답 없음";
|
||||
throw new RuntimeException("API 호출 실패: " + msg);
|
||||
}
|
||||
|
||||
List<ChgrCurrState> list = response.getData();
|
||||
if (list == null || list.isEmpty()) {
|
||||
log.info("가져온 충전소 데이터가 없습니다.");
|
||||
}
|
||||
|
||||
List<ChgrCurrState> savedStations = super.repository.saveAll(list);
|
||||
|
||||
log.info("총 {}건의 충전소 데이터가 DB에 동기화되었습니다.", savedStations.size());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer getUserId() {
|
||||
return HttpUtil.getUserId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getUserName(Integer id) {
|
||||
return LoginUtil.getUserName(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChgrCurrState newSearchParam(ChgrCurrState chgrCurrState) {
|
||||
if(chgrCurrState == null){
|
||||
chgrCurrState = new ChgrCurrState();
|
||||
}
|
||||
return chgrCurrState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EnumSqlType useSqlTyp() {
|
||||
return EnumSqlType.SPECIFICATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Specification<ChgrCurrState> getSpecification(ChgrCurrState chgrCurrState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Page<ChgrCurrState> _selectPage(ChgrCurrState chgrCurrState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ChgrCurrState> _selectList(ChgrCurrState chgrCurrState) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -8,13 +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.util.ArrayList;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@@ -23,11 +22,6 @@ public class ChgrHistService extends BaseService<ChgrHist, Integer, ChgrHistRepo
|
||||
|
||||
|
||||
private final ApiClient apiClient;
|
||||
@Value("${jcms.api.base-url}")
|
||||
private String baseUrl;
|
||||
|
||||
@Value("${jcms.api.token}")
|
||||
private String apiToken;
|
||||
|
||||
public ChgrHistService(ChgrHistRepository repository, ApiClient apiClient) {
|
||||
super(repository);
|
||||
@@ -37,7 +31,9 @@ public class ChgrHistService extends BaseService<ChgrHist, Integer, ChgrHistRepo
|
||||
@Transactional
|
||||
@Scheduled(cron = "0 */10 * * * *")
|
||||
public void getChgrHist() throws IllegalAccessException {
|
||||
ApiResponse<List<ChgrHistResponseDto>> response = apiClient.fetchChgrHist(baseUrl, apiToken);
|
||||
|
||||
String startDt = LocalDate.now().toString();
|
||||
ApiResponse<List<ChgrHistResponseDto>> response = apiClient.fetchChgrHist(startDt, "50");
|
||||
|
||||
if (response == null || !Integer.valueOf(200).equals(response.getCode())) {
|
||||
String msg = (response != null) ? response.getMessage() : "응답 없음";
|
||||
@@ -46,21 +42,30 @@ public class ChgrHistService extends BaseService<ChgrHist, Integer, ChgrHistRepo
|
||||
|
||||
List<ChgrHistResponseDto> dtoList = response.getData();
|
||||
if (dtoList == null || dtoList.isEmpty()) {
|
||||
log.info("가져온 충전소 데이터가 없습니다.");
|
||||
log.info("가져온 충전이력 데이터가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<ChgrHist> entityList = dtoList.stream()
|
||||
.map(ChgrHistResponseDto::toEntity)
|
||||
.toList();
|
||||
|
||||
List<ChgrHist> updateList = new ArrayList<>();
|
||||
int insertCount = 0;
|
||||
int updateCount = 0;
|
||||
|
||||
for (ChgrHist h : entityList) {
|
||||
update(h);
|
||||
updateList.add(h);
|
||||
Integer id = BaseService.getId(h);
|
||||
|
||||
if (id != null && super.repository.existsById(id)) {
|
||||
super.update(h);
|
||||
updateCount++;
|
||||
} else {
|
||||
super.insert(h);
|
||||
insertCount++;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("총 {}건의 충전소 데이터가 DB에 동기화되었습니다.", updateList.size());
|
||||
|
||||
log.info("충전이력 동기화 완료 (신규 추가: {}건 / 기존 업데이트: {}건)", insertCount, updateCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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<Provider, String, ProviderRepos
|
||||
|
||||
private final ApiClient apiClient;
|
||||
|
||||
@Value("${jcms.api.base-url}")
|
||||
private String baseUrl;
|
||||
|
||||
@Value("${jcms.api.token}")
|
||||
private String apiToken;
|
||||
|
||||
public ProviderService(ProviderRepository repository, ApiClient apiClient) {
|
||||
super(repository);
|
||||
this.apiClient = apiClient;
|
||||
@@ -40,7 +30,7 @@ public class ProviderService extends BaseService<Provider, String, ProviderRepos
|
||||
@Transactional
|
||||
@Scheduled(cron = "0 0 0 * * *")
|
||||
public void getProviders() {
|
||||
ApiResponse<List<ProviderDto>> response = apiClient.fetchProviders(baseUrl, apiToken);
|
||||
ApiResponse<List<ProviderDto>> response = apiClient.fetchProviders();
|
||||
|
||||
if (response == null || !Integer.valueOf(200).equals(response.getCode())) {
|
||||
String msg = (response != null) ? response.getMessage() : "응답 없음";
|
||||
|
||||
@@ -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<Station,String,StationRepository> {
|
||||
|
||||
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<Station,String,StationRepository
|
||||
@Transactional
|
||||
@Scheduled(cron = "0 0 0 * * *")
|
||||
public void getStations() {
|
||||
ApiResponse<List<StationDto>> response = apiClient.fetchStations(baseUrl, apiToken);
|
||||
ApiResponse<List<StationDto>> response = apiClient.fetchStations();
|
||||
|
||||
if (response == null || !Integer.valueOf(200).equals(response.getCode())) {
|
||||
String msg = (response != null) ? response.getMessage() : "응답 없음";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
user-id: ${API_USER_ID:apiuser}
|
||||
user-pwd: ${API_USER_PWD:apiuser}
|
||||
@@ -0,0 +1,307 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/cmsLayout}">
|
||||
<body>
|
||||
<section layout:fragment="Content">
|
||||
<div class="w-100" style="margin-bottom: .3rem;">
|
||||
<div th:replace="~{common/fragments/searchBox :: SearchBoxFragment(~{ :: #searchBoxContent}, ~{ :: #searchBoxFooter})}">
|
||||
<div id="searchBoxContent">
|
||||
<div class="row px-2">
|
||||
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="width:180px">provider_id</span>
|
||||
<input type="text" id="providerIdLike" name="providerIdLike" class="form-control" placeholder="provider_id 입력" maxlength="50">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="width:180px">st_id</span>
|
||||
<input type="text" id="stIdLike" name="stIdLike" class="form-control" placeholder="st_id 입력" maxlength="50">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="width:180px">chgr_id</span>
|
||||
<input type="text" id="chgrIdLike" name="chgrIdLike" class="form-control" placeholder="chgr_id 입력" maxlength="50">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="width:180px">상태 일시</span>
|
||||
<input type="text" id="stateDtLike" name="stateDtLike" class="form-control" placeholder="상태 일시 입력" maxlength="50">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="width:180px">op_mode</span>
|
||||
<input type="text" id="opModeLike" name="opModeLike" class="form-control" placeholder="op_mode_cd 입력" maxlength="50">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="width:180px">ch1_rechg_state_cd</span>
|
||||
<select id="ch1RechgStateCdLike" name="ch1RechgStateCdLike" class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="width:180px">ch1_door_state_cd</span>
|
||||
<select id="ch1DoorStateCdLike" name="ch1DoorStateCdLike" class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="width:180px">ch1_plug_state_cd</span>
|
||||
<select id="ch1PlugStateCdLike" name="ch1PlugStateCdLike" class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- row 닫힘 -->
|
||||
</div> <!-- searchBoxContent 닫힘 -->
|
||||
|
||||
<div id="searchBoxFooter">
|
||||
<div class="row float-end">
|
||||
<div class="col-xs-12">
|
||||
<button type="button" class="btn btn-sm btn-info" id="searchBtn">검색</button>
|
||||
<button type="button" class="btn btn-sm btn-info" id="resetBtn">리셋</button>
|
||||
<th:block th:if="${menuRole?.roleWrite eq true}">
|
||||
<button type="button" class="btn btn-sm btn-primary pl-2" onclick="showModal(ModalMode.REGISTER, {}, '');">등록</button>
|
||||
</th:block>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- searchBoxFooter 닫힘 -->
|
||||
</div> <!-- SearchBoxFragment 닫힘 -->
|
||||
</div> <!-- w-100 닫힘 -->
|
||||
|
||||
<div class="w-100">
|
||||
<div class="box box-primary">
|
||||
<div class="form">
|
||||
<table id="datatable" class="table table-striped table-hover dataTable" style="width:100%;"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 권한 변수 선언 -->
|
||||
<script th:inline="javascript">
|
||||
const menuRoleWrite = /*[[${menuRole != null ? menuRole.roleWrite : false}]]*/ false;
|
||||
const menuRoleModify = /*[[${menuRole != null ? menuRole.roleModify : false}]]*/ false;
|
||||
const menuRoleDel = /*[[${menuRole != null ? menuRole.roleDel : false}]]*/ false;
|
||||
</script>
|
||||
|
||||
<script th:inline="none">
|
||||
const rootPath = "/cms/chgrCurrState/";
|
||||
let datatable;
|
||||
|
||||
const ch1RechgStateCdCdList = getCdDtlList("rechgState");
|
||||
const ch1DoorStateCdCdList = getCdDtlList("doorState");
|
||||
const ch1PlugStateCdCdList = getCdDtlList("plugState");
|
||||
|
||||
$(document).ready(function() {
|
||||
setSearchCdDtlOptions("ch1RechgStateCdLike", ch1RechgStateCdCdList);
|
||||
setSearchCdDtlOptions("ch1DoorStateCdLike", ch1DoorStateCdCdList);
|
||||
setSearchCdDtlOptions("ch1PlugStateCdLike", ch1PlugStateCdCdList);
|
||||
|
||||
const className = "dt-head-center dt-body-center";
|
||||
datatable = newDataTable(
|
||||
"#datatable",
|
||||
rootPath + "list.json",
|
||||
function(d) {
|
||||
if (strUtil.isNotEmpty($('#providerIdLike').val())) {d.providerId = $('#providerIdLike').val();}
|
||||
if (strUtil.isNotEmpty($('#stIdLike').val())) {d.stId = $('#stIdLike').val();}
|
||||
if (strUtil.isNotEmpty($('#chgrIdLike').val())) {d.chgrId = $('#chgrIdLike').val();}
|
||||
if (strUtil.isNotEmpty($('#stateDtLike').val())) {d.stateDt = $('#stateDtLike').val();}
|
||||
if (strUtil.isNotEmpty($('#opModeLike').val())) {d.opMode = $('#opModeLike').val();}
|
||||
if (strUtil.isNotEmpty($('#ch1RechgStateCdLike').val())) {d.ch1RechgStateCd = $('#ch1RechgStateCdLike').val();}
|
||||
if (strUtil.isNotEmpty($('#ch1DoorStateCdLike').val())) {d.ch1DoorStateCd = $('#ch1DoorStateCdLike').val();}
|
||||
if (strUtil.isNotEmpty($('#ch1PlugStateCdLike').val())) {d.ch1PlugStateCd = $('#ch1PlugStateCdLike').val();}
|
||||
return d;
|
||||
},
|
||||
{
|
||||
order: [],
|
||||
columns: [
|
||||
{title: "사업자 ID", name: "provider_id", data: "providerId", className},
|
||||
{title: "충전소 ID", name: "st_id", data: "stId", className},
|
||||
{title: "충전기 ID", name: "chgr_id", data: "chgrId", className},
|
||||
{title: "상태 일시", name: "state_dt", data: "stateDt", className, orderable: false},
|
||||
{title: "운영모드", name: "op_mode", data: "opMode", className, orderable: false},
|
||||
|
||||
{title: "CH1 상태", orderable: false, className,
|
||||
render: (data, type, row) => {
|
||||
let s1 = getCdDtlDtColNm(ch1RechgStateCdCdList, row["ch1RechgStateCd"]) || "";
|
||||
let s2 = getCdDtlDtColNm(ch1DoorStateCdCdList, row["ch1DoorStateCd"]) || "";
|
||||
let s3 = getCdDtlDtColNm(ch1PlugStateCdCdList, row["ch1PlugStateCd"]) || "";
|
||||
return `<div class="d-flex justify-content-center gap-1">${s1} ${s2} ${s3}</div>`;
|
||||
}
|
||||
},
|
||||
{title: "integrated_kwh", name: "integrated_kwh", data: "integratedKwh", className, visible: false},
|
||||
{title: "dc_power_wh", name: "dc_power_wh", data: "dcPowerWh", className, visible: false},
|
||||
{title: "ac_power_wh", name: "ac_power_wh", data: "acPowerWh", className, visible: false},
|
||||
|
||||
{title: "기능", orderable:false, width:"110px",
|
||||
render: (data, type, row) => {
|
||||
return mkRowDataFunctions({providerId: row['providerId'], stId: row['stId'], chgrId: row['chgrId']}, row['stId'], true, menuRoleModify, menuRoleDel);
|
||||
}
|
||||
}
|
||||
],
|
||||
allCheck: false,
|
||||
});
|
||||
});
|
||||
|
||||
const showModal = (mode, ids, title) => {
|
||||
if(!checkRole(mode)) { alert("권한이 없습니다."); return; }
|
||||
|
||||
let modalInputArray = [
|
||||
new ModalInput({
|
||||
inputType: ModalInputType.text,
|
||||
inputId: "providerId",
|
||||
inputName: "providerId",
|
||||
hasOldPk: true,
|
||||
inputLabel: "provider_id",
|
||||
inputPlaceholder: "provider_id",
|
||||
isReq: false,
|
||||
isEnable: true,
|
||||
minLen: 0,
|
||||
maxLen: 20,
|
||||
}),
|
||||
new ModalInput({
|
||||
inputType: ModalInputType.text,
|
||||
inputId: "stId",
|
||||
inputName: "stId",
|
||||
hasOldPk: true,
|
||||
inputLabel: "st_id",
|
||||
inputPlaceholder: "st_id",
|
||||
isReq: false,
|
||||
isEnable: true,
|
||||
minLen: 0,
|
||||
maxLen: 20,
|
||||
}),
|
||||
new ModalInput({
|
||||
inputType: ModalInputType.text,
|
||||
inputId: "chgrId",
|
||||
inputName: "chgrId",
|
||||
hasOldPk: true,
|
||||
inputLabel: "chgr_id",
|
||||
inputPlaceholder: "chgr_id",
|
||||
isReq: false,
|
||||
isEnable: true,
|
||||
minLen: 0,
|
||||
maxLen: 20,
|
||||
}),
|
||||
new ModalInput({
|
||||
inputType: ModalInputType.textarea,
|
||||
inputId: "stateDt",
|
||||
inputName: "stateDt",
|
||||
inputLabel: "상태 일시",
|
||||
inputPlaceholder: "상태 일시",
|
||||
isReq: false,
|
||||
isEnable: true,
|
||||
minLen: 0,
|
||||
maxLen: 250,
|
||||
}),
|
||||
new ModalInput({
|
||||
inputType: ModalInputType.textarea,
|
||||
inputId: "opMode",
|
||||
inputName: "opMode",
|
||||
inputLabel: "op_mode_cd",
|
||||
inputPlaceholder: "op_mode_cd",
|
||||
isReq: false,
|
||||
isEnable: true,
|
||||
minLen: 0,
|
||||
maxLen: 250,
|
||||
}),
|
||||
new ModalInput({
|
||||
inputType: ModalInputType.select,
|
||||
inputId: "ch1RechgStateCd",
|
||||
inputName: "ch1RechgStateCd",
|
||||
inputLabel: "ch1_rechg_state_cd",
|
||||
inputPlaceholder: "ch1_rechg_state_cd",
|
||||
isReq: false,
|
||||
isEnable: true,
|
||||
itemArray: toCdDtlItemArray(ch1RechgStateCdCdList),
|
||||
}),
|
||||
new ModalInput({
|
||||
inputType: ModalInputType.select,
|
||||
inputId: "ch1DoorStateCd",
|
||||
inputName: "ch1DoorStateCd",
|
||||
inputLabel: "ch1_door_state_cd",
|
||||
inputPlaceholder: "ch1_door_state_cd",
|
||||
isReq: false,
|
||||
isEnable: true,
|
||||
itemArray: toCdDtlItemArray(ch1DoorStateCdCdList),
|
||||
}),
|
||||
new ModalInput({
|
||||
inputType: ModalInputType.select,
|
||||
inputId: "ch1PlugStateCd",
|
||||
inputName: "ch1PlugStateCd",
|
||||
inputLabel: "ch1_plug_state_cd",
|
||||
inputPlaceholder: "ch1_plug_state_cd",
|
||||
isReq: false,
|
||||
isEnable: true,
|
||||
itemArray: toCdDtlItemArray(ch1PlugStateCdCdList),
|
||||
}),
|
||||
new ModalInput({
|
||||
inputType: ModalInputType.text,
|
||||
inputId: "integratedKwh",
|
||||
inputName: "integratedKwh",
|
||||
inputLabel: "integrated_kwh",
|
||||
inputPlaceholder: "integrated_kwh",
|
||||
isReq: false,
|
||||
isEnable: true,
|
||||
}),
|
||||
new ModalInput({
|
||||
inputType: ModalInputType.text,
|
||||
inputId: "dcPowerWh",
|
||||
inputName: "dcPowerWh",
|
||||
inputLabel: "dc_power_wh",
|
||||
inputPlaceholder: "dc_power_wh",
|
||||
isReq: false,
|
||||
isEnable: true,
|
||||
}),
|
||||
new ModalInput({
|
||||
inputType: ModalInputType.text,
|
||||
inputId: "acPowerWh",
|
||||
inputName: "acPowerWh",
|
||||
inputLabel: "ac_power_wh",
|
||||
inputPlaceholder: "ac_power_wh",
|
||||
isReq: false,
|
||||
isEnable: true,
|
||||
})
|
||||
];
|
||||
|
||||
if(!rootPath){ alert("rootPath를 지정하십시요."); return; }
|
||||
const insertUrl = rootPath+"insert.json";
|
||||
const updateUrl = rootPath+"update.json";
|
||||
const loadUrl = rootPath+"view.json";
|
||||
const deleteUrl = rootPath+"delete.json";
|
||||
const modalWidth = 500;
|
||||
const labelWidth = '140px';
|
||||
|
||||
if(mode === ModalMode.REGISTER) {
|
||||
newModal(new ModalInfo({modalTitle: "등록", inputArray: modalInputArray, modalWidth}), mode, 'main', {insertUrl, labelWidth, callBack: () => { defaultCallback(); }})
|
||||
} else if(mode===ModalMode.VIEW || mode===ModalMode.VIEW_ONLY || mode===ModalMode.MODIFY) {
|
||||
newModal(new ModalInfo({modalTitle: "상세정보", inputArray: modalInputArray, modalWidth}), mode, 'main', {loadUrl, updateUrl, ids, labelWidth, callBack: () => { defaultCallback(); }})
|
||||
} else if(mode === ModalMode.DELETE) {
|
||||
newDelete(deleteUrl, ids, title, () => { defaultCallback(); });
|
||||
}
|
||||
const defaultCallback = () => {
|
||||
datatable.ajax.reload();
|
||||
}
|
||||
};
|
||||
|
||||
$("#searchBtn").click(function() {
|
||||
datatable.ajax.reload();
|
||||
});
|
||||
|
||||
$("#resetBtn").click(function() {
|
||||
$("#providerIdLike").val("");
|
||||
$("#stIdLike").val("");
|
||||
$("#chgrIdLike").val("");
|
||||
$("#stateDtLike").val("");
|
||||
$("#opModeLike").val("");
|
||||
$("#ch1RechgStateCdLike").val("");
|
||||
$("#ch1DoorStateCdLike").val("");
|
||||
$("#ch1PlugStateCdLike").val("");
|
||||
datatable.ajax.reload();
|
||||
});
|
||||
</script>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user