From 76392631f2e238245f9a8152ef3ad74e1d181ac0 Mon Sep 17 00:00:00 2001 From: gudae01 Date: Mon, 21 Jul 2025 10:29:19 +0900 Subject: [PATCH] =?UTF-8?q?load=20local=20file=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/SettingService.java | 145 ++++++++-- src/main/resources/static/index.html | 260 +++++++++++++----- 2 files changed, 317 insertions(+), 88 deletions(-) diff --git a/src/main/java/org/mobidgim/mobidigimproject/service/SettingService.java b/src/main/java/org/mobidgim/mobidigimproject/service/SettingService.java index 796f090..2425aed 100644 --- a/src/main/java/org/mobidgim/mobidigimproject/service/SettingService.java +++ b/src/main/java/org/mobidgim/mobidigimproject/service/SettingService.java @@ -1,7 +1,6 @@ package org.mobidgim.mobidigimproject.service; -import com.fasterxml.jackson.annotation.JsonInclude; - +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; @@ -9,51 +8,100 @@ import org.mobidgim.mobidigimproject.model.device.DeviceDTO; import org.mobidgim.mobidigimproject.model.register.RegisterDTO; import org.mobidgim.mobidigimproject.model.register.enums.CanInput; import org.mobidgim.mobidigimproject.model.register.enums.Protocol; +import org.mobidgim.mobidigimproject.model.register.sebDTO.CanField; +import org.mobidgim.mobidigimproject.model.register.sebDTO.ModbusField; +import org.mobidgim.mobidigimproject.model.register.sebDTO.OpcUaField; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import javax.swing.text.html.parser.Parser; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Map; @Service public class SettingService { private final ObjectMapper mapper; + private static final String DEFAULT_DEVICE_FILENAME = "deviceConfig.json"; + private static final String DEFAULT_PROTOCOL_FILENAME = "protocolConfig.json"; + private static final String TEMP_DIRECTORY = "./temp"; @Autowired public SettingService(ObjectMapper mapper) { this.mapper = mapper; this.mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); + } + // 디렉토리 생성 메서드 + private void ensureDirectoryExists() throws IOException { + Path dirPath = Paths.get(TEMP_DIRECTORY); + if (!Files.exists(dirPath)) { + Files.createDirectory(dirPath); + } } + // Device 파일 저장 (기본 파일명 사용) public void saveFileWithDevice(DeviceDTO request) throws IOException { + saveFileWithDevice(request, DEFAULT_DEVICE_FILENAME); + } + + // Device 파일 저장 (사용자 지정 파일명) + public void saveFileWithDevice(DeviceDTO request, String filename) throws IOException { mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); - mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(request); - String directoryPath = "./temp"; - Path dirPath = Paths.get(directoryPath); + ensureDirectoryExists(); - if(!Files.exists(dirPath)){ - Files.createDirectory(dirPath); - } - - String filename = "./temp/deviceConfig.json"; - Files.writeString(Paths.get(filename), json); + String filePath = TEMP_DIRECTORY + "/" + filename; + Files.writeString(Paths.get(filePath), json); } + // Device 파일 읽기 (기본 파일명 사용) public DeviceDTO readFileWithDevice() throws IOException { + return readFileWithDevice(DEFAULT_DEVICE_FILENAME); + } + + // Device 파일 읽기 (사용자 지정 파일명) + public DeviceDTO readFileWithDevice(String filename) throws IOException { + String filePath = TEMP_DIRECTORY + "/" + filename; + Path path = Paths.get(filePath); + + if (!Files.exists(path)) { + throw new IOException("Device configuration file not found: " + filename); + } + + String jsonString = Files.readString(path); ObjectMapper mapper = new ObjectMapper(); mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); - return mapper.readValue(new File("./temp/deviceConfig.json"), DeviceDTO.class); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return mapper.readValue(jsonString, DeviceDTO.class); + } + + // Device 파일 존재 여부 확인 + public boolean deviceFileExists() { + return deviceFileExists(DEFAULT_DEVICE_FILENAME); + } + + public boolean deviceFileExists(String filename) { + String filePath = TEMP_DIRECTORY + "/" + filename; + return Files.exists(Paths.get(filePath)); } + // Protocol 파일 저장 (기본 파일명 사용) public RegisterDTO saveFileWithRegister(RegisterDTO request) throws IOException { + return saveFileWithRegister(request, DEFAULT_PROTOCOL_FILENAME); + } + // Protocol 파일 저장 (사용자 지정 파일명) + public RegisterDTO saveFileWithRegister(RegisterDTO request, String filename) throws IOException { + // 프로토콜에 따른 데이터 정리 if (request.getProtocol() != Protocol.OPC_UA) { request.setOPC_UA(null); } @@ -66,26 +114,31 @@ public class SettingService { request.setCAN(null); } - String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(request); - String directoryPath = "./temp"; - Path dirPath = Paths.get(directoryPath); - - if(!Files.exists(dirPath)){ - Files.createDirectory(dirPath); - } + ensureDirectoryExists(); - String filename = "./temp/protocolConfig.json"; - Files.writeString(Paths.get(filename), json); + String filePath = TEMP_DIRECTORY + "/" + filename; + Files.writeString(Paths.get(filePath), json); return request; } + // Protocol 파일 읽기 (기본 파일명 사용) public RegisterDTO readFileWithRegister() throws IOException { - String filename = "./temp/protocolConfig.json"; - Path filePath = Paths.get(filename); - String jsonString = Files.readString(filePath); + return readFileWithRegister(DEFAULT_PROTOCOL_FILENAME); + } + + // Protocol 파일 읽기 (사용자 지정 파일명) + public RegisterDTO readFileWithRegister(String filename) throws IOException { + String filePath = TEMP_DIRECTORY + "/" + filename; + Path path = Paths.get(filePath); + + if (!Files.exists(path)) { + throw new IOException("Protocol configuration file not found: " + filename); + } + + String jsonString = Files.readString(path); ObjectMapper mapper = new ObjectMapper(); mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @@ -93,4 +146,46 @@ public class SettingService { return registerDTO; } -} + // Protocol 파일 존재 여부 확인 + public boolean protocolFileExists() { + return protocolFileExists(DEFAULT_PROTOCOL_FILENAME); + } + + public boolean protocolFileExists(String filename) { + String filePath = TEMP_DIRECTORY + "/" + filename; + return Files.exists(Paths.get(filePath)); + } + + // temp 디렉토리의 모든 JSON 파일 목록 조회 + public List getAvailableDeviceFiles() throws IOException { + return getAvailableFiles("device"); + } + + public List getAvailableProtocolFiles() throws IOException { + return getAvailableFiles("protocol"); + } + + private List getAvailableFiles(String type) throws IOException { + ensureDirectoryExists(); + Path dirPath = Paths.get(TEMP_DIRECTORY); + + return Files.list(dirPath) + .filter(path -> path.toString().endsWith(".json")) + .map(path -> path.getFileName().toString()) + .filter(filename -> { + try { + if ("device".equals(type)) { + readFileWithDevice(filename); + return true; + } else if ("protocol".equals(type)) { + readFileWithRegister(filename); + return true; + } + return false; + } catch (Exception e) { + return false; // 파일이 해당 타입에 맞지 않으면 제외 + } + }) + .toList(); + } +} \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 852aa48..104c510 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -55,6 +55,7 @@
+
{ @@ -885,13 +879,6 @@ return; } - // 파일명 검증 (data-expected-filename 사용) - if (fileName !== expectedFileName){ - alert(`Invalid file. '${expectedFileName}' Please select a file. Current file: ${fileName} `); - uploadFileInput.value = ''; // 잘못된 파일 선택 시 input 초기화 - return; - } - reader.onload = async (e) => { try { const fileContent = e.target.result; @@ -1041,18 +1028,34 @@ const expr = document.getElementById('modbus_expr_input').value; const mask = document.getElementById('modbus_mask_input').value; - const newRow = modbusRowTemplate.content.cloneNode(true); - newRow.querySelector('.field-display').textContent = field; - newRow.querySelector('.addr-display').textContent = addr; - newRow.querySelector('.idt-display').textContent = idt; - newRow.querySelector('.odt-display').textContent = odt; - newRow.querySelector('.dv-display').textContent = defaultValue; - newRow.querySelector('.shift-display').textContent = shift; - newRow.querySelector('.expr-display').textContent = expr; - newRow.querySelector('.mask-display').textContent = mask; + const firstRow = modbusFieldList.querySelector('tr'); + + // 첫 행이 비어있다면 그 자리에 값을 넣기 + if (firstRow && firstRow.querySelector('.field-display').textContent.trim() === '') { + firstRow.querySelector('.field-display').textContent = field; + firstRow.querySelector('.addr-display').textContent = addr; + firstRow.querySelector('.idt-display').textContent = idt; + firstRow.querySelector('.odt-display').textContent = odt; + firstRow.querySelector('.dv-display').textContent = defaultValue; + firstRow.querySelector('.shift-display').textContent = shift; + firstRow.querySelector('.expr-display').textContent = expr; + firstRow.querySelector('.mask-display').textContent = mask; + } else { + // 새로운 행을 추가 + const newRow = modbusRowTemplate.content.cloneNode(true); + newRow.querySelector('.field-display').textContent = field; + newRow.querySelector('.addr-display').textContent = addr; + newRow.querySelector('.idt-display').textContent = idt; + newRow.querySelector('.odt-display').textContent = odt; + newRow.querySelector('.dv-display').textContent = defaultValue; + newRow.querySelector('.shift-display').textContent = shift; + newRow.querySelector('.expr-display').textContent = expr; + newRow.querySelector('.mask-display').textContent = mask; - modbusFieldList.appendChild(newRow); + modbusFieldList.appendChild(newRow); + } + // 입력 필드 초기화 document.getElementById('modbus_field_input').value = ''; document.getElementById('modbus_addr_input').value = ''; document.getElementById('modbus_idt_input').value = ''; @@ -1095,29 +1098,46 @@ const expr = document.getElementById('opcua_expr_input').value; const mask = document.getElementById('opcua_mask_input').value; - const newRow = opcUaRowTemplate.content.cloneNode(true); - newRow.querySelector('.opcua-field-display').textContent = field; - newRow.querySelector('.opcua-addr-display').textContent = addr; - newRow.querySelector('.opcua-ns-display').textContent = ns; - newRow.querySelector('.opcua-odt-display').textContent = odt; - newRow.querySelector('.opcua-dv-display').textContent = defaultValue; - newRow.querySelector('.opcua-shift-display').textContent = shift; - newRow.querySelector('.opcua-expr-display').textContent = expr; - newRow.querySelector('.opcua-mask-display').textContent = mask; - - opcUaFieldList.appendChild(newRow); - - document.getElementById('opcua_field_input').value = ''; - document.getElementById('opcua_addr_input').value = ''; - document.getElementById('opcua_ns_input').value = ''; - document.getElementById('opcua_odt_input').value = ''; - document.getElementById('opcua_dv_input').value = ''; - document.getElementById('opcua_shift_input').value = ''; - document.getElementById('opcua_expr_input').value = ''; - document.getElementById('opcua_mask_input').value = ''; + const firstRow = opcUaFieldList.querySelector('tr'); + + // 첫 행이 비어있다면 그 자리에 값을 넣기 + if (firstRow && firstRow.querySelector('.opcua-field-display').textContent.trim() === '') { + firstRow.querySelector('.opcua-field-display').textContent = field; + firstRow.querySelector('.opcua-addr-display').textContent = addr; + firstRow.querySelector('.opcua-ns-display').textContent = ns; + firstRow.querySelector('.opcua-odt-display').textContent = odt; + firstRow.querySelector('.opcua-dv-display').textContent = defaultValue; + firstRow.querySelector('.opcua-shift-display').textContent = shift; + firstRow.querySelector('.opcua-expr-display').textContent = expr; + firstRow.querySelector('.opcua-mask-display').textContent = mask; + } else { + // 새로운 행을 추가 + const newRow = opcUaRowTemplate.content.cloneNode(true); + newRow.querySelector('.opcua-field-display').textContent = field; + newRow.querySelector('.opcua-addr-display').textContent = addr; + newRow.querySelector('.opcua-ns-display').textContent = ns; + newRow.querySelector('.opcua-odt-display').textContent = odt; + newRow.querySelector('.opcua-dv-display').textContent = defaultValue; + newRow.querySelector('.opcua-shift-display').textContent = shift; + newRow.querySelector('.opcua-expr-display').textContent = expr; + newRow.querySelector('.opcua-mask-display').textContent = mask; + + opcUaFieldList.appendChild(newRow); + } + + // 입력 필드 초기화 + document.getElementById('modbus_field_input').value = ''; + document.getElementById('modbus_addr_input').value = ''; + document.getElementById('modbus_idt_input').value = ''; + document.getElementById('modbus_odt_input').value = ''; + document.getElementById('modbus_dv_input').value = ''; + document.getElementById('modbus_shift_input').value = ''; + document.getElementById('modbus_expr_input').value = ''; + document.getElementById('modbus_mask_input').value = ''; }); + document.getElementById('opcua_modify_btn').onclick = function () { if (!selectedRow) return alert('Please select the row you want to modify'); selectedRow.querySelector('.opcua-field-display').textContent = document.getElementById('opcua_field_input').value; @@ -1437,7 +1457,66 @@ URL.revokeObjectURL(url); } /*-----------------------------------------------------*/ + // localStorage 자동 저장 (Device) + function autoSaveToLocal() { + const data = collectDeviceData(); + localStorage.setItem('deviceConfigAutoSave', JSON.stringify(data)); + } + // localStorage 자동 저장 (Protocol) + function autoSaveProtocolToLocal() { + const config = collectProtocolData(); + localStorage.setItem('protocolConfigAutoSave', JSON.stringify(config)); + } + + // localStorage에서 불러오기 + function loadFromLocalIfExists() { + const saved = localStorage.getItem('deviceConfigAutoSave'); + if (saved) { + try { + loadFromData(JSON.parse(saved)); + console.log('localStorage에서 자동 저장된 Device 설정을 불러왔습니다.'); + return true; + } catch (e) { + console.warn('localStorage Device 설정 파싱 실패:', e); + } + } + return false; + } + + // localStorage에서 불러오기 (Protocol) + function loadProtocolFromLocalIfExists() { + const saved = localStorage.getItem('protocolConfigAutoSave'); + if (saved) { + try { + loadFromRegisterJson(JSON.parse(saved)); + console.log('localStorage에서 자동 저장된 Protocol 설정을 불러왔습니다.'); + return true; + } catch (e) { + console.warn('localStorage Protocol 설정 파싱 실패:', e); + } + } + return false; + } + + // input, select 등 값이 바뀔 때마다 자동 저장 (Device) + function bindAutoSaveEvents() { + document.querySelectorAll('input, select').forEach(el => { + el.addEventListener('change', autoSaveToLocal); + }); + } + + // input, select 등 값이 바뀔 때마다 자동 저장 (Protocol) + function bindAutoSaveProtocolEvents() { + // reg, modbus, opcua, can 페이지 내의 input/select만 + const protocolSections = ['#page-reg', '#page-modbus', '#page-opcua', '#page-can']; + protocolSections.forEach(section => { + document.querySelectorAll(section + ' input, ' + section + ' select').forEach(el => { + el.addEventListener('change', autoSaveProtocolToLocal); + }); + }); + } + /*-----protocol에 설정 데이터를 protocolConfig.json 파일로 변경하여 유저가 다운로드 할수 있게 하는 함수-----*/ function downloadProtocolJson(obj, filename='protocolConfig.json') { const json = JSON.stringify(obj, null, 2); // 보기 좋게 들여쓰기 2칸 @@ -1456,30 +1535,85 @@ /*-------페이지 로드시 서버에 있는 데이터를 불러오는 함수---------*/ async function loadInitialData() { - try { - // Device 설정 로드 - const deviceResponse = await fetch('http://localhost:8080/setting/get-device'); - if (deviceResponse.ok) { - const deviceData = await deviceResponse.json(); - if (deviceData && deviceData.body) { - loadFromData(deviceData.body); + // 1. localStorage 우선 적용 (Device) + let deviceLoadedFromLocal = loadFromLocalIfExists(); + // 2. 서버에서 파일 목록 조회 및 로드 (Device) + if (!deviceLoadedFromLocal) { + try { + // 사용 가능한 Device 파일 목록 조회 + const deviceFilesResponse = await fetch('http://localhost:8080/setting/device-files'); + if (deviceFilesResponse.ok) { + const deviceFilesData = await deviceFilesResponse.json(); + if (deviceFilesData && deviceFilesData.body && deviceFilesData.body.length > 0) { + // 첫 번째 유효한 Device 파일 로드 + const firstDeviceFile = deviceFilesData.body[0]; + console.log(`Device 파일 발견: ${firstDeviceFile}`); + + const deviceResponse = await fetch(`http://localhost:8080/setting/get-device/${firstDeviceFile}`); + if (deviceResponse.ok) { + const deviceData = await deviceResponse.json(); + if (deviceData && deviceData.body) { + loadFromData(deviceData.body); + console.log(`Device 설정 자동 로드 완료: ${firstDeviceFile}`); + } + } + } else { + // 기본 파일명으로 시도 + const deviceResponse = await fetch('http://localhost:8080/setting/get-device'); + if (deviceResponse.ok) { + const deviceData = await deviceResponse.json(); + if (deviceData && deviceData.body) { + loadFromData(deviceData.body); + console.log('Device 설정 자동 로드 완료 (기본 파일)'); + } + } + } } + } catch (error) { + console.log('Device 설정 파일이 없거나 로드 실패, 기본값 사용:', error.message); } - } catch (error) { - alert('Device configuration file missing or loading failed'); } - - try { - // Protocol 설정 로드 - const protocolResponse = await fetch('http://localhost:8080/setting/get-protocol'); - if (protocolResponse.ok) { - const protocolData = await protocolResponse.json(); - if (protocolData && protocolData.body) { - loadFromRegisterJson(protocolData.body); + bindAutoSaveEvents(); + + // 1. localStorage 우선 적용 (Protocol) + let protocolLoadedFromLocal = loadProtocolFromLocalIfExists(); + // 2. 서버에서 파일 목록 조회 및 로드 (Protocol) + if (!protocolLoadedFromLocal) { + try { + // 사용 가능한 Protocol 파일 목록 조회 + const protocolFilesResponse = await fetch('http://localhost:8080/setting/protocol-files'); + if (protocolFilesResponse.ok) { + const protocolFilesData = await protocolFilesResponse.json(); + if (protocolFilesData && protocolFilesData.body && protocolFilesData.body.length > 0) { + // 첫 번째 유효한 Protocol 파일 로드 + const firstProtocolFile = protocolFilesData.body[0]; + console.log(`Protocol 파일 발견: ${firstProtocolFile}`); + + const protocolResponse = await fetch(`http://localhost:8080/setting/get-protocol/${firstProtocolFile}`); + if (protocolResponse.ok) { + const protocolData = await protocolResponse.json(); + if (protocolData && protocolData.body) { + loadFromRegisterJson(protocolData.body); + console.log(`Protocol 설정 자동 로드 완료: ${firstProtocolFile}`); + } + } + } else { + // 기본 파일명으로 시도 + const protocolResponse = await fetch('http://localhost:8080/setting/get-protocol'); + if (protocolResponse.ok) { + const protocolData = await protocolResponse.json(); + if (protocolData && protocolData.body) { + loadFromRegisterJson(protocolData.body); + console.log('Protocol 설정 자동 로드 완료 (기본 파일)'); + } + } + } } + } catch (error) { + console.log('Protocol 설정 파일이 없거나 로드 실패, 기본값 사용:', error.message); } - } catch (error) { - alert('Protocol configuration file missing or loading failed'); } + } + bindAutoSaveProtocolEvents(); } /*-----------------------------------------------------*/