chgrCurrState 소스정리

This commit is contained in:
2026-07-29 17:19:42 +09:00
parent 14f96f037c
commit bcfdfe52a9
9 changed files with 588 additions and 10 deletions
@@ -2,6 +2,7 @@ package net.jwsi.jcms.vpp.api;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.jwsi.jcms.vpp.chgr.ChgrResponseDto; 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.chgrHist.ChgrHistResponseDto;
import net.jwsi.jcms.vpp.provider.ProviderDto; import net.jwsi.jcms.vpp.provider.ProviderDto;
import net.jwsi.jcms.vpp.station.StationDto; import net.jwsi.jcms.vpp.station.StationDto;
@@ -54,4 +55,13 @@ public class ApiClient {
.retrieve() .retrieve()
.body(new ParameterizedTypeReference<ApiResponse<List<ChgrHistResponseDto>>>() {}); .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>>>() {});
}
} }
@@ -20,7 +20,8 @@ public class ExternalApiService {
private String baseUrl; private String baseUrl;
@Value("${jcms.api.user-id}") @Value("${jcms.api.user-id}")
private String apiUserId; private String
apiUserId;
@Value("${jcms.api.user-pwd}") @Value("${jcms.api.user-pwd}")
private String apiUserPwd; private String apiUserPwd;
@@ -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;
}
}
@@ -13,8 +13,7 @@ import org.springframework.data.jpa.domain.Specification;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDateTime; import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@Slf4j @Slf4j
@@ -33,7 +32,8 @@ public class ChgrHistService extends BaseService<ChgrHist, Integer, ChgrHistRepo
@Scheduled(cron = "0 */10 * * * *") @Scheduled(cron = "0 */10 * * * *")
public void getChgrHist() throws IllegalAccessException { public void getChgrHist() throws IllegalAccessException {
ApiResponse<List<ChgrHistResponseDto>> response = apiClient.fetchChgrHist(LocalDateTime.now().toString(), "50"); String startDt = LocalDate.now().toString();
ApiResponse<List<ChgrHistResponseDto>> response = apiClient.fetchChgrHist(startDt, "50");
if (response == null || !Integer.valueOf(200).equals(response.getCode())) { if (response == null || !Integer.valueOf(200).equals(response.getCode())) {
String msg = (response != null) ? response.getMessage() : "응답 없음"; String msg = (response != null) ? response.getMessage() : "응답 없음";
@@ -42,21 +42,30 @@ public class ChgrHistService extends BaseService<ChgrHist, Integer, ChgrHistRepo
List<ChgrHistResponseDto> dtoList = response.getData(); List<ChgrHistResponseDto> dtoList = response.getData();
if (dtoList == null || dtoList.isEmpty()) { if (dtoList == null || dtoList.isEmpty()) {
log.info("가져온 충전 데이터가 없습니다."); log.info("가져온 충전이력 데이터가 없습니다.");
return;
} }
List<ChgrHist> entityList = dtoList.stream() List<ChgrHist> entityList = dtoList.stream()
.map(ChgrHistResponseDto::toEntity) .map(ChgrHistResponseDto::toEntity)
.toList(); .toList();
List<ChgrHist> updateList = new ArrayList<>(); int insertCount = 0;
int updateCount = 0;
for (ChgrHist h : entityList) { for (ChgrHist h : entityList) {
update(h); Integer id = BaseService.getId(h);
updateList.add(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 @Override
@@ -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>