API 그룹 팝업 JSP 파일 추가 - 예제 코드 및 UI 구조 작성
Summernote 에디터 스타일 CSS 추가 - 텍스트 및 컴포넌트 스타일 정의 OpenAPI POC JS 파일 추가 - API 스펙 빌더 및 스텝별 렌더 구현
This commit is contained in:
@@ -2,6 +2,7 @@ package com.eactive.eai.rms.data.entity.onl.apim.apigroup;
|
||||
|
||||
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroup;
|
||||
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroupApi;
|
||||
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroupApiId;
|
||||
import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroup;
|
||||
import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroupApi;
|
||||
import com.eactive.eai.data.jpa.AbstractDataService;
|
||||
@@ -75,4 +76,37 @@ public class ApiGroupService extends AbstractDataService<ApiGroup, String, ApiGr
|
||||
.where(qApiGroup.id.in(apiGroupIds))
|
||||
.fetch();
|
||||
}
|
||||
|
||||
/** API 를 지정한 그룹 목록에만 소속시킨다(기존 소속 제거 후 재설정). */
|
||||
public void setApiGroupsForApi(String apiId, List<String> groupIds) {
|
||||
deleteApiGroupApiByApiId(apiId);
|
||||
if (groupIds == null) {
|
||||
return;
|
||||
}
|
||||
for (String gid : groupIds) {
|
||||
if (StringUtils.isBlank(gid)) {
|
||||
continue;
|
||||
}
|
||||
ApiGroup group = repository.findById(gid).orElse(null);
|
||||
if (group == null) {
|
||||
continue;
|
||||
}
|
||||
Hibernate.initialize(group.getApiGroupApiList());
|
||||
List<ApiGroupApi> list = group.getApiGroupApiList();
|
||||
boolean exists = false;
|
||||
for (ApiGroupApi a : list) {
|
||||
if (a.getId() != null && apiId.equals(a.getId().getApiId())) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
ApiGroupApi a = new ApiGroupApi();
|
||||
a.setId(new ApiGroupApiId(gid, apiId));
|
||||
a.setDisplayOrder(list.size() + 1);
|
||||
list.add(a);
|
||||
repository.save(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public interface PortalPropertyMapper extends GenericMapper<PropertyUI, PortalPr
|
||||
@Mapping(source = "prptyGroupName", target = "id.propertyGroupName")
|
||||
@Mapping(source = "prptyName", target = "id.propertyName")
|
||||
@Mapping(source = "prpty2Val", target = "propertyValue")
|
||||
@Mapping(source = "prptyDesc", target = "propertyDesc")
|
||||
@Override
|
||||
PortalProperty toEntity(PropertyUI vo);
|
||||
|
||||
@@ -21,6 +22,7 @@ public interface PortalPropertyMapper extends GenericMapper<PropertyUI, PortalPr
|
||||
@Mapping(source ="id.propertyGroupName", target = "prptyGroupName")
|
||||
@Mapping(source ="id.propertyName", target = "prptyName")
|
||||
@Mapping(source ="propertyValue", target = "prpty2Val")
|
||||
@Mapping(source ="propertyDesc", target = "prptyDesc")
|
||||
@InheritInverseConfiguration
|
||||
@Override
|
||||
PropertyUI toVo(PortalProperty entity);
|
||||
|
||||
@@ -15,4 +15,7 @@ public class PropertyUI {
|
||||
|
||||
@JsonProperty("PRPTY2VAL")
|
||||
private String prpty2Val;
|
||||
|
||||
@JsonProperty("PRPTYDESC")
|
||||
private String prptyDesc;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ApiSpecController extends OnlBaseAnnotationController {
|
||||
return "/onl/transaction/apim/apiSpecManPopup";
|
||||
}
|
||||
|
||||
@GetMapping(value = "/onl/transaction/apim/apiSpecManPopup.view", params = "cmd=POPUP_LIST")
|
||||
@GetMapping(value = "/onl/transaction/apim/apiSpecMan.view", params = "cmd=POPUP_LIST")
|
||||
public String detailOrgPopupView() {
|
||||
return "/onl/transaction/apim/apiSpecManDisplayOrgPopup";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,541 @@
|
||||
package com.eactive.eai.rms.onl.transaction.apim;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.node.TextNode;
|
||||
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
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.ResponseBody;
|
||||
|
||||
import com.eactive.eai.data.entity.onl.message.EAIMessageEntity;
|
||||
import com.eactive.eai.rms.common.base.OnlBaseAnnotationController;
|
||||
import com.eactive.eai.rms.data.entity.onl.eaimsg.EAIMessageService;
|
||||
import com.eactive.eai.rms.onl.manage.rule.layout.ui.LayoutUI;
|
||||
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiInterfaceUI;
|
||||
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiSpecInfoUI;
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import com.eactive.eai.rms.data.entity.onl.apim.apigroup.ApiGroupService;
|
||||
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroup;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
/**
|
||||
* 신규 OpenAPI Spec 6단계 편집 마법사 컨트롤러.
|
||||
*
|
||||
* apiInterfaceMan 상세의 "OpenAPI Spec" 버튼이 새 탭(_blank)으로 여는 페이지 및 마법사가 호출하는
|
||||
* cmd API 를 제공한다. 저장은 기존 컬럼 + testbedSpec(OpenAPI JSON)에 매핑(스키마 무변경, 경량).
|
||||
*
|
||||
* (MVC 컨트롤러는 springapp-servlet.xml 의 com.eactive.eai.rms 스캔 범위에서만 등록되므로 본 컨트롤러는
|
||||
* 해당 패키지에 둔다. 기존 apiSpecMan.view(v1)와 URL 을 분리해 공존한다.)
|
||||
*/
|
||||
@Controller
|
||||
public class DjbApiSpecController extends OnlBaseAnnotationController {
|
||||
|
||||
@Autowired
|
||||
private ApiSpecManService apiSpecManService;
|
||||
|
||||
@Autowired
|
||||
private ApiInterfaceService apiInterfaceService;
|
||||
|
||||
@Autowired
|
||||
private EAIMessageService eaiMessageService;
|
||||
|
||||
@Autowired
|
||||
private DjbApiSpecSwaggerEnricher swaggerEnricher;
|
||||
|
||||
@Autowired
|
||||
private DjbApiSpecLayoutMergeService layoutMergeService;
|
||||
|
||||
@Autowired
|
||||
private PortalPropertyService portalPropertyService;
|
||||
|
||||
@Autowired
|
||||
private ApiGroupService apiGroupService;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
// GW base URL 단일 기준 = 포탈 DjbTestbedGatewayProperty 의 djb.gateway.base-url (PTL_PROPERTY 그룹 'Portal').
|
||||
// 별도 swagger.gw.address 프로퍼티는 두지 않는다.
|
||||
private static final String GW_BASE_URL_KEY = "djb.gateway.base-url";
|
||||
private static final String GW_BASE_URL_DEFAULT = "PortalMock";
|
||||
// "PortalMock" 은 포탈이 요청 origin 으로 치환하는 sentinel 이라 admin 미리보기용 서버 주소로는 부적합 → 대체값 사용.
|
||||
private static final String GW_PREVIEW_FALLBACK = "http://127.0.0.1:39310";
|
||||
|
||||
// ── 진입 ────────────────────────────────────────────────
|
||||
@GetMapping(value = "/onl/transaction/apim/djbApiSpecMan.view", params = "cmd=DETAIL")
|
||||
public String viewDetail(Model model) {
|
||||
// GW base URL (djb.gateway.base-url, 그룹 'Portal') — 없으면 기본값으로 생성. DjbTestbedGatewayProperty 와 동일 키.
|
||||
String gw = portalPropertyService.getOrCreateProperty(
|
||||
"Portal", GW_BASE_URL_KEY, GW_BASE_URL_DEFAULT, "GW base URL (테스트베드 공통)");
|
||||
if (StringUtils.isBlank(gw) || GW_BASE_URL_DEFAULT.equalsIgnoreCase(gw.trim())) {
|
||||
gw = GW_PREVIEW_FALLBACK; // PortalMock → admin 미리보기용 구체 주소로 대체
|
||||
}
|
||||
model.addAttribute("gwAddress", gw.trim());
|
||||
return "/onl/transaction/apim/djbApiSpecManPopup";
|
||||
}
|
||||
|
||||
// API 그룹 선택 팝업(iframe 모달 대상). 그룹 목록 그리드는 apiGroupMan.json?cmd=LIST 재사용.
|
||||
@GetMapping(value = "/onl/transaction/apim/djbApiSpecMan.view", params = "cmd=GROUP_POPUP")
|
||||
public String viewGroupPopup() {
|
||||
return "/onl/transaction/apim/djbApiGroupPopup";
|
||||
}
|
||||
|
||||
// ── 조회: 저장 spec 우선 + authtype securityScheme 주입 ──
|
||||
@PostMapping(value = "/onl/transaction/apim/djbApiSpecMan.json", params = "cmd=DETAIL_SPEC")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, String>> detailSpec(String eaiSvcName, String regen) throws Exception {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
if (StringUtils.isBlank(eaiSvcName)) {
|
||||
result.put("status", "fail");
|
||||
result.put("errorMsg", "eaiSvcName 이 없습니다.");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
// regen=true 이면 저장본 무시하고 레이아웃 기반으로 강제 재생성 (자동 생성/초기화)
|
||||
boolean force = "true".equalsIgnoreCase(regen);
|
||||
String spec;
|
||||
String source;
|
||||
ApiSpecInfoUI saved = force ? null : apiSpecManService.selectDetail(eaiSvcName);
|
||||
if (!force && saved != null && StringUtils.isNotBlank(saved.getTestbedSpec())) {
|
||||
spec = saved.getTestbedSpec();
|
||||
source = "saved";
|
||||
} else {
|
||||
spec = generateSpec(eaiSvcName);
|
||||
source = force ? "regenerated" : "generated";
|
||||
}
|
||||
|
||||
String authtype = eaiMessageService.findById(eaiSvcName)
|
||||
.map(EAIMessageEntity::getAuthtype)
|
||||
.orElse(null);
|
||||
String enriched = swaggerEnricher.enrich(spec, authtype);
|
||||
|
||||
result.put("testbedSpec", enriched);
|
||||
result.put("source", source);
|
||||
result.put("authType", authtype == null ? "" : authtype);
|
||||
// API 그룹 소속(조인테이블 ApiGroupApi) — 저장 여부와 무관하게 현재 소속 반환
|
||||
try {
|
||||
ArrayNode groups = objectMapper.createArrayNode();
|
||||
for (ApiGroup g : apiGroupService.findByApiId(eaiSvcName)) {
|
||||
ObjectNode gn = objectMapper.createObjectNode();
|
||||
gn.put("id", g.getId());
|
||||
gn.put("groupName", g.getGroupName());
|
||||
groups.add(gn);
|
||||
}
|
||||
result.put("apiGroups", objectMapper.writeValueAsString(groups));
|
||||
} catch (Exception e) {
|
||||
logger.warn("API 그룹 조회 실패: " + eaiSvcName, e);
|
||||
}
|
||||
// 저장본이면 폼 복원용 기본정보/공개설정도 함께 반환
|
||||
if (saved != null) {
|
||||
putIfNotNull(result, "apiName", saved.getApiName());
|
||||
putIfNotNull(result, "apiSimpleDescription", saved.getApiSimpleDescription());
|
||||
putIfNotNull(result, "apiUrl", saved.getApiUrl());
|
||||
putIfNotNull(result, "apiMethod", saved.getApiMethod());
|
||||
putIfNotNull(result, "apiContentType", saved.getApiContentType());
|
||||
putIfNotNull(result, "description", saved.getDescription());
|
||||
putIfNotNull(result, "apiRequestSpec", saved.getApiRequestSpec());
|
||||
putIfNotNull(result, "apiResponseSpec", saved.getApiResponseSpec());
|
||||
putIfNotNull(result, "sampleRequest", saved.getSampleRequest());
|
||||
putIfNotNull(result, "sampleResponse", saved.getSampleResponse());
|
||||
putIfNotNull(result, "responseType", saved.getResponseType());
|
||||
putIfNotNull(result, "mockUrl", saved.getMockUrl());
|
||||
putIfNotNull(result, "displayYn", saved.getDisplayYn());
|
||||
putIfNotNull(result, "displayRoleCode", saved.getDisplayRoleCode());
|
||||
putIfNotNull(result, "displayOrg", saved.getDisplayOrg());
|
||||
}
|
||||
// 예제/설명자료(요청·응답 메시지 스펙 HTML)는 프론트에서 스키마 기준으로 생성 → 자동생성/재생성 결과 일치.
|
||||
// 저장본이면 위 saved 블록에서 저장값을 이미 반환.
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
// ── 저장: 마법사 폼 → ApiSpecInfoUI → apiSpecManService.save (기존 재사용) ──
|
||||
@PostMapping(value = "/onl/transaction/apim/djbApiSpecMan.json", params = "cmd=SAVE")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, String>> save(ApiSpecInfoUI apiSpecInfoUI, String apiGroupIds) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
if (apiSpecInfoUI == null || StringUtils.isBlank(apiSpecInfoUI.getApiId())) {
|
||||
result.put("status", "fail");
|
||||
result.put("errorMsg", "API ID 가 없습니다.");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
// CrossScriptingFilter(RequestWrapper.cleanXSS)가 요청 파라미터의 특수문자를 엔티티로 치환
|
||||
// ('('→( ')'→) '"'→" 등)하므로, 저장 전 모든 자유텍스트/JSON 필드를 언이스케이프해 원복한다.
|
||||
// (JSON 필드는 특히 '"'→" 로 깨지므로 필수)
|
||||
apiSpecInfoUI.setApiName(StringEscapeUtils.unescapeHtml(apiSpecInfoUI.getApiName()));
|
||||
apiSpecInfoUI.setApiSimpleDescription(StringEscapeUtils.unescapeHtml(apiSpecInfoUI.getApiSimpleDescription()));
|
||||
apiSpecInfoUI.setApiUrl(StringEscapeUtils.unescapeHtml(apiSpecInfoUI.getApiUrl()));
|
||||
apiSpecInfoUI.setMockUrl(StringEscapeUtils.unescapeHtml(apiSpecInfoUI.getMockUrl()));
|
||||
apiSpecInfoUI.setApiRequestSpec(StringEscapeUtils.unescapeHtml(apiSpecInfoUI.getApiRequestSpec()));
|
||||
apiSpecInfoUI.setApiResponseSpec(StringEscapeUtils.unescapeHtml(apiSpecInfoUI.getApiResponseSpec()));
|
||||
apiSpecInfoUI.setSampleRequest(StringEscapeUtils.unescapeHtml(apiSpecInfoUI.getSampleRequest()));
|
||||
apiSpecInfoUI.setSampleResponse(StringEscapeUtils.unescapeHtml(apiSpecInfoUI.getSampleResponse()));
|
||||
apiSpecInfoUI.setDescription(StringEscapeUtils.unescapeHtml(apiSpecInfoUI.getDescription()));
|
||||
apiSpecInfoUI.setTestbedSpec(StringEscapeUtils.unescapeHtml(apiSpecInfoUI.getTestbedSpec()));
|
||||
|
||||
apiSpecManService.save(apiSpecInfoUI);
|
||||
|
||||
// API 그룹 소속(조인테이블) 동기화 — apiGroupIds(CSV) 로 재설정
|
||||
try {
|
||||
java.util.List<String> gids = new ArrayList<>();
|
||||
if (StringUtils.isNotBlank(apiGroupIds)) {
|
||||
for (String g : apiGroupIds.split(",")) {
|
||||
if (StringUtils.isNotBlank(g)) {
|
||||
gids.add(g.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
apiGroupService.setApiGroupsForApi(apiSpecInfoUI.getApiId(), gids);
|
||||
} catch (Exception e) {
|
||||
logger.warn("API 그룹 동기화 실패: " + apiSpecInfoUI.getApiId(), e);
|
||||
}
|
||||
|
||||
result.put("status", "success");
|
||||
result.put("apiId", apiSpecInfoUI.getApiId());
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
// ── 레이아웃 불러오기 (GW) ──
|
||||
@PostMapping(value = "/onl/transaction/apim/djbApiSpecMan.json", params = "cmd=LOAD_REQUEST_LAYOUT")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> loadRequestLayout(String eaiSvcName) throws Exception {
|
||||
return loadLayout(eaiSvcName, true);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/transaction/apim/djbApiSpecMan.json", params = "cmd=LOAD_RESPONSE_LAYOUT")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> loadResponseLayout(String eaiSvcName) throws Exception {
|
||||
return loadLayout(eaiSvcName, false);
|
||||
}
|
||||
|
||||
// ── HTML 테이블 생성 (기존 generateSpecTableFromLayout 재사용) ──
|
||||
@PostMapping(value = "/onl/transaction/apim/djbApiSpecMan.json", params = "cmd=GENERATE_HTML")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, String>> generateHtml(String eaiSvcName, String layoutType) throws Exception {
|
||||
LayoutUI layout = resolveLayout(eaiSvcName, !"RESPONSE".equalsIgnoreCase(layoutType));
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("html", apiSpecManService.generateSpecTableFromLayout(layout));
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
// ── 메시지 예제 생성 (기존 generateSampleDataFromLayout 재사용) ──
|
||||
@PostMapping(value = "/onl/transaction/apim/djbApiSpecMan.json", params = "cmd=GENERATE_SAMPLE")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, String>> generateSample(String eaiSvcName, String layoutType) throws Exception {
|
||||
LayoutUI layout = resolveLayout(eaiSvcName, !"RESPONSE".equalsIgnoreCase(layoutType));
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("json", apiSpecManService.generateSampleDataFromLayout(layout));
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
// ── 내부 헬퍼 ──────────────────────────────────────────
|
||||
private ResponseEntity<Map<String, Object>> loadLayout(String eaiSvcName, boolean request) throws Exception {
|
||||
Map<String, Object> res = new LinkedHashMap<>();
|
||||
List<String> warnings = new ArrayList<>();
|
||||
if (StringUtils.isBlank(eaiSvcName)) {
|
||||
res.put("layoutType", request ? "REQUEST" : "RESPONSE");
|
||||
res.put("fields", new ArrayList<>());
|
||||
warnings.add("eaiSvcName 이 없습니다.");
|
||||
res.put("warnings", warnings);
|
||||
return ResponseEntity.ok(res);
|
||||
}
|
||||
LayoutUI layout = resolveLayout(eaiSvcName, request);
|
||||
if (layout == null || layout.getLayoutItems() == null || layout.getLayoutItems().isEmpty()) {
|
||||
warnings.add((request ? "요청" : "응답") + " 레이아웃이 없습니다.");
|
||||
}
|
||||
res.put("layoutType", request ? "REQUEST" : "RESPONSE");
|
||||
res.put("fields", layoutMergeService.toFields(layout, request ? "REQUEST" : "RESPONSE"));
|
||||
res.put("warnings", warnings);
|
||||
return ResponseEntity.ok(res);
|
||||
}
|
||||
|
||||
private LayoutUI resolveLayout(String eaiSvcName, boolean request) throws Exception {
|
||||
ApiInterfaceUI apiInterfaceUI = apiInterfaceService.selectDetail(eaiSvcName);
|
||||
if (apiInterfaceUI == null) {
|
||||
return null;
|
||||
}
|
||||
String layoutName = request ? apiInterfaceUI.getInboundRequestLayout() : apiInterfaceUI.getInboundResponseLayout();
|
||||
return StringUtils.isNotEmpty(layoutName) ? apiSpecManService.selectLayoutUI(layoutName) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 레이아웃 기반 자동 생성 + 자동생성 규칙(백엔드 단일 소스).
|
||||
* 규칙: title/tag/summary = API명(eaiSvcDesc), operationId = 인터페이스ID,
|
||||
* servers[0].url 을 path 앞에 붙이고 servers 제거(서버 선택 제거),
|
||||
* 요청/200 응답 예제를 레이아웃 샘플로 임베드.
|
||||
*/
|
||||
private String generateSpec(String eaiSvcName) throws Exception {
|
||||
ApiInterfaceUI apiInterfaceUI = apiInterfaceService.selectDetail(eaiSvcName);
|
||||
if (apiInterfaceUI == null) {
|
||||
throw new IllegalStateException("API 인터페이스 정보를 찾을 수 없습니다: " + eaiSvcName);
|
||||
}
|
||||
Map<String, String> inboundAdapterSpec = apiInterfaceService.getHttpAdapterInfo(apiInterfaceUI.getFromAdapter());
|
||||
if (inboundAdapterSpec == null) {
|
||||
inboundAdapterSpec = new HashMap<>();
|
||||
}
|
||||
String requestLayoutName = apiInterfaceUI.getInboundRequestLayout();
|
||||
String responseLayoutName = apiInterfaceUI.getInboundResponseLayout();
|
||||
LayoutUI requestLayoutUI = StringUtils.isNotEmpty(requestLayoutName)
|
||||
? apiSpecManService.selectLayoutUI(requestLayoutName) : null;
|
||||
LayoutUI responseLayoutUI = StringUtils.isNotEmpty(responseLayoutName)
|
||||
? apiSpecManService.selectLayoutUI(responseLayoutName) : null;
|
||||
|
||||
String baseSpec = apiSpecManService.generateSwaggerSpec(requestLayoutUI, responseLayoutUI, inboundAdapterSpec, apiInterfaceUI);
|
||||
|
||||
String apiName = StringUtils.isNotBlank(apiInterfaceUI.getEaiSvcDesc()) ? apiInterfaceUI.getEaiSvcDesc() : eaiSvcName;
|
||||
String reqSample = apiSpecManService.generateSampleDataFromLayout(requestLayoutUI);
|
||||
String resSample = apiSpecManService.generateSampleDataFromLayout(responseLayoutUI);
|
||||
String contentType = StringUtils.isNotBlank(apiInterfaceUI.getRestContentType()) ? apiInterfaceUI.getRestContentType() : "application/json";
|
||||
return applyAutoRules(baseSpec, apiName, eaiSvcName, reqSample, resSample, contentType);
|
||||
}
|
||||
|
||||
/** 자동생성 규칙 후처리(Jackson 트리 조작). 실패 시 원본 유지. */
|
||||
private String applyAutoRules(String specJson, String apiName, String eaiSvcName, String reqSample, String resSample, String contentType) {
|
||||
try {
|
||||
JsonNode parsed = objectMapper.readTree(specJson);
|
||||
if (!parsed.isObject()) {
|
||||
return specJson;
|
||||
}
|
||||
ObjectNode root = (ObjectNode) parsed;
|
||||
|
||||
root.put("openapi", "3.1.0"); // OpenAPI 3.1 방출(Swagger UI 5.x oas31 렌더 지원)
|
||||
getOrCreateObject(root, "info").put("title", apiName);
|
||||
|
||||
String tagName = "DJBank"; // 자동생성 태그명 고정
|
||||
ArrayNode tags = objectMapper.createArrayNode();
|
||||
ObjectNode tag = objectMapper.createObjectNode();
|
||||
tag.put("name", tagName);
|
||||
tag.put("description", apiName); // 태그 설명 = 간단설명(API명)
|
||||
tags.add(tag);
|
||||
root.set("tags", tags);
|
||||
|
||||
// 어댑터경로(servers[0].url = getHttpAdapterInfo urlPath)를 path 앞에 baking 하고 servers 제거.
|
||||
// 호스트(gw=djb.gateway.base-url / mock=Mock URL / sample=없음)는 프론트가 응답유형에 따라 서버로 붙인다.
|
||||
String adapterPath = "";
|
||||
JsonNode serversNode = root.get("servers");
|
||||
if (serversNode != null && serversNode.isArray() && serversNode.size() > 0 && serversNode.get(0).get("url") != null) {
|
||||
adapterPath = serversNode.get(0).get("url").asText("");
|
||||
}
|
||||
root.remove("servers");
|
||||
|
||||
JsonNode pathsNode = root.get("paths");
|
||||
if (pathsNode != null && pathsNode.isObject()) {
|
||||
ObjectNode paths = (ObjectNode) pathsNode;
|
||||
List<String> pathKeys = new ArrayList<>();
|
||||
Iterator<String> pit = paths.fieldNames();
|
||||
while (pit.hasNext()) {
|
||||
pathKeys.add(pit.next());
|
||||
}
|
||||
for (String pk : pathKeys) {
|
||||
JsonNode pathItem = paths.get(pk);
|
||||
if (pathItem != null && pathItem.isObject()) {
|
||||
List<String> methods = new ArrayList<>();
|
||||
Iterator<String> mit = pathItem.fieldNames();
|
||||
while (mit.hasNext()) {
|
||||
methods.add(mit.next());
|
||||
}
|
||||
for (String mk : methods) {
|
||||
JsonNode opNode = pathItem.get(mk);
|
||||
if (opNode != null && opNode.isObject()) {
|
||||
ObjectNode op = (ObjectNode) opNode;
|
||||
op.put("operationId", eaiSvcName);
|
||||
op.put("summary", apiName);
|
||||
ArrayNode opTags = objectMapper.createArrayNode();
|
||||
opTags.add(tagName);
|
||||
op.set("tags", opTags);
|
||||
// 예제(요청/응답 본문)는 프론트에서 스키마 기준으로 생성 → 자동생성/재생성 결과 일치. 여기선 임베드하지 않음.
|
||||
markGwOnOperation(op);
|
||||
addResponseHeader(op, "200", "Content-Type", contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
String newKey = adapterPath.isEmpty() ? pk
|
||||
: (adapterPath.replaceAll("/+$", "") + (pk.startsWith("/") ? "" : "/") + pk);
|
||||
if (!newKey.equals(pk)) {
|
||||
paths.set(newKey, pathItem);
|
||||
paths.remove(pk);
|
||||
}
|
||||
}
|
||||
}
|
||||
return objectMapper.writeValueAsString(root);
|
||||
} catch (Exception e) {
|
||||
logger.error("applyAutoRules 실패, 원본 spec 유지", e);
|
||||
return specJson;
|
||||
}
|
||||
}
|
||||
|
||||
private void setContentExample(ObjectNode op, String sampleJson) {
|
||||
if (StringUtils.isBlank(sampleJson)) {
|
||||
return;
|
||||
}
|
||||
JsonNode body = op.get("requestBody");
|
||||
if (body == null || !body.isObject()) {
|
||||
return;
|
||||
}
|
||||
JsonNode content = body.get("content");
|
||||
if (content == null || !content.isObject()) {
|
||||
return;
|
||||
}
|
||||
Iterator<String> mts = content.fieldNames();
|
||||
if (!mts.hasNext()) {
|
||||
return;
|
||||
}
|
||||
JsonNode mtNode = content.get(mts.next());
|
||||
if (mtNode != null && mtNode.isObject()) {
|
||||
((ObjectNode) mtNode).set("example", parseJsonOrText(sampleJson));
|
||||
}
|
||||
}
|
||||
|
||||
private void setResponseExample(ObjectNode op, String code, String sampleJson) {
|
||||
if (StringUtils.isBlank(sampleJson)) {
|
||||
return;
|
||||
}
|
||||
JsonNode resps = op.get("responses");
|
||||
if (resps == null || !resps.isObject()) {
|
||||
return;
|
||||
}
|
||||
JsonNode r = resps.get(code);
|
||||
if (r == null || !r.isObject()) {
|
||||
return;
|
||||
}
|
||||
ObjectNode content = getOrCreateObject((ObjectNode) r, "content");
|
||||
ObjectNode appjson = getOrCreateObject(content, "application/json");
|
||||
appjson.set("example", parseJsonOrText(sampleJson));
|
||||
}
|
||||
|
||||
/** GW 유래 스키마 필드에 x-djb-gw 마커 부여(프론트에서 잠금 표시). */
|
||||
private void markGwOnOperation(ObjectNode op) {
|
||||
JsonNode body = op.get("requestBody");
|
||||
if (body != null && body.isObject()) {
|
||||
markGwInContent(body.get("content"));
|
||||
}
|
||||
JsonNode resps = op.get("responses");
|
||||
if (resps != null && resps.isObject()) {
|
||||
List<String> codes = new ArrayList<>();
|
||||
Iterator<String> it = resps.fieldNames();
|
||||
while (it.hasNext()) {
|
||||
codes.add(it.next());
|
||||
}
|
||||
for (String c : codes) {
|
||||
JsonNode r = resps.get(c);
|
||||
if (r != null && r.isObject()) {
|
||||
markGwInContent(r.get("content"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void markGwInContent(JsonNode content) {
|
||||
if (content == null || !content.isObject()) {
|
||||
return;
|
||||
}
|
||||
List<String> mts = new ArrayList<>();
|
||||
Iterator<String> it = content.fieldNames();
|
||||
while (it.hasNext()) {
|
||||
mts.add(it.next());
|
||||
}
|
||||
for (String mt : mts) {
|
||||
JsonNode m = content.get(mt);
|
||||
if (m != null && m.isObject()) {
|
||||
markGwSchema(m.get("schema"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void markGwSchema(JsonNode schema) {
|
||||
if (schema == null || !schema.isObject()) {
|
||||
return;
|
||||
}
|
||||
ObjectNode s = (ObjectNode) schema;
|
||||
JsonNode props = s.get("properties");
|
||||
if (props != null && props.isObject()) {
|
||||
ArrayNode required = (s.get("required") != null && s.get("required").isArray())
|
||||
? (ArrayNode) s.get("required") : objectMapper.createArrayNode();
|
||||
java.util.Set<String> existing = new java.util.HashSet<>();
|
||||
required.forEach(n -> existing.add(n.asText()));
|
||||
List<String> keys = new ArrayList<>();
|
||||
Iterator<String> it = props.fieldNames();
|
||||
while (it.hasNext()) {
|
||||
keys.add(it.next());
|
||||
}
|
||||
for (String k : keys) {
|
||||
JsonNode p = props.get(k);
|
||||
if (p != null && p.isObject()) {
|
||||
((ObjectNode) p).put("x-djb-gw", true);
|
||||
if (!existing.contains(k)) { // GW 필드는 기본 '필수'
|
||||
required.add(k);
|
||||
existing.add(k);
|
||||
}
|
||||
markGwSchema(p);
|
||||
}
|
||||
}
|
||||
if (required.size() > 0) {
|
||||
s.set("required", required);
|
||||
}
|
||||
}
|
||||
JsonNode items = s.get("items");
|
||||
if (items != null && items.isObject()) {
|
||||
markGwSchema(items);
|
||||
}
|
||||
}
|
||||
|
||||
/** 응답 헤더 미리 등록(중복 시 스킵). */
|
||||
private void addResponseHeader(ObjectNode op, String code, String headerName, String example) {
|
||||
JsonNode resps = op.get("responses");
|
||||
if (resps == null || !resps.isObject()) {
|
||||
return;
|
||||
}
|
||||
JsonNode r = resps.get(code);
|
||||
if (r == null || !r.isObject()) {
|
||||
return;
|
||||
}
|
||||
ObjectNode headers = getOrCreateObject((ObjectNode) r, "headers");
|
||||
if (headers.has(headerName)) {
|
||||
return;
|
||||
}
|
||||
ObjectNode h = objectMapper.createObjectNode();
|
||||
h.put("description", headerName);
|
||||
ObjectNode sc = objectMapper.createObjectNode();
|
||||
sc.put("type", "string");
|
||||
h.set("schema", sc);
|
||||
h.put("example", example);
|
||||
headers.set(headerName, h);
|
||||
}
|
||||
|
||||
private JsonNode parseJsonOrText(String s) {
|
||||
try {
|
||||
return objectMapper.readTree(s);
|
||||
} catch (Exception e) {
|
||||
return TextNode.valueOf(s);
|
||||
}
|
||||
}
|
||||
|
||||
private ObjectNode getOrCreateObject(ObjectNode parent, String field) {
|
||||
JsonNode node = parent.get(field);
|
||||
if (node != null && node.isObject()) {
|
||||
return (ObjectNode) node;
|
||||
}
|
||||
ObjectNode created = objectMapper.createObjectNode();
|
||||
parent.set(field, created);
|
||||
return created;
|
||||
}
|
||||
|
||||
private void putIfNotNull(Map<String, String> map, String key, String value) {
|
||||
if (value != null) {
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.eactive.eai.rms.onl.transaction.apim;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.eactive.eai.rms.onl.manage.rule.layout.ui.LayoutItemUI;
|
||||
import com.eactive.eai.rms.onl.manage.rule.layout.ui.LayoutUI;
|
||||
|
||||
/**
|
||||
* D9 레이아웃 머지: GW LayoutUI 를 마법사 그리드용 필드 목록으로 변환한다.
|
||||
*
|
||||
* <p>GW 에서 온 필드는 {@code sourceType=GW}(항목명/데이터타입 잠금)로 표기하고, 예제값은
|
||||
* {@link DjbApiSpecSampleGenerator} 규칙으로 자동 채운다. 저장된 spec 의 부가정보(설명/예제/제약)
|
||||
* 병합은 클라이언트(그리드)에서 사용자 편집분과 합치므로, 서버는 GW 원본 필드 + 자동 예제까지 제공한다.
|
||||
*/
|
||||
@Service
|
||||
public class DjbApiSpecLayoutMergeService {
|
||||
|
||||
@Autowired
|
||||
private DjbApiSpecSampleGenerator sampleGenerator;
|
||||
|
||||
/** LayoutUI → 그리드 필드 행 목록(flat, depth/parentIndex 로 트리 복원 가능). */
|
||||
public List<Map<String, Object>> toFields(LayoutUI layout, String layoutType) {
|
||||
List<Map<String, Object>> rows = new ArrayList<>();
|
||||
if (layout == null || layout.getLayoutItems() == null) {
|
||||
return rows;
|
||||
}
|
||||
for (LayoutItemUI it : layout.getLayoutItems()) {
|
||||
// serno 0 은 root 컨테이너 → 스킵 (ApiSpecManService.generateSpecTableRows 규칙과 동일)
|
||||
if (it.getLoutItemSerno() != null && it.getLoutItemSerno() == 0) {
|
||||
continue;
|
||||
}
|
||||
String dataType = it.getLoutItemDataType();
|
||||
int len = it.getLoutItemLength();
|
||||
int dec = it.getLoutItemDecimal();
|
||||
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
r.put("serno", it.getLoutItemSerno());
|
||||
r.put("name", it.getLoutItemName());
|
||||
r.put("desc", it.getLoutItemDesc());
|
||||
r.put("itemType", it.getLoutItemType()); // FIELD / GROUP / GRID
|
||||
r.put("dataType", dataType);
|
||||
r.put("category", sampleGenerator.categoryOf(dataType));
|
||||
r.put("length", len);
|
||||
r.put("decimal", dec);
|
||||
r.put("depth", it.getLoutItemDepth());
|
||||
r.put("parentIndex", it.getParentLoutItemIndex());
|
||||
r.put("required", false);
|
||||
r.put("sourceType", "GW");
|
||||
r.put("example", sampleGenerator.exampleFor(dataType, it.getLoutItemName(), len, dec));
|
||||
rows.add(r);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.eactive.eai.rms.onl.transaction.apim;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* D10 예제값 자동 생성 규칙.
|
||||
* <ul>
|
||||
* <li>string → {@code sample_} + 항목명. 최대길이 초과 시 left-cut(앞에서 자름).</li>
|
||||
* <li>integer → {@code 1}</li>
|
||||
* <li>number → {@code 1.1} (소수 자릿수 지정 시 {@code 1.1000} 형태)</li>
|
||||
* <li>boolean → {@code false}</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Service
|
||||
public class DjbApiSpecSampleGenerator {
|
||||
|
||||
/** 원시 데이터타입(레이아웃 값)을 OpenAPI 계열 카테고리로 분류. */
|
||||
public String categoryOf(String rawDataType) {
|
||||
if (rawDataType == null) {
|
||||
return "string";
|
||||
}
|
||||
String t = rawDataType.trim().toLowerCase();
|
||||
if (t.contains("bool")) {
|
||||
return "boolean";
|
||||
}
|
||||
if (t.contains("decimal") || t.contains("double") || t.contains("float") || t.contains("number")) {
|
||||
return "number";
|
||||
}
|
||||
if (t.contains("int") || t.contains("long")) {
|
||||
return "integer";
|
||||
}
|
||||
if (t.contains("object") || t.contains("group")) {
|
||||
return "object";
|
||||
}
|
||||
if (t.contains("array") || t.contains("grid") || t.contains("list")) {
|
||||
return "array";
|
||||
}
|
||||
return "string";
|
||||
}
|
||||
|
||||
/**
|
||||
* 예제값 생성.
|
||||
* @param rawDataType 레이아웃 데이터타입(원시)
|
||||
* @param fieldName 항목명
|
||||
* @param maxLen 최대 길이(0/음수면 무제한)
|
||||
* @param decimalLen 소수 자릿수(0이면 미적용)
|
||||
*/
|
||||
public String exampleFor(String rawDataType, String fieldName, int maxLen, int decimalLen) {
|
||||
switch (categoryOf(rawDataType)) {
|
||||
case "integer":
|
||||
return "1";
|
||||
case "number":
|
||||
if (decimalLen > 0) {
|
||||
StringBuilder sb = new StringBuilder("1.");
|
||||
for (int i = 0; i < decimalLen; i++) {
|
||||
sb.append(i == 0 ? '1' : '0');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
return "1.1";
|
||||
case "boolean":
|
||||
return "false";
|
||||
case "object":
|
||||
case "array":
|
||||
return ""; // 컨테이너는 예제값 없음(자식으로 표현)
|
||||
default:
|
||||
String v = "sample_" + StringUtils.defaultString(fieldName);
|
||||
if (maxLen > 0 && v.length() > maxLen) {
|
||||
v = v.substring(0, maxLen); // left-cut
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package com.eactive.eai.rms.onl.transaction.apim;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* 생성/저장된 OpenAPI(3.0) spec JSON 에 인증 체계(components.securitySchemes + 글로벌 security)를 주입한다.
|
||||
*
|
||||
* <p>포털 {@code DjbSwaggerSpecEnricher} 와 동일 모델링:
|
||||
* 게이트웨이가 토큰/키를 표준 Authorization 이 아니라 커스텀 헤더로 받으므로 OAuth·API_KEY 모두
|
||||
* <b>apiKey-in-header</b> 스킴으로 표현(헤더명만 상이).
|
||||
*
|
||||
* <p>authtype(TSEAIHE01.AUTHTYPE, 소문자 저장) 매핑:
|
||||
* {@code oauth/oauth2/ca → OAUTH}, {@code api_key/apikey → API_KEY}, 그 외 → 주입 없음.
|
||||
* 원본 트리를 보존하며 조작한다(문자열 치환 금지). 매핑 불가/빈 스펙/파싱 실패 시 원본 유지(멱등).
|
||||
*/
|
||||
@Service
|
||||
public class DjbApiSpecSwaggerEnricher {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public static final String OAUTH_SCHEME = "djbOAuth";
|
||||
public static final String API_KEY_SCHEME = "djbApiKey";
|
||||
// 게이트웨이 헤더명 (포털 DjbTestbedGatewayProperty 기본값과 동일)
|
||||
public static final String OAUTH_HEADER = "X-AUTH-TOKEN";
|
||||
public static final String API_KEY_HEADER = "X-DJB-API-Key";
|
||||
|
||||
/** authtype → 스킴명. 매핑 불가면 null. */
|
||||
public String schemeNameOf(String authtype) {
|
||||
if (authtype == null) {
|
||||
return null;
|
||||
}
|
||||
switch (authtype.trim().toLowerCase()) {
|
||||
case "oauth":
|
||||
case "oauth2":
|
||||
case "ca":
|
||||
return OAUTH_SCHEME;
|
||||
case "api_key":
|
||||
case "apikey":
|
||||
return API_KEY_SCHEME;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* spec JSON 에 authtype 기반 securityScheme 주입. 매핑 불가/빈 스펙/파싱 실패 시 원본 그대로 반환.
|
||||
*/
|
||||
public String enrich(String specJson, String authtype) {
|
||||
String schemeName = schemeNameOf(authtype);
|
||||
if (StringUtils.isBlank(specJson) || schemeName == null) {
|
||||
return specJson;
|
||||
}
|
||||
try {
|
||||
JsonNode parsed = objectMapper.readTree(specJson);
|
||||
if (!parsed.isObject()) {
|
||||
return specJson;
|
||||
}
|
||||
ObjectNode root = (ObjectNode) parsed;
|
||||
|
||||
ObjectNode scheme = objectMapper.createObjectNode();
|
||||
scheme.put("type", "apiKey");
|
||||
scheme.put("in", "header");
|
||||
scheme.put("name", OAUTH_SCHEME.equals(schemeName) ? OAUTH_HEADER : API_KEY_HEADER);
|
||||
|
||||
boolean swagger2 = root.has("swagger") && !root.has("openapi");
|
||||
if (swagger2) {
|
||||
getOrCreateObject(root, "securityDefinitions").set(schemeName, scheme);
|
||||
} else {
|
||||
ObjectNode components = getOrCreateObject(root, "components");
|
||||
getOrCreateObject(components, "securitySchemes").set(schemeName, scheme);
|
||||
}
|
||||
|
||||
// 글로벌 security 요구사항 (apiKey 스킴은 빈 스코프 배열)
|
||||
ArrayNode security = objectMapper.createArrayNode();
|
||||
ObjectNode requirement = objectMapper.createObjectNode();
|
||||
requirement.set(schemeName, objectMapper.createArrayNode());
|
||||
security.add(requirement);
|
||||
root.set("security", security);
|
||||
|
||||
return objectMapper.writeValueAsString(root);
|
||||
} catch (Exception e) {
|
||||
return specJson; // 파싱/직렬화 실패 → 원본 유지(멱등)
|
||||
}
|
||||
}
|
||||
|
||||
private ObjectNode getOrCreateObject(ObjectNode parent, String field) {
|
||||
JsonNode node = parent.get(field);
|
||||
if (node != null && node.isObject()) {
|
||||
return (ObjectNode) node;
|
||||
}
|
||||
ObjectNode created = objectMapper.createObjectNode();
|
||||
parent.set(field, created);
|
||||
return created;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<configuration scan="true" scanPeriod="10 seconds">
|
||||
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
|
||||
|
||||
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
|
||||
<property name="PREFIX" value="${inst.Type:-ems}" />
|
||||
<property name="LOG_HOME" value="/Users/rinjae/eactive/djb-eapim/djb-eapim/eapim-admin/logs" />
|
||||
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
|
||||
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
|
||||
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<charset>utf-8</charset>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="STDOUT"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/${PREFIX}_stdout.log</file>
|
||||
<rollingPolicy
|
||||
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>
|
||||
${LOG_HOME}/${PREFIX}_stdout.log-%d{yyyy-MM-dd}.%i
|
||||
</fileNamePattern>
|
||||
<maxFileSize>500MB</maxFileSize>
|
||||
<maxHistory>10</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>utf-8</charset>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ACCESS_APPENDER"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/${PREFIX}_access.log</file>
|
||||
<rollingPolicy
|
||||
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>
|
||||
${LOG_HOME}/${PREFIX}_access.log-%d{yyyy-MM-dd}.%i
|
||||
</fileNamePattern>
|
||||
<maxFileSize>500MB</maxFileSize>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>utf-8</charset>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="SMS_APPENDER"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/sms.log</file>
|
||||
<rollingPolicy
|
||||
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>
|
||||
${LOG_HOME}/sms.log-%d{yyyy-MM-dd}.%i
|
||||
</fileNamePattern>
|
||||
<maxFileSize>500MB</maxFileSize>
|
||||
<maxHistory>10</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>utf-8</charset>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="QUERY_APPENDER"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/${PREFIX}_query.log</file>
|
||||
<rollingPolicy
|
||||
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>
|
||||
${LOG_HOME}/${PREFIX}_query.log-%d{yyyy-MM-dd}.%i
|
||||
</fileNamePattern>
|
||||
<maxFileSize>500MB</maxFileSize>
|
||||
<maxHistory>10</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>utf-8</charset>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="DYNAMIC_APPENDER"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/${PREFIX}_dynamic.log</file>
|
||||
<rollingPolicy
|
||||
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>
|
||||
${LOG_HOME}/${PREFIX}_dynamic.log-%d{yyyy-MM-dd}.%i
|
||||
</fileNamePattern>
|
||||
<maxFileSize>500MB</maxFileSize>
|
||||
<maxHistory>10</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>utf-8</charset>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="HIBERNATE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/${PREFIX}_hibernate.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/${PREFIX}_hibernate.log-%d{yyyy-MM-dd}.%i</fileNamePattern>
|
||||
<maxFileSize>500MB</maxFileSize>
|
||||
<maxHistory>10</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>utf-8</charset>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="HOTSWAP_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/${PREFIX}_hotswap.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/${PREFIX}_hotswap.log-%d{yyyy-MM-dd}.%i</fileNamePattern>
|
||||
<maxFileSize>500MB</maxFileSize>
|
||||
<maxHistory>10</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>utf-8</charset>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="com.eactive.eai.rms.onl.server" level="ERROR" />
|
||||
<logger name="com.eactive.eai.rms.onl.dashboard" level="ERROR" />
|
||||
<logger name="com.eactive.eai.rms.common.acl.user.BizDao" level="ERROR" />
|
||||
<logger name="com.eactive.eai.rms.common.acl.user.BizDao" level="ERROR" />
|
||||
<logger name="com.eactive.eai.rms.ext.djb.job.ApiStatusMonitorJob" level="ERROR" />
|
||||
|
||||
<logger name="org.springframework.orm.jpa.JpaTransactionManager" level="ERROR" additivity="false" />
|
||||
|
||||
<logger name="org.springframework" level="INFO" />
|
||||
<logger name="net.sf.ehcache" level="INFO" />
|
||||
|
||||
|
||||
<logger name="ACCESS_LOGGER" level="DEBUG" >
|
||||
<appender-ref ref="ACCESS_APPENDER" />
|
||||
</logger>
|
||||
<logger name="SMS_LOGGER" level="DEBUG" >
|
||||
<appender-ref ref="SMS_APPENDER" />
|
||||
</logger>
|
||||
<logger name="DYNAMIC_LOGGER" level="DEBUG" >
|
||||
<appender-ref ref="DYNAMIC_APPENDER" />
|
||||
</logger>
|
||||
|
||||
|
||||
<!-- QUERY_APPENDER start -->
|
||||
<!-- Hibernate SQL & 파라미터 바인딩 -->
|
||||
<logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="QUERY_APPENDER" />
|
||||
<appender-ref ref="HIBERNATE_APPENDER" />
|
||||
</logger>
|
||||
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" additivity="false">
|
||||
<appender-ref ref="QUERY_APPENDER" />
|
||||
<appender-ref ref="HIBERNATE_APPENDER" />
|
||||
</logger>
|
||||
<logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="TRACE" additivity="false">
|
||||
<appender-ref ref="QUERY_APPENDER" />
|
||||
<appender-ref ref="HIBERNATE_APPENDER" />
|
||||
</logger>
|
||||
<!-- Hibernate 일반 로그 (DDL/init/connection 등) -->
|
||||
<logger name="org.hibernate" level="INFO" additivity="false">
|
||||
<appender-ref ref="HIBERNATE_APPENDER" />
|
||||
<appender-ref ref="STDOUT" />
|
||||
</logger>
|
||||
|
||||
<!-- iBATIS 2.3.4 (commons-logging 또는 log4j-over-slf4j 통해 SLF4J 도달) -->
|
||||
<logger name="com.ibatis" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="QUERY_APPENDER" />
|
||||
</logger>
|
||||
<logger name="com.eactive.eai.rms.common.base.SchemaIdConfiguredSqlMapTemplate" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="QUERY_APPENDER" />
|
||||
</logger>
|
||||
<!-- QUERY_APPENDER end -->
|
||||
|
||||
<!-- HotswapAgent는 자체 logger를 사용하므로 logback에 안 잡힘. hotswap-agent.properties의 LOGFILE로 직접 파일 출력 -->
|
||||
|
||||
<root>
|
||||
<level value="INFO" />
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user