암호화 모듈 설정 관리 화면 추가 (feature/crypto-module)

- CryptoModuleConfig CRUD 관리 화면 신규 개발
  - JPA DataService/Repository, MapStruct UiMapper, ManService, Controller
  - 목록(jqGrid, 4가지 검색조건), 상세/등록/수정/삭제 화면
  - INSERT/UPDATE/DELETE 후 게이트웨이 전 인스턴스에 ReloadCryptoModuleCommand broadcast
- 버그 수정
  - getById(getReferenceById) → findById 교체: Persistable.isNew=true 유지로 인한 persist 에러
  - insert 시 entity.setCryptoId(null): 빈 문자열 cryptoId 전송 시 UUID 생성기 오동작 방지
  - 검색 버튼 클릭 시 algType/keySourceType/useYn getSearchForJqgrid 누락 수정
- UX 개선
  - 암호화키/복호화키/IV 필드에 텍스트→HEX 변환 보조 입력 및 키 길이 실시간 표시
  - 상세 화면에 수정자/수정일시 정보성 표시
- DB: EMSADM.CRYPTO_MODULE_CONFIG 테이블 (MODIFIED_BY/MODIFIED_AT 포함 전체 DDL)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curry772
2026-05-07 10:51:39 +09:00
parent e9bff78cbf
commit 49fdc3b577
11 changed files with 1132 additions and 0 deletions
@@ -0,0 +1,13 @@
package com.eactive.eai.rms.data.entity.onl.security;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.eactive.eai.data.DataService;
import com.eactive.eai.data.entity.onl.security.CryptoModuleConfig;
public interface CryptoModuleConfigDataService extends DataService<CryptoModuleConfig, String> {
Page<CryptoModuleConfig> findAll(Pageable pageable, String searchName, String algType, String keySourceType, String useYn);
}
@@ -0,0 +1,38 @@
package com.eactive.eai.rms.data.entity.onl.security;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.eai.data.entity.onl.security.CryptoModuleConfig;
import com.eactive.eai.data.entity.onl.security.QCryptoModuleConfig;
import com.eactive.eai.data.jpa.AbstractDataService;
import com.querydsl.core.types.dsl.BooleanExpression;
@Service
@Transactional
public class CryptoModuleConfigDataServiceImpl
extends AbstractDataService<CryptoModuleConfig, String, CryptoModuleConfigRepository>
implements CryptoModuleConfigDataService {
@Override
public Page<CryptoModuleConfig> findAll(Pageable pageable, String searchName, String algType,
String keySourceType, String useYn) {
QCryptoModuleConfig q = QCryptoModuleConfig.cryptoModuleConfig;
BooleanExpression predicate = q.cryptoName.containsIgnoreCase(searchName != null ? searchName : "");
if (algType != null && !algType.isEmpty()) {
predicate = predicate.and(q.algType.eq(algType));
}
if (keySourceType != null && !keySourceType.isEmpty()) {
predicate = predicate.and(q.keySourceType.eq(keySourceType));
}
if (useYn != null && !useYn.isEmpty()) {
predicate = predicate.and(q.useYn.eq(useYn));
}
return repository.findAll(predicate, pageable);
}
}
@@ -0,0 +1,10 @@
package com.eactive.eai.rms.data.entity.onl.security;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import com.eactive.eai.data.entity.onl.security.CryptoModuleConfig;
import com.eactive.eai.data.jpa.BaseRepository;
interface CryptoModuleConfigRepository
extends BaseRepository<CryptoModuleConfig, String>, QuerydslPredicateExecutor<CryptoModuleConfig> {
}
@@ -0,0 +1,118 @@
package com.eactive.eai.rms.onl.manage.crypto;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.SortDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PostMapping;
import com.eactive.eai.agent.command.CommonCommand;
import com.eactive.eai.rms.common.base.OnlBaseAnnotationController;
import com.eactive.eai.rms.common.login.SessionManager;
import com.eactive.eai.rms.common.vo.GridResponse;
@Controller
public class CryptoModuleConfigManController extends OnlBaseAnnotationController {
private static final String RELOAD_CRYPTO_MODULE_COMMAND = "com.eactive.eai.agent.security.ReloadCryptoModuleCommand";
private final CryptoModuleConfigManService service;
@Autowired
public CryptoModuleConfigManController(CryptoModuleConfigManService service) {
this.service = service;
}
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Integer.class, new CustomNumberEditor(Integer.class, true));
}
@GetMapping(value = "/onl/admin/security/cryptoModuleMan.view")
public String viewList() {
return "/onl/admin/security/cryptoModuleMan";
}
@GetMapping(value = "/onl/admin/security/cryptoModuleMan.view", params = "cmd=DETAIL")
public String detailView() {
return "/onl/admin/security/cryptoModuleManDetail";
}
@PostMapping(value = "/onl/admin/security/cryptoModuleMan.json", params = "cmd=LIST")
public ResponseEntity<GridResponse<CryptoModuleConfigUI>> selectList(
@SortDefault("cryptoName") Pageable pageable,
String searchName, String algType, String keySourceType, String useYn) {
Page<CryptoModuleConfigUI> page = service.selectList(pageable, searchName, algType, keySourceType, useYn);
return ResponseEntity.ok(new GridResponse<>(page));
}
@PostMapping(value = "/onl/admin/security/cryptoModuleMan.json", params = "cmd=DETAIL")
public ResponseEntity<CryptoModuleConfigUI> selectDetail(String cryptoId) {
return ResponseEntity.ok(service.selectDetail(cryptoId));
}
@PostMapping(value = "/onl/admin/security/cryptoModuleMan.json", params = "cmd=INSERT")
public ResponseEntity<Map<String, String>> insert(@Valid CryptoModuleConfigUI ui, BindingResult bindingResult,
HttpServletRequest request) {
if (bindingResult.hasErrors()) {
Map<String, String> errors = new HashMap<>();
for (FieldError error : bindingResult.getFieldErrors()) {
errors.put(error.getField(), error.getDefaultMessage());
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
}
String cryptoId = service.insert(ui, SessionManager.getUserId(request));
CommonCommand.builder()
.name(RELOAD_CRYPTO_MODULE_COMMAND)
.args(cryptoId)
.build()
.broadcast(agentUtilService);
return ResponseEntity.ok().build();
}
@PostMapping(value = "/onl/admin/security/cryptoModuleMan.json", params = "cmd=UPDATE")
public ResponseEntity<Map<String, String>> update(@Valid CryptoModuleConfigUI ui, BindingResult bindingResult,
HttpServletRequest request) {
if (bindingResult.hasErrors()) {
Map<String, String> errors = new HashMap<>();
for (FieldError error : bindingResult.getFieldErrors()) {
errors.put(error.getField(), error.getDefaultMessage());
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
}
service.update(ui, SessionManager.getUserId(request));
CommonCommand.builder()
.name(RELOAD_CRYPTO_MODULE_COMMAND)
.args(ui.getCryptoId())
.build()
.broadcast(agentUtilService);
return ResponseEntity.ok().build();
}
@PostMapping(value = "/onl/admin/security/cryptoModuleMan.json", params = "cmd=DELETE")
public ResponseEntity<Void> delete(String cryptoId) {
service.delete(cryptoId);
CommonCommand.builder()
.name(RELOAD_CRYPTO_MODULE_COMMAND)
.args(cryptoId)
.build()
.broadcast(agentUtilService);
return ResponseEntity.ok().build();
}
}
@@ -0,0 +1,64 @@
package com.eactive.eai.rms.onl.manage.crypto;
import java.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.eai.data.entity.onl.security.CryptoModuleConfig;
import com.eactive.eai.rms.common.base.BaseService;
import com.eactive.eai.rms.data.entity.onl.security.CryptoModuleConfigDataService;
import com.eactive.eai.rms.onl.common.exception.BizException;
@Service
@Transactional
public class CryptoModuleConfigManService extends BaseService {
private final CryptoModuleConfigDataService dataService;
private final CryptoModuleConfigUiMapper mapper;
@Autowired
public CryptoModuleConfigManService(CryptoModuleConfigDataService dataService,
CryptoModuleConfigUiMapper mapper) {
this.dataService = dataService;
this.mapper = mapper;
}
public Page<CryptoModuleConfigUI> selectList(Pageable pageable, String searchName,
String algType, String keySourceType, String useYn) {
Page<CryptoModuleConfig> page = dataService.findAll(pageable, searchName, algType, keySourceType, useYn);
return page.map(mapper::toVo);
}
public CryptoModuleConfigUI selectDetail(String cryptoId) {
CryptoModuleConfig entity = dataService.findById(cryptoId)
.orElseThrow(() -> new BizException("암호화 모듈을 찾을 수 없습니다: " + cryptoId));
return mapper.toVo(entity);
}
public String insert(CryptoModuleConfigUI ui, String loginId) {
CryptoModuleConfig entity = mapper.toEntity(ui);
entity.setCryptoId(null);
entity.setModifiedBy(loginId);
entity.setModifiedAt(LocalDateTime.now());
dataService.save(entity);
return entity.getCryptoId();
}
public void update(CryptoModuleConfigUI ui, String loginId) {
CryptoModuleConfig entity = dataService.findById(ui.getCryptoId())
.orElseThrow(() -> new BizException("암호화 모듈을 찾을 수 없습니다: " + ui.getCryptoId()));
mapper.updateToEntity(ui, entity);
entity.setModifiedBy(loginId);
entity.setModifiedAt(LocalDateTime.now());
dataService.save(entity);
}
public void delete(String cryptoId) {
dataService.deleteById(cryptoId);
}
}
@@ -0,0 +1,71 @@
package com.eactive.eai.rms.onl.manage.crypto;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class CryptoModuleConfigUI {
@JsonProperty("CRYPTO_ID")
private String cryptoId;
@JsonProperty("CRYPTO_NAME")
@NotNull
@Size(max = 100)
private String cryptoName;
@JsonProperty("CRYPTO_DESC")
private String cryptoDesc;
@JsonProperty("ALG_TYPE")
@NotNull
private String algType;
@JsonProperty("CIPHER_MODE")
@NotNull
private String cipherMode;
@JsonProperty("PADDING")
private String padding;
@JsonProperty("IV_HEX")
private String ivHex;
@JsonProperty("KEY_SOURCE_TYPE")
@NotNull
private String keySourceType;
@JsonProperty("ENC_KEY_HEX")
private String encKeyHex;
@JsonProperty("DEC_KEY_HEX")
private String decKeyHex;
@JsonProperty("KEY_DERIV_STRATEGY")
private String keyDerivStrategy;
@JsonProperty("KEY_DERIV_PARAMS")
private String keyDerivParams;
@JsonProperty("CACHE_YN")
@NotNull
private String cacheYn;
@JsonProperty("CACHE_TTL_SEC")
private Integer cacheTtlSec;
@JsonProperty("USE_YN")
@NotNull
private String useYn;
@JsonProperty("MODIFIED_BY")
private String modifiedBy;
@JsonProperty("MODIFIED_AT")
private String modifiedAt;
}
@@ -0,0 +1,18 @@
package com.eactive.eai.rms.onl.manage.crypto;
import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.NullValuePropertyMappingStrategy;
import org.mapstruct.ReportingPolicy;
import com.eactive.eai.data.entity.onl.security.CryptoModuleConfig;
import com.eactive.eai.data.mapper.GenericMapper;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface CryptoModuleConfigUiMapper extends GenericMapper<CryptoModuleConfigUI, CryptoModuleConfig> {
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void updateToEntity(CryptoModuleConfigUI ui, @MappingTarget CryptoModuleConfig entity);
}