merge
@@ -0,0 +1,260 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>광주은행 API Portal</title>
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<style type="text/css">
|
||||
/* Reset styles */
|
||||
body, table, td, p, a, li, blockquote {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
table, td {
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
a[x-apple-data-detectors] {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
font-size: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
}
|
||||
/* Custom styles */
|
||||
.email-wrapper {
|
||||
width: 100%;
|
||||
background-color: #c3dfea;
|
||||
}
|
||||
.email-container {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
background-color: #c3dfea;
|
||||
}
|
||||
.header-section {
|
||||
padding: 28px 25px;
|
||||
}
|
||||
.content-section {
|
||||
background-color: #fdfdfd;
|
||||
margin: 0 25px;
|
||||
border-top: 4px solid #0049b4;
|
||||
min-height: 400px;
|
||||
}
|
||||
.content-inner {
|
||||
padding: 40px 30px;
|
||||
}
|
||||
.footer-section {
|
||||
padding: 25px;
|
||||
}
|
||||
.footer-notice {
|
||||
font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-size: 12px;
|
||||
color: #515151;
|
||||
line-height: 20px;
|
||||
}
|
||||
.footer-links {
|
||||
font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-size: 12px;
|
||||
color: #515151;
|
||||
}
|
||||
.footer-links a {
|
||||
color: #515151;
|
||||
text-decoration: none;
|
||||
}
|
||||
.footer-links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.footer-info {
|
||||
font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-size: 12px;
|
||||
color: #515151;
|
||||
}
|
||||
.footer-copyright {
|
||||
font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-size: 12px;
|
||||
color: #8c959f;
|
||||
}
|
||||
.divider {
|
||||
color: #515151;
|
||||
padding: 0 7px;
|
||||
}
|
||||
.logo-text {
|
||||
font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #212529;
|
||||
vertical-align: middle;
|
||||
padding-left: 8px;
|
||||
}
|
||||
/* Responsive styles */
|
||||
@media screen and (max-width: 700px) {
|
||||
.email-container {
|
||||
width: 100% !important;
|
||||
}
|
||||
.content-section {
|
||||
margin: 0 15px !important;
|
||||
}
|
||||
.content-inner {
|
||||
padding: 30px 20px !important;
|
||||
}
|
||||
.header-section,
|
||||
.footer-section {
|
||||
padding: 20px 15px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; background-color: #c3dfea;">
|
||||
<!-- Email Wrapper -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #c3dfea;">
|
||||
<tr>
|
||||
<td align="center" style="padding: 0;">
|
||||
<!-- Email Container -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="700" class="email-container" style="max-width: 700px; background-color: #c3dfea;">
|
||||
|
||||
<!-- Header Section -->
|
||||
<tr>
|
||||
<td class="header-section" style="padding: 28px 25px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td style="vertical-align: middle;">
|
||||
<!-- Logo Image - Replace src with actual logo URL -->
|
||||
<img src="email_logo_top.png" alt="광주은행" width="118" height="22" style="display: inline-block; vertical-align: middle;">
|
||||
<span class="logo-text" style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 16px; font-weight: 700; color: #212529; vertical-align: middle; padding-left: 8px;">API Portal</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Content Section -->
|
||||
<tr>
|
||||
<td style="padding: 0 25px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #fdfdfd; border-top: 4px solid #0049b4;">
|
||||
<tr>
|
||||
<td class="content-inner" style="padding: 40px 30px; min-height: 400px;">
|
||||
|
||||
<!-- ====================== -->
|
||||
<!-- EMAIL CONTENT START -->
|
||||
<!-- ====================== -->
|
||||
|
||||
<!-- 이 영역에 실제 이메일 내용을 작성하세요 -->
|
||||
<!-- Example content structure: -->
|
||||
<!--
|
||||
<h1 style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 24px; color: #212529; margin: 0 0 20px 0;">
|
||||
이메일 제목
|
||||
</h1>
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 14px; color: #515151; line-height: 1.6; margin: 0 0 15px 0;">
|
||||
이메일 본문 내용이 들어갑니다.
|
||||
</p>
|
||||
-->
|
||||
|
||||
<!-- ====================== -->
|
||||
<!-- EMAIL CONTENT END -->
|
||||
<!-- ====================== -->
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Section -->
|
||||
<tr>
|
||||
<td class="footer-section" style="padding: 25px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<!-- Footer Logo and Notice -->
|
||||
<tr>
|
||||
<td style="padding-bottom: 10px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td width="96" style="vertical-align: top; padding-right: 25px;">
|
||||
<!-- Footer Logo - Replace src with actual logo URL -->
|
||||
<img src="email_logo_bottom.png" alt="광주은행" width="96" height="18" style="display: block;">
|
||||
</td>
|
||||
<td style="vertical-align: top;">
|
||||
<p class="footer-notice" style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #515151; line-height: 20px; margin: 0;">
|
||||
본 메일은 발신 전용으로 회신되지 않습니다.<br>
|
||||
관련 문의사항은 고객센터 (1588-3388) 또는 홈페이지를 이용하시기 바랍니다.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Links -->
|
||||
<tr>
|
||||
<td style="padding: 10px 0 0 121px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td class="footer-links" style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #515151;">
|
||||
<a href="https://www.kjbank.com" style="color: #515151; text-decoration: none;">은행소개</a>
|
||||
<span class="divider" style="color: #515151; padding: 0 7px;">|</span>
|
||||
<a href="https://www.kjbank.com/privacy" style="color: #515151; text-decoration: none;">개인정보처리방침</a>
|
||||
<span class="divider" style="color: #515151; padding: 0 7px;">|</span>
|
||||
<a href="https://www.kjbank.com/faq" style="color: #515151; text-decoration: none;">자주 묻는 질문</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Info -->
|
||||
<tr>
|
||||
<td style="padding: 8px 0 0 121px;">
|
||||
<p class="footer-info" style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #515151; margin: 0;">
|
||||
광주광역시 동구 제봉로225, 1층(대인동)
|
||||
<span class="divider" style="color: #515151; padding: 0 7px;">|</span>
|
||||
고객센터 : 1588-3388
|
||||
<span class="divider" style="color: #515151; padding: 0 7px;">|</span>
|
||||
사업자 등록번호 : ???-??-?????
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Copyright -->
|
||||
<tr>
|
||||
<td style="padding: 12px 0 0 121px;">
|
||||
<p class="footer-copyright" style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #8c959f; margin: 0;">
|
||||
Copyright KWANGJU Bank. ALL Rights Reserved.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<!-- End Email Container -->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- End Email Wrapper -->
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,255 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>일회용 비밀번호 - 광주은행 API Portal</title>
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<style type="text/css">
|
||||
/* Reset styles */
|
||||
body, table, td, p, a, li, blockquote {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
table, td {
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
a[x-apple-data-detectors] {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
font-size: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
}
|
||||
/* Responsive styles */
|
||||
@media screen and (max-width: 700px) {
|
||||
.email-container {
|
||||
width: 100% !important;
|
||||
}
|
||||
.content-section {
|
||||
margin: 0 15px !important;
|
||||
}
|
||||
.content-inner {
|
||||
padding: 30px 20px !important;
|
||||
}
|
||||
.header-section,
|
||||
.footer-section {
|
||||
padding: 20px 15px !important;
|
||||
}
|
||||
.password-box {
|
||||
width: 90% !important;
|
||||
}
|
||||
.login-button {
|
||||
width: 80% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; background-color: #ececec;">
|
||||
<!-- Email Wrapper -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #ececec;">
|
||||
<tr>
|
||||
<td align="center" style="padding: 0;">
|
||||
<!-- Email Container -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="700" class="email-container" style="max-width: 700px; background-color: #ececec;">
|
||||
|
||||
<!-- Header Section -->
|
||||
<tr>
|
||||
<td class="header-section" style="padding: 14px 25px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td style="vertical-align: middle;">
|
||||
<!-- Logo Image -->
|
||||
<img src="email_logo_top.png" alt="광주은행" width="118" height="22" style="display: inline-block; vertical-align: middle;">
|
||||
<span style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 16px; font-weight: 700; color: #212529; vertical-align: middle; padding-left: 8px;">API Portal</span>
|
||||
</td>
|
||||
<td style="vertical-align: middle; text-align: right;">
|
||||
<!-- Key Icon -->
|
||||
<img src="email_icon_key.png" alt="비밀번호" width="74" height="60" style="display: inline-block; vertical-align: middle;">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Content Section -->
|
||||
<tr>
|
||||
<td style="padding: 0 25px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #fdfdfd; border-top: 4px solid #0049b4;">
|
||||
<tr>
|
||||
<td class="content-inner" style="padding: 40px 30px;">
|
||||
|
||||
<!-- Title -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 30px;">
|
||||
<h1 style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 28px; font-weight: 700; color: #2f3b51; margin: 0;">
|
||||
[일회용 비밀번호 ]
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Greeting Message -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 30px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 20px; color: #000000; line-height: 36px; margin: 0; text-align: center;">
|
||||
<span style="font-weight: 700;">{{userName}}님</span> 안녕하세요.<br>
|
||||
광주은행 API Portal 비밀번호가 초기화 되어 일회용<br>
|
||||
비밀번호를 발급해 드립니다.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- One-Time Password Box -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 30px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" class="password-box" style="width: 438px; max-width: 100%;">
|
||||
<tr>
|
||||
<td align="center" style="background-color: #ffffff; border: 6px solid #e5e7eb; border-radius: 20px; padding: 20px 30px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 24px; font-weight: 700; color: #0049b4; margin: 0; letter-spacing: 2px;">
|
||||
{{otpPassword}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Login Guide Text -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 25px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 20px; font-weight: 700; color: #212529; margin: 0;">
|
||||
아래 버튼을 클릭하여 로그인을 진행해 주세요.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Login Button -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 20px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td align="center" class="login-button" style="background-color: #0049b4; border-radius: 12px; width: 220px;">
|
||||
<a href="{{loginUrl}}" target="_blank" style="display: block; padding: 18px 40px; font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 18px; font-weight: 700; color: #ffffff; text-decoration: none; text-align: center;">
|
||||
로그인
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Section -->
|
||||
<tr>
|
||||
<td class="footer-section" style="padding: 25px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<!-- Footer Logo and Notice -->
|
||||
<tr>
|
||||
<td style="padding-bottom: 10px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td width="96" style="vertical-align: top; padding-right: 25px;">
|
||||
<!-- Footer Logo -->
|
||||
<img src="email_logo_bottom.png" alt="광주은행" width="96" height="18" style="display: block;">
|
||||
</td>
|
||||
<td style="vertical-align: top;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #515151; line-height: 20px; margin: 0;">
|
||||
본 메일은 발신 전용으로 회신되지 않습니다.<br>
|
||||
관련 문의사항은 고객센터 (1588-3388) 또는 홈페이지를 이용하시기 바랍니다.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Links -->
|
||||
<tr>
|
||||
<td style="padding: 10px 0 0 121px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #515151;">
|
||||
<a href="https://www.kjbank.com" style="color: #515151; text-decoration: none;">은행소개</a>
|
||||
<span style="color: #515151; padding: 0 7px;">|</span>
|
||||
<a href="https://www.kjbank.com/privacy" style="color: #515151; text-decoration: none;">개인정보처리방침</a>
|
||||
<span style="color: #515151; padding: 0 7px;">|</span>
|
||||
<a href="https://www.kjbank.com/faq" style="color: #515151; text-decoration: none;">자주 묻는 질문</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Info -->
|
||||
<tr>
|
||||
<td style="padding: 8px 0 0 121px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #515151; margin: 0;">
|
||||
광주광역시 동구 제봉로225, 1층(대인동)
|
||||
<span style="color: #515151; padding: 0 7px;">|</span>
|
||||
고객센터 : 1588-3388
|
||||
<span style="color: #515151; padding: 0 7px;">|</span>
|
||||
사업자 등록번호 : ???-??-?????
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Copyright -->
|
||||
<tr>
|
||||
<td style="padding: 12px 0 0 121px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #8c959f; margin: 0;">
|
||||
Copyright KWANGJU Bank. ALL Rights Reserved.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<!-- End Email Container -->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- End Email Wrapper -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,234 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>비밀번호 변경 완료 안내 - 광주은행 API Portal</title>
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<style type="text/css">
|
||||
/* Reset styles */
|
||||
body, table, td, p, a, li, blockquote {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
table, td {
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
a[x-apple-data-detectors] {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
font-size: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
}
|
||||
/* Responsive styles */
|
||||
@media screen and (max-width: 700px) {
|
||||
.email-container {
|
||||
width: 100% !important;
|
||||
}
|
||||
.content-section {
|
||||
margin: 0 15px !important;
|
||||
}
|
||||
.content-inner {
|
||||
padding: 30px 20px !important;
|
||||
}
|
||||
.header-section,
|
||||
.footer-section {
|
||||
padding: 20px 15px !important;
|
||||
}
|
||||
.login-button {
|
||||
width: 80% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; background-color: #ececec;">
|
||||
<!-- Email Wrapper -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #ececec;">
|
||||
<tr>
|
||||
<td align="center" style="padding: 0;">
|
||||
<!-- Email Container -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="700" class="email-container" style="max-width: 700px; background-color: #ececec;">
|
||||
|
||||
<!-- Header Section -->
|
||||
<tr>
|
||||
<td class="header-section" style="padding: 14px 25px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td style="vertical-align: middle;">
|
||||
<!-- Logo Image -->
|
||||
<img src="email_logo_top.png" alt="광주은행" width="118" height="22" style="display: inline-block; vertical-align: middle;">
|
||||
<span style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 16px; font-weight: 700; color: #212529; vertical-align: middle; padding-left: 8px;">API Portal</span>
|
||||
</td>
|
||||
<td style="vertical-align: middle; text-align: right;">
|
||||
<!-- Check/Shield Icon -->
|
||||
<img src="email_icon_check.png" alt="완료" width="64" height="64" style="display: inline-block; vertical-align: middle;">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Content Section -->
|
||||
<tr>
|
||||
<td style="padding: 0 25px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #fdfdfd; border-top: 4px solid #0049b4;">
|
||||
<tr>
|
||||
<td class="content-inner" style="padding: 50px 30px;">
|
||||
|
||||
<!-- Title -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 40px;">
|
||||
<h1 style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 28px; font-weight: 700; color: #2f3b51; margin: 0;">
|
||||
[ 비밀번호 변경 완료 안내 ]
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Greeting Message -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 50px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 20px; color: #000000; line-height: 36px; margin: 0; text-align: center;">
|
||||
<span style="font-weight: 700;">{{userName}}님</span> 안녕하세요.<br>
|
||||
광주은행 API Portal 비밀번호가 변경되어 안내 드립니다.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Login Guide Text -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 25px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 20px; font-weight: 700; color: #212529; margin: 0;">
|
||||
아래 버튼을 클릭하여 로그인을 진행해 주세요.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Login Button -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 20px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td align="center" class="login-button" style="background-color: #0049b4; border-radius: 12px; width: 220px;">
|
||||
<a href="{{loginUrl}}" target="_blank" style="display: block; padding: 18px 40px; font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 18px; font-weight: 700; color: #ffffff; text-decoration: none; text-align: center;">
|
||||
로그인
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Section -->
|
||||
<tr>
|
||||
<td class="footer-section" style="padding: 25px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<!-- Footer Logo and Notice -->
|
||||
<tr>
|
||||
<td style="padding-bottom: 10px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td width="96" style="vertical-align: top; padding-right: 25px;">
|
||||
<!-- Footer Logo -->
|
||||
<img src="email_logo_bottom.png" alt="광주은행" width="96" height="18" style="display: block;">
|
||||
</td>
|
||||
<td style="vertical-align: top;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #515151; line-height: 20px; margin: 0;">
|
||||
본 메일은 발신 전용으로 회신되지 않습니다.<br>
|
||||
관련 문의사항은 고객센터 (1588-3388) 또는 홈페이지를 이용하시기 바랍니다.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Links -->
|
||||
<tr>
|
||||
<td style="padding: 10px 0 0 121px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #515151;">
|
||||
<a href="https://www.kjbank.com" style="color: #515151; text-decoration: none;">은행소개</a>
|
||||
<span style="color: #515151; padding: 0 7px;">|</span>
|
||||
<a href="https://www.kjbank.com/privacy" style="color: #515151; text-decoration: none;">개인정보처리방침</a>
|
||||
<span style="color: #515151; padding: 0 7px;">|</span>
|
||||
<a href="https://www.kjbank.com/faq" style="color: #515151; text-decoration: none;">자주 묻는 질문</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Info -->
|
||||
<tr>
|
||||
<td style="padding: 8px 0 0 121px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #515151; margin: 0;">
|
||||
광주광역시 동구 제봉로225, 1층(대인동)
|
||||
<span style="color: #515151; padding: 0 7px;">|</span>
|
||||
고객센터 : 1588-3388
|
||||
<span style="color: #515151; padding: 0 7px;">|</span>
|
||||
사업자 등록번호 : ???-??-?????
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Copyright -->
|
||||
<tr>
|
||||
<td style="padding: 12px 0 0 121px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #8c959f; margin: 0;">
|
||||
Copyright KWANGJU Bank. ALL Rights Reserved.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<!-- End Email Container -->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- End Email Wrapper -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,235 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>이메일 인증 - 광주은행 API Portal</title>
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<style type="text/css">
|
||||
/* Reset styles */
|
||||
body, table, td, p, a, li, blockquote {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
table, td {
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
a[x-apple-data-detectors] {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
font-size: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
}
|
||||
/* Responsive styles */
|
||||
@media screen and (max-width: 700px) {
|
||||
.email-container {
|
||||
width: 100% !important;
|
||||
}
|
||||
.content-section {
|
||||
margin: 0 15px !important;
|
||||
}
|
||||
.content-inner {
|
||||
padding: 30px 20px !important;
|
||||
}
|
||||
.header-section,
|
||||
.footer-section {
|
||||
padding: 20px 15px !important;
|
||||
}
|
||||
.verification-box {
|
||||
width: 90% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; background-color: #c3dfea;">
|
||||
<!-- Email Wrapper -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #c3dfea;">
|
||||
<tr>
|
||||
<td align="center" style="padding: 0;">
|
||||
<!-- Email Container -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="700" class="email-container" style="max-width: 700px; background-color: #c3dfea;">
|
||||
|
||||
<!-- Header Section -->
|
||||
<tr>
|
||||
<td class="header-section" style="padding: 14px 25px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td style="vertical-align: middle;">
|
||||
<!-- Logo Image -->
|
||||
<img src="email_logo_top.png" alt="광주은행" width="118" height="22" style="display: inline-block; vertical-align: middle;">
|
||||
<span style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 16px; font-weight: 700; color: #212529; vertical-align: middle; padding-left: 8px;">API Portal</span>
|
||||
</td>
|
||||
<td style="vertical-align: middle; text-align: right;">
|
||||
<!-- Lock Icon -->
|
||||
<img src="email_icon_lock.png" alt="보안" width="74" height="60" style="display: inline-block; vertical-align: middle;">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Content Section -->
|
||||
<tr>
|
||||
<td style="padding: 0 25px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: #fdfdfd; border-top: 4px solid #0049b4;">
|
||||
<tr>
|
||||
<td class="content-inner" style="padding: 40px 30px;">
|
||||
|
||||
<!-- Title -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 40px;">
|
||||
<h1 style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 28px; font-weight: 700; color: #2f3b51; margin: 0;">
|
||||
[ 이메일 인증 ]
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Greeting Message -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 30px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 20px; color: #000000; line-height: 36px; margin: 0; text-align: center;">
|
||||
<span style="font-weight: 700;">{{userName}}님</span> 안녕하세요.<br>
|
||||
광주은행 API Portal에 가입해 주셔서 감사합니다.<br>
|
||||
아래 인증번호를 이메일 인증 페이지에 입력 해주세요.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Verification Code Box -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 30px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" class="verification-box" style="width: 438px; max-width: 100%;">
|
||||
<tr>
|
||||
<td align="center" style="background-color: #ffffff; border: 6px solid #e5e7eb; border-radius: 20px; padding: 20px 30px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 24px; font-weight: 700; color: #0049b4; margin: 0; letter-spacing: 2px;">
|
||||
{{verificationCode}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Verification Link -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 20px;">
|
||||
<a href="{{verificationUrl}}" style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 18px; color: #212529; text-decoration: none; word-break: break-all;">
|
||||
{{verificationUrl}}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Section -->
|
||||
<tr>
|
||||
<td class="footer-section" style="padding: 25px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<!-- Footer Logo and Notice -->
|
||||
<tr>
|
||||
<td style="padding-bottom: 10px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td width="96" style="vertical-align: top; padding-right: 25px;">
|
||||
<!-- Footer Logo -->
|
||||
<img src="email_logo_bottom.png" alt="광주은행" width="96" height="18" style="display: block;">
|
||||
</td>
|
||||
<td style="vertical-align: top;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #515151; line-height: 20px; margin: 0;">
|
||||
본 메일은 발신 전용으로 회신되지 않습니다.<br>
|
||||
관련 문의사항은 고객센터 (1588-3388) 또는 홈페이지를 이용하시기 바랍니다.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Links -->
|
||||
<tr>
|
||||
<td style="padding: 10px 0 0 121px;">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #515151;">
|
||||
<a href="https://www.kjbank.com" style="color: #515151; text-decoration: none;">은행소개</a>
|
||||
<span style="color: #515151; padding: 0 7px;">|</span>
|
||||
<a href="https://www.kjbank.com/privacy" style="color: #515151; text-decoration: none;">개인정보처리방침</a>
|
||||
<span style="color: #515151; padding: 0 7px;">|</span>
|
||||
<a href="https://www.kjbank.com/faq" style="color: #515151; text-decoration: none;">자주 묻는 질문</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer Info -->
|
||||
<tr>
|
||||
<td style="padding: 8px 0 0 121px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #515151; margin: 0;">
|
||||
광주광역시 동구 제봉로225, 1층(대인동)
|
||||
<span style="color: #515151; padding: 0 7px;">|</span>
|
||||
고객센터 : 1588-3388
|
||||
<span style="color: #515151; padding: 0 7px;">|</span>
|
||||
사업자 등록번호 : ???-??-?????
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Copyright -->
|
||||
<tr>
|
||||
<td style="padding: 12px 0 0 121px;">
|
||||
<p style="font-family: 'Noto Sans KR', 'Malgun Gothic', sans-serif; font-size: 12px; color: #8c959f; margin: 0;">
|
||||
Copyright KWANGJU Bank. ALL Rights Reserved.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<!-- End Email Container -->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- End Email Wrapper -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
@Controller
|
||||
@RequestMapping("/agreements")
|
||||
public class AgreementsController {
|
||||
public static final String TERMS_AGREEMENTS = "fragment/kbank/terms_agreements";
|
||||
public static final String TERMS_AGREEMENTS = "fragment/kjbank/terms_agreements";
|
||||
|
||||
private final AgreementsFacade agreementsFacade;
|
||||
|
||||
|
||||
@@ -4,18 +4,11 @@ package com.eactive.apim.portal.apps.apis.controller;
|
||||
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
|
||||
import com.eactive.apim.portal.apps.apis.service.ApiService;
|
||||
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
|
||||
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
|
||||
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
|
||||
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
|
||||
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
|
||||
import com.eactive.apim.portal.apps.app.service.AppServiceFacade;
|
||||
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
|
||||
import com.eactive.apim.portal.common.util.SecurityUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -28,11 +21,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequiredArgsConstructor
|
||||
public class ApiAjaxController {
|
||||
|
||||
private final static String STG_SERVER = "STG";
|
||||
|
||||
private final ApiService apiService;
|
||||
private final ApiServiceService apiServiceService;
|
||||
private final AppServiceFacade appServiceFacade;
|
||||
|
||||
public Map<String, ApiServiceDTO> getMainIconsFromServiceDtos(List<ApiServiceDTO> apiServices) {
|
||||
Map<String, ApiServiceDTO> mainIconsMap = new HashMap<>();
|
||||
@@ -54,53 +44,15 @@ public class ApiAjaxController {
|
||||
Map<String, ApiServiceDTO> mainIconsMap = getMainIconsFromServiceDtos(apiServices);
|
||||
List<ApiSpecInfoDto> apiSpecInfoDtos = apiService.findApis();
|
||||
|
||||
for (ApiSpecInfoDto spec : apiSpecInfoDtos) {
|
||||
ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId());
|
||||
if (serviceDTO != null) {
|
||||
String mainIcon = serviceDTO.getMainIcon();
|
||||
String service = serviceDTO.getGroupName();
|
||||
spec.setMainIcon(mainIcon);
|
||||
spec.setService(service);
|
||||
}
|
||||
}
|
||||
return apiSpecInfoDtos;
|
||||
}
|
||||
|
||||
@GetMapping("/for_request_prod")
|
||||
@Secured("ROLE_API_KEY_REQUEST")
|
||||
public List<ApiSpecInfoDto> apiRequestProd(@ModelAttribute ApiGroupSearch search) {
|
||||
|
||||
List<ApiServiceDTO> apiServices = apiServiceService.searchApiGroups(search);
|
||||
Map<String, ApiServiceDTO> mainIconsMap = getMainIconsFromServiceDtos(apiServices);
|
||||
|
||||
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
|
||||
|
||||
List<ClientDTO> apikeys = appServiceFacade.getApiKeyList(user.getPortalOrg(), STG_SERVER);
|
||||
|
||||
List<ApiServiceApiDTO> apis = apikeys.stream()
|
||||
.flatMap(client -> client.getApiList().stream()) // flatten all apiLists into a single stream of ApiServiceApiDTO
|
||||
.distinct() // remove duplicates
|
||||
.collect(Collectors.toList()); // collect to List
|
||||
|
||||
List<ApiSpecInfoDto> apiSpecInfoDtos = new ArrayList<>();
|
||||
|
||||
apis.forEach(api -> {
|
||||
ApiSpecInfoDto specInfoDto = new ApiSpecInfoDto();
|
||||
specInfoDto.setApiId(api.getApiId());
|
||||
specInfoDto.setApiName(api.getApiDesc());
|
||||
apiSpecInfoDtos.add(specInfoDto);
|
||||
});
|
||||
|
||||
for (ApiSpecInfoDto spec : apiSpecInfoDtos) {
|
||||
ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId());
|
||||
if (serviceDTO != null) {
|
||||
String mainIcon = serviceDTO.getMainIcon();
|
||||
String service = serviceDTO.getGroupName();
|
||||
spec.setMainIcon(mainIcon);
|
||||
spec.setService(service);
|
||||
}
|
||||
}
|
||||
return apiSpecInfoDtos;
|
||||
// Filter APIs that have matching service information
|
||||
return apiSpecInfoDtos.stream()
|
||||
.filter(spec -> mainIconsMap.containsKey(spec.getApiId()))
|
||||
.peek(spec -> {
|
||||
ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId());
|
||||
spec.setMainIcon(serviceDTO.getMainIcon());
|
||||
spec.setService(serviceDTO.getGroupName());
|
||||
})
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package com.eactive.apim.portal.apps.apis.controller;
|
||||
|
||||
|
||||
import com.eactive.apim.portal.apps.apis.dto.ApiSearchData;
|
||||
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
|
||||
import com.eactive.apim.portal.apps.apis.service.ApiSearchFacade;
|
||||
import com.eactive.apim.portal.apps.apis.service.ApiService;
|
||||
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
|
||||
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
|
||||
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import com.eactive.apim.portal.common.exception.NotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
package com.eactive.apim.portal.apps.app.controller;
|
||||
|
||||
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
|
||||
import com.eactive.apim.portal.apps.apis.dto.ApiSearchData;
|
||||
import com.eactive.apim.portal.apps.apis.service.ApiSearchFacade;
|
||||
import com.eactive.apim.portal.apps.app.dto.AppRequestDTO;
|
||||
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
|
||||
import com.eactive.apim.portal.apps.app.dto.ClientSimpleDTO;
|
||||
import com.eactive.apim.portal.apps.app.mapper.CredentialMapper;
|
||||
import com.eactive.apim.portal.apps.app.service.AppServiceFacade;
|
||||
import com.eactive.apim.portal.common.dto.ResponseDTO;
|
||||
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
|
||||
import com.eactive.apim.portal.common.util.EncryptionUtil;
|
||||
import com.eactive.apim.portal.common.util.SecurityUtil;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.ValidationUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/myapikey")
|
||||
@RequiredArgsConstructor
|
||||
@Secured("ROLE_API_KEY_REQUEST")
|
||||
public class ApiKeyRestController {
|
||||
|
||||
private final static String PROD_SERVER = "PROD";
|
||||
private final static String STG_SERVER = "STG";
|
||||
|
||||
private final AppServiceFacade appServiceFacade;
|
||||
private final CredentialMapper credentialMapper;
|
||||
private final EncryptionUtil encryptionUtil;
|
||||
private final ApiSearchFacade apiSearchFacade;
|
||||
|
||||
@PostMapping("/api_key_request")
|
||||
public ResponseDTO apiRequest(@RequestBody AppRequestDTO appRequest) {
|
||||
appRequest.setOrg(SecurityUtil.getUserOrg());
|
||||
if (appRequest.getType() == null) {
|
||||
appRequest.setType(AppRequestType.NEW);
|
||||
}
|
||||
AppRequestDTO appRequestDTO = appServiceFacade.createAppRequest(appRequest);
|
||||
appServiceFacade.beginApproval(appRequestDTO.getApproval().getId());
|
||||
return new ResponseDTO(200, "SUCCESS", appRequestDTO.getType().getDescription() + " 신청이 완료되었습니다.");
|
||||
}
|
||||
|
||||
@PostMapping("/api_key_request_prod")
|
||||
public ResponseDTO requestProdApiKey(@RequestBody AppRequestDTO appRequest) {
|
||||
appRequest.setOrg(SecurityUtil.getUserOrg());
|
||||
|
||||
if (appRequest.getClientId() != null && !appRequest.getClientId().isEmpty()) {
|
||||
try {
|
||||
appRequest.setClientId(encryptionUtil.decrypt(appRequest.getClientId()));
|
||||
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
return new ResponseDTO(400, "FAILURE", "API 키 정보가 올바르지 않습니다.");
|
||||
}
|
||||
appRequest.setType(AppRequestType.PROD_MODIFY);
|
||||
} else {
|
||||
appRequest.setType(AppRequestType.PROD_NEW);
|
||||
}
|
||||
|
||||
AppRequestDTO appRequestDTO = appServiceFacade.createAppRequest(appRequest);
|
||||
appServiceFacade.beginApproval(appRequestDTO.getApproval().getId());
|
||||
|
||||
return new ResponseDTO(200, "SUCCESS", appRequestDTO.getType().getDescription() + " 신청이 완료되었습니다.");
|
||||
}
|
||||
|
||||
@GetMapping("/dev")
|
||||
public List<ClientSimpleDTO> apiKeyList() {
|
||||
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
|
||||
List<ClientDTO> apikeys = appServiceFacade.getApiKeyList(user.getPortalOrg(), STG_SERVER);
|
||||
|
||||
ApiSearchData apiSearchData = apiSearchFacade.loadAllApiData();
|
||||
for (ClientDTO apikey : apikeys) {
|
||||
apikey.getApiList().forEach(api -> {
|
||||
api.setService(apiSearchData.getServicesByApiId().get(api.getApiId()).getGroupName());
|
||||
});
|
||||
}
|
||||
|
||||
return apikeys.stream().map(credentialMapper::mapToClientSimpleDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@PostMapping("/prod")
|
||||
public ClientSimpleDTO apiKeyProdList(@RequestBody AppRequestDTO appRequest) {
|
||||
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
|
||||
|
||||
try {
|
||||
ClientDTO apiKey = appServiceFacade.getApiKey(user.getPortalOrg().getId(), encryptionUtil.decrypt(appRequest.getClientId()), PROD_SERVER);
|
||||
ApiSearchData apiSearchData = apiSearchFacade.loadAllApiData();
|
||||
apiKey.getApiList().forEach(api -> {
|
||||
api.setService(apiSearchData.getServicesByApiId().get(api.getApiId()).getGroupName());
|
||||
});
|
||||
apiKey.setClientid(encryptionUtil.encrypt(apiKey.getClientid()));
|
||||
return credentialMapper.mapToClientSimpleDTO(apiKey);
|
||||
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
return new ClientSimpleDTO();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.eactive.apim.portal.apps.app.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* API Key Registration Session Data
|
||||
* Holds data across multiple registration steps
|
||||
*/
|
||||
@Data
|
||||
public class ApiKeyRegistrationDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// Client ID for modification operations
|
||||
private String clientId;
|
||||
|
||||
// Request type for tracking operation type
|
||||
private String requestType; // "NEW" or "MODIFY"
|
||||
|
||||
// Existing app icon file ID (for modifications)
|
||||
private String appIconFileId;
|
||||
|
||||
// Step 1: Basic Information
|
||||
// Transient field for file upload (not serialized in session)
|
||||
private transient MultipartFile appIcon;
|
||||
|
||||
// Persisted file data
|
||||
private String appIconFileName;
|
||||
private byte[] appIconData;
|
||||
private String appIconContentType;
|
||||
|
||||
@NotBlank(message = "앱 이름을 입력해주세요.")
|
||||
@Size(max = 100, message = "앱 이름은 100자를 초과할 수 없습니다.")
|
||||
private String appName;
|
||||
|
||||
@NotBlank(message = "앱 설명을 입력해주세요.")
|
||||
@Size(max = 500, message = "앱 설명은 500자를 초과할 수 없습니다.")
|
||||
private String appDescription;
|
||||
|
||||
// Optional field - allows empty string or valid URL with http(s):// protocol
|
||||
@Pattern(regexp = "^$|^https?://.*", message = "올바른 URL 형식이 아닙니다.")
|
||||
private String callbackUrl;
|
||||
|
||||
private List<String> ipWhitelist = new ArrayList<>();
|
||||
|
||||
// Step 2: API Selection
|
||||
private List<String> selectedApis = new ArrayList<>();
|
||||
|
||||
// Helper method to add IP to whitelist
|
||||
public void addIpAddress(String ip) {
|
||||
if (ip != null && !ip.trim().isEmpty() && !ipWhitelist.contains(ip)) {
|
||||
ipWhitelist.add(ip);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to set IP whitelist from comma-separated string
|
||||
public void setIpWhitelistFromString(String ipString) {
|
||||
ipWhitelist.clear();
|
||||
if (ipString != null && !ipString.trim().isEmpty()) {
|
||||
String[] ips = ipString.split(",");
|
||||
for (String ip : ips) {
|
||||
String trimmedIp = ip.trim();
|
||||
if (!trimmedIp.isEmpty()) {
|
||||
ipWhitelist.add(trimmedIp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to get IP whitelist as comma-separated string
|
||||
public String getIpWhitelistAsString() {
|
||||
return String.join(",", ipWhitelist);
|
||||
}
|
||||
|
||||
// Helper method to get app icon as base64 data URI for image display
|
||||
public String getAppIconBase64() {
|
||||
if (appIconData == null || appIconData.length == 0) {
|
||||
return null;
|
||||
}
|
||||
String base64Data = Base64.getEncoder().encodeToString(appIconData);
|
||||
String contentType = appIconContentType != null ? appIconContentType : "image/png";
|
||||
return "data:" + contentType + ";base64," + base64Data;
|
||||
}
|
||||
|
||||
// Validation helpers
|
||||
public boolean isStep1Complete() {
|
||||
// callbackUrl is optional, so we only check required fields
|
||||
// For modifications, appIconFileId indicates an existing icon
|
||||
boolean hasIcon = (appIconData != null && appIconData.length > 0)
|
||||
|| (appIconFileId != null && !appIconFileId.trim().isEmpty());
|
||||
return hasIcon &&
|
||||
appName != null && !appName.trim().isEmpty() &&
|
||||
appDescription != null && !appDescription.trim().isEmpty();
|
||||
}
|
||||
|
||||
public boolean isStep2Complete() {
|
||||
return selectedApis != null && !selectedApis.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
return isStep1Complete() && isStep2Complete();
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,14 @@ public class AppRequestDTO {
|
||||
|
||||
private String reason;
|
||||
|
||||
private String appDescription;
|
||||
|
||||
private String callbackUrl;
|
||||
|
||||
private String ipWhitelist;
|
||||
|
||||
private String appIconFileId;
|
||||
|
||||
private LocalDateTime createdDate;
|
||||
|
||||
private String prevApiList;
|
||||
|
||||
@@ -45,6 +45,10 @@ public class ClientDTO {
|
||||
|
||||
private String appstatus;
|
||||
|
||||
private String appDescription;
|
||||
|
||||
private String appIconFileId;
|
||||
|
||||
private List<ApiServiceApiDTO> apiList = new ArrayList<>();
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.eactive.apim.portal.apps.app.mapper;
|
||||
import com.eactive.apim.portal.apprequest.entity.AppRequest;
|
||||
import com.eactive.apim.portal.apps.apiservice.mapper.ApiServiceApiMapper;
|
||||
import com.eactive.apim.portal.apps.app.dto.AppRequestDTO;
|
||||
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
|
||||
import com.eactive.apim.portal.apps.approval.mapper.ApprovalMapper;
|
||||
import com.eactive.apim.portal.apps.user.mapper.PortalOrgMapper;
|
||||
import com.eactive.eai.data.mapper.BaseMapperConfig;
|
||||
@@ -13,4 +14,53 @@ import org.mapstruct.Mapper;
|
||||
@Mapper(config = BaseMapperConfig.class, uses = {PortalOrgMapper.class, ApiServiceApiMapper.class, ApprovalMapper.class})
|
||||
public interface AppRequestMapper extends GenericMapper<AppRequestDTO, AppRequest> {
|
||||
|
||||
/**
|
||||
* AppRequest를 ClientDTO로 간단하게 매핑합니다.
|
||||
* 뷰에서 AppRequest를 ClientDTO와 동일한 형식으로 표시하기 위해 사용됩니다.
|
||||
*
|
||||
* @param appRequest 앱 요청 엔티티
|
||||
* @return ClientDTO 표시용 DTO
|
||||
*/
|
||||
default ClientDTO mapToClientDTO(AppRequest appRequest) {
|
||||
if (appRequest == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClientDTO dto = new ClientDTO();
|
||||
|
||||
// 기본 식별 정보
|
||||
dto.setClientid(appRequest.getId()); // AppRequest ID를 임시 client ID로 사용
|
||||
dto.setClientname(appRequest.getClientName());
|
||||
|
||||
// 앱 상세 정보
|
||||
dto.setAppDescription(appRequest.getAppDescription());
|
||||
dto.setAppIconFileId(appRequest.getAppIconFileId());
|
||||
|
||||
// 네트워크 설정
|
||||
dto.setRedirecturi(appRequest.getCallbackUrl());
|
||||
dto.setAllowedips(appRequest.getIpWhitelist());
|
||||
|
||||
// 상태 정보 (ApprovalStatus → appstatus 매핑)
|
||||
if (appRequest.getStatus() != null) {
|
||||
switch (appRequest.getStatus()) {
|
||||
case APPROVED:
|
||||
dto.setAppstatus("1"); // 승인됨 = 활성화
|
||||
break;
|
||||
case DENIED:
|
||||
dto.setAppstatus("0"); // 거부됨 = 비활성화
|
||||
break;
|
||||
default:
|
||||
dto.setAppstatus("2"); // 대기중/진행중 = 보류
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 수정 날짜 (createdDate를 modifiedon으로 사용)
|
||||
dto.setModifiedon(appRequest.getCreatedDate());
|
||||
|
||||
// API 목록은 별도 처리 필요 (콤마로 구분된 문자열 → List<ApiServiceApiDTO>)
|
||||
// 필요시 컨트롤러에서 추가 처리
|
||||
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ import com.eactive.apim.portal.apprequest.entity.AppRequest;
|
||||
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
|
||||
import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
|
||||
import com.eactive.apim.portal.approval.entity.Approval;
|
||||
import com.eactive.apim.portal.approval.statemachine.ProcessingState;
|
||||
import com.eactive.apim.portal.approval.statemachine.RequestedState;
|
||||
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
|
||||
import com.eactive.apim.portal.apps.apis.service.ApiService;
|
||||
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
|
||||
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
|
||||
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
|
||||
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
|
||||
import com.eactive.apim.portal.apps.app.dto.ApiKeyRegistrationDTO;
|
||||
import com.eactive.apim.portal.apps.app.dto.AppRequestDTO;
|
||||
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
|
||||
import com.eactive.apim.portal.apps.app.mapper.AppRequestMapper;
|
||||
@@ -21,16 +23,24 @@ import com.eactive.apim.portal.apps.approval.service.ApprovalService;
|
||||
import com.eactive.apim.portal.apps.user.dto.PortalOrgDTO;
|
||||
import com.eactive.apim.portal.apps.user.mapper.PortalOrgMapper;
|
||||
import com.eactive.apim.portal.common.exception.NotFoundException;
|
||||
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
|
||||
import com.eactive.apim.portal.common.util.ApiServiceHelper;
|
||||
import com.eactive.apim.portal.file.entity.FileInfo;
|
||||
import com.eactive.apim.portal.file.service.FileService;
|
||||
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.transaction.Transactional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@@ -47,53 +57,64 @@ public class AppServiceFacade {
|
||||
private final AppRequestMapper appRequestMapper;
|
||||
private final PortalOrgMapper portalOrgMapper;
|
||||
private final ApiServiceHelper apiServiceHelper;
|
||||
private final FileService fileService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
public Page<ClientDTO> getApiKeyPageForServer(Pageable pageable, PortalOrg portalOrg, String server) {
|
||||
Page<Credential> clients = credentialRepository.findAllByOrgidAndServer(pageable, portalOrg.getId(), server);
|
||||
return clients.map(credentialMapper::toVo);
|
||||
}
|
||||
public List<ClientDTO> getApikeyList(PortalOrg portalOrg) {
|
||||
|
||||
public List<ClientDTO> getApiKeyList(PortalOrg portalOrg, String server) {
|
||||
List<Credential> clients = credentialRepository.findAllByOrgidAndServer(portalOrg.getId(), server);
|
||||
List<Credential> clients = credentialRepository.findAllByOrgid(portalOrg.getId());
|
||||
return clients.stream().map(credentialMapper::toVo).collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
public ClientDTO getApiKey(String orgid, String clientId, String server) {
|
||||
return credentialRepository.findByClientidAndOrgidAndServer(clientId, orgid, server).map(credentialMapper::toVo).orElse(null);
|
||||
public List<AppRequest> getPendingApiKeyList(PortalOrg portalOrg) {
|
||||
List<AppRequest> appRequests = appRequestRepository.findAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(portalOrg, Arrays.asList(AppRequestType.NEW, AppRequestType.MODIFY, AppRequestType.DELETE),
|
||||
Arrays.asList(new ProcessingState(), new RequestedState()));
|
||||
|
||||
return appRequests;
|
||||
}
|
||||
|
||||
public ClientDTO getApiKey(String orgid, String clientId) {
|
||||
return credentialRepository.findByClientidAndOrgid(clientId, orgid).map(credentialMapper::toVo).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* AppRequest ID와 조직 정보로 AppRequest를 조회합니다.
|
||||
* API 그룹 및 API 목록 정보를 함께 처리하여 반환합니다.
|
||||
*
|
||||
* @param id AppRequest ID
|
||||
* @param portalOrg 포털 조직
|
||||
* @return AppRequest DTO (없으면 null)
|
||||
*/
|
||||
public AppRequestDTO getAppRequestById(String id, PortalOrg portalOrg) {
|
||||
return getApiRequestByIdAndPortalOrg(id, portalOrg);
|
||||
}
|
||||
|
||||
|
||||
public Page<AppRequestDTO> getApiRequestHistory(Pageable pageable, PortalOrgDTO portalOrg) {
|
||||
return appRequestRepository.findAllByOrgAndTypeIsIn(pageable, portalOrgMapper.toEntity(portalOrg), Arrays.asList(AppRequestType.NEW,AppRequestType.MODIFY, AppRequestType.DELETE)).map(appRequestMapper::toVo);
|
||||
}
|
||||
|
||||
public Page<AppRequestDTO> getApiRequestProdHistory(Pageable pageable, PortalOrgDTO portalOrg) {
|
||||
return appRequestRepository.findAllByOrgAndTypeIsIn(pageable, portalOrgMapper.toEntity(portalOrg), Arrays.asList(AppRequestType.PROD_NEW,AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE)).map(appRequestMapper::toVo);
|
||||
return appRequestRepository.findAllByOrgAndTypeIsIn(pageable, portalOrgMapper.toEntity(portalOrg), Arrays.asList(AppRequestType.NEW, AppRequestType.MODIFY, AppRequestType.DELETE)).map(appRequestMapper::toVo);
|
||||
}
|
||||
|
||||
public AppRequestDTO createAppRequest(AppRequestDTO appRequest) {
|
||||
|
||||
setApiGroupsFromApiList(appRequest);
|
||||
AppRequest request = appRequestRepository.save(appRequestMapper.toEntity(appRequest));
|
||||
|
||||
if (request.getType() != null && (request.getType().equals(AppRequestType.MODIFY) || request.getType().equals(AppRequestType.DELETE) ||
|
||||
request.getType().equals(AppRequestType.PROD_MODIFY) || request.getType().equals(AppRequestType.PROD_DELETE))) {
|
||||
if (request.getType() != null && (request.getType().equals(AppRequestType.MODIFY) || request.getType().equals(AppRequestType.DELETE))) {
|
||||
|
||||
Optional<Credential> client = credentialRepository.findById(request.getClientId());
|
||||
|
||||
if (client.isPresent()) {
|
||||
request.setClientName(client.get().getClientname());
|
||||
|
||||
if(client.get().getApiList() != null) {
|
||||
if (client.get().getApiList() != null) {
|
||||
// 기존 API 목록을 저장
|
||||
String existingApiList = client.get().getApiList().stream()
|
||||
.map(ApiSpecInfo::getApiId)
|
||||
.collect(Collectors.joining(","));
|
||||
.map(ApiSpecInfo::getApiId)
|
||||
.collect(Collectors.joining(","));
|
||||
request.setPrevApiList(existingApiList);
|
||||
}
|
||||
|
||||
if(AppRequestType.DELETE.equals(request.getType()) || AppRequestType.PROD_DELETE.equals(request.getType())) {
|
||||
if (AppRequestType.DELETE.equals(request.getType())) {
|
||||
request.setApiList(request.getPrevApiList());
|
||||
}
|
||||
} else {
|
||||
@@ -108,22 +129,6 @@ public class AppServiceFacade {
|
||||
return appRequestMapper.toVo(request);
|
||||
}
|
||||
|
||||
private void setApiGroupsFromApiList(AppRequestDTO appRequest) {
|
||||
if (StringUtils.isEmpty(appRequest.getApiList())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> uniqueGroupIds = Arrays.stream(appRequest.getApiList().split(","))
|
||||
.map(apiId -> apiServiceService.findApiServiceByApiId(apiId))
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(apiService -> apiService.getApiGroupApiList().stream())
|
||||
.map(ApiServiceApiDTO::getApiGroupId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
appRequest.setApiGroupList(String.join(",", uniqueGroupIds));
|
||||
}
|
||||
|
||||
|
||||
public void beginApproval(String approvalId) {
|
||||
approvalService.beginApproval(approvalId);
|
||||
}
|
||||
@@ -132,27 +137,11 @@ public class AppServiceFacade {
|
||||
appRequestRepository.findByIdAndOrg(id, portalOrg).ifPresent(approvalService::cancelAppApproval);
|
||||
}
|
||||
|
||||
public List<AppRequestDTO> getProdApiRequestHistory(String clientid) {
|
||||
return appRequestRepository.findAllByClientIdsContainsAndTypeIsIn(clientid,Arrays.asList(AppRequestType.PROD_NEW,AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE)).stream().map(appRequestMapper::toVo).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
public AppRequestDTO getApiRequestProdByIdAndPortalOrg(String id, PortalOrg portalOrg) {
|
||||
AppRequestDTO appRequest = appRequestRepository.findByIdAndOrgAndTypeIsIn(id, portalOrg,
|
||||
Arrays.asList(AppRequestType.PROD_NEW,AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE)).map(this::convertToAppRequestDTO).orElse(null);
|
||||
|
||||
if(appRequest != null) {
|
||||
processApiGroups(appRequest);
|
||||
processApiList(appRequest);
|
||||
}
|
||||
return appRequest;
|
||||
}
|
||||
|
||||
public AppRequestDTO getApiRequestByIdAndPortalOrg(String id, PortalOrg org) {
|
||||
AppRequestDTO appRequest = appRequestRepository.findByIdAndOrg(id, org).map(this::convertToAppRequestDTO).orElse(null);
|
||||
|
||||
if(appRequest != null) {
|
||||
processApiGroups(appRequest);
|
||||
if (appRequest != null) {
|
||||
processApiList(appRequest);
|
||||
}
|
||||
return appRequest;
|
||||
@@ -167,16 +156,6 @@ public class AppServiceFacade {
|
||||
return dto;
|
||||
}
|
||||
|
||||
private void processApiGroups(AppRequestDTO appRequest) {
|
||||
|
||||
if (!StringUtils.isEmpty(appRequest.getApiGroupList())) {
|
||||
String[] apiGroupList = appRequest.getApiGroupList().split(",");
|
||||
for (String apiGroupId : apiGroupList) {
|
||||
appRequest.getApiServiceDTOList().add(apiServiceService.getApiGroupById(apiGroupId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processApiList(AppRequestDTO appRequest) {
|
||||
|
||||
if (!StringUtils.isEmpty(appRequest.getApiList())) {
|
||||
@@ -192,4 +171,140 @@ public class AppServiceFacade {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ApiKeyRegistrationDTO를 기반으로 AppRequest를 생성합니다.
|
||||
*
|
||||
* @param registration API Key 등록 정보
|
||||
* @param portalOrg 포털 조직
|
||||
* @return 생성된 AppRequest DTO
|
||||
* @throws IOException 파일 처리 중 오류 발생 시
|
||||
*/
|
||||
public AppRequestDTO createAppRequestFromRegistration(ApiKeyRegistrationDTO registration, PortalOrg portalOrg) throws IOException {
|
||||
// 1. 앱 아이콘 파일 저장
|
||||
String appIconFileId = null;
|
||||
|
||||
if (registration.getAppIconData() != null && registration.getAppIconData().length > 0) {
|
||||
FileInfo fileInfo = fileService.createSingleFileFromBytes(registration.getAppIconFileName(), registration.getAppIconContentType(), registration.getAppIconData());
|
||||
appIconFileId = fileInfo.getFileId();
|
||||
}
|
||||
|
||||
// 2. AppRequestDTO 생성 및 필드 매핑
|
||||
AppRequestDTO appRequestDTO = new AppRequestDTO();
|
||||
appRequestDTO.setOrg(portalOrgMapper.toVo(portalOrg));
|
||||
appRequestDTO.setType(AppRequestType.NEW);
|
||||
|
||||
// 기본 정보 매핑
|
||||
appRequestDTO.setClientName(registration.getAppName());
|
||||
appRequestDTO.setAppDescription(registration.getAppDescription());
|
||||
appRequestDTO.setCallbackUrl(registration.getCallbackUrl());
|
||||
appRequestDTO.setAppIconFileId(appIconFileId);
|
||||
|
||||
// IP 화이트리스트 매핑 (List<String> → comma-separated String)
|
||||
if (registration.getIpWhitelist() != null && !registration.getIpWhitelist().isEmpty()) {
|
||||
appRequestDTO.setIpWhitelist(String.join(",", registration.getIpWhitelist()));
|
||||
}
|
||||
|
||||
// 선택된 API 목록 매핑 (List<String> → comma-separated String)
|
||||
if (registration.getSelectedApis() != null && !registration.getSelectedApis().isEmpty()) {
|
||||
appRequestDTO.setApiList(String.join(",", registration.getSelectedApis()));
|
||||
}
|
||||
|
||||
// 3. 기존 createAppRequest 메소드 호출하여 승인 프로세스 시작
|
||||
return createAppRequest(appRequestDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* ApiKeyRegistrationDTO를 기반으로 수정 AppRequest를 생성합니다.
|
||||
*
|
||||
* @param registration API Key 수정 정보
|
||||
* @param portalOrg 포털 조직
|
||||
* @return 생성된 AppRequest DTO
|
||||
* @throws IOException 파일 처리 중 오류 발생 시
|
||||
*/
|
||||
public AppRequestDTO createModifyRequestFromRegistration(ApiKeyRegistrationDTO registration, PortalOrg portalOrg) throws IOException {
|
||||
// 1. 기존 Credential 정보 조회
|
||||
ClientDTO existingClient = getApiKey(portalOrg.getId(), registration.getClientId());
|
||||
if (existingClient == null) {
|
||||
throw new NotFoundException("Client not found: " + registration.getClientId());
|
||||
}
|
||||
|
||||
// 2. 앱 아이콘 파일 저장 (변경된 경우)
|
||||
String appIconFileId = null;
|
||||
|
||||
// 새로운 아이콘 데이터가 있으면 저장
|
||||
if (registration.getAppIconData() != null && registration.getAppIconData().length > 0) {
|
||||
FileInfo fileInfo = fileService.createSingleFileFromBytes(registration.getAppIconFileName(), registration.getAppIconContentType(), registration.getAppIconData());
|
||||
appIconFileId = fileInfo.getFileId();
|
||||
}
|
||||
// 새로운 아이콘이 없으면 기존 아이콘 ID 사용 (registration에서 가져오거나 기존 클라이언트에서 가져옴)
|
||||
else if (registration.getAppIconFileId() != null) {
|
||||
appIconFileId = registration.getAppIconFileId();
|
||||
} else {
|
||||
appIconFileId = existingClient.getAppIconFileId();
|
||||
}
|
||||
|
||||
// 3. AppRequestDTO 생성 및 필드 매핑
|
||||
AppRequestDTO appRequestDTO = new AppRequestDTO();
|
||||
appRequestDTO.setOrg(portalOrgMapper.toVo(portalOrg));
|
||||
appRequestDTO.setType(AppRequestType.MODIFY);
|
||||
appRequestDTO.setClientId(registration.getClientId()); // 수정할 클라이언트 ID 설정
|
||||
|
||||
// 기본 정보 매핑
|
||||
appRequestDTO.setClientName(registration.getAppName());
|
||||
appRequestDTO.setAppDescription(registration.getAppDescription());
|
||||
appRequestDTO.setCallbackUrl(registration.getCallbackUrl());
|
||||
appRequestDTO.setAppIconFileId(appIconFileId);
|
||||
|
||||
// IP 화이트리스트 매핑 (List<String> → comma-separated String)
|
||||
if (registration.getIpWhitelist() != null && !registration.getIpWhitelist().isEmpty()) {
|
||||
appRequestDTO.setIpWhitelist(String.join(",", registration.getIpWhitelist()));
|
||||
}
|
||||
|
||||
// 선택된 API 목록 매핑 (List<String> → comma-separated String)
|
||||
if (registration.getSelectedApis() != null && !registration.getSelectedApis().isEmpty()) {
|
||||
appRequestDTO.setApiList(String.join(",", registration.getSelectedApis()));
|
||||
}
|
||||
|
||||
// 4. 기존 createAppRequest 메소드 호출하여 승인 프로세스 시작
|
||||
// createAppRequest 내부에서 MODIFY 타입인 경우 기존 API 목록을 prevApiList에 저장함
|
||||
return createAppRequest(appRequestDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자의 비밀번호를 검증합니다.
|
||||
* Client Secret 조회 등 민감한 작업 수행 시 본인 확인을 위해 사용됩니다.
|
||||
*
|
||||
* @param user 인증된 사용자 정보
|
||||
* @param password 검증할 비밀번호 (평문)
|
||||
* @return 비밀번호 일치 여부
|
||||
*/
|
||||
public boolean verifyUserPassword(PortalAuthenticatedUser user, String password) {
|
||||
if (user == null || password == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// PasswordEncoder를 사용하여 평문 비밀번호와 저장된 해시를 비교
|
||||
return passwordEncoder.matches(password, user.getPassword());
|
||||
} catch (Exception e) {
|
||||
// 비밀번호 검증 중 오류 발생 시 false 반환 (보안상 구체적인 오류 노출 방지)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API Key(Credential)를 즉시 삭제합니다.
|
||||
* 승인 프로세스 없이 바로 삭제 처리됩니다.
|
||||
*
|
||||
* @param orgId 조직 ID
|
||||
* @param clientId 삭제할 클라이언트 ID
|
||||
* @throws NotFoundException 클라이언트를 찾을 수 없는 경우
|
||||
*/
|
||||
public void deleteApp(String orgId, String clientId) {
|
||||
Credential credential = credentialRepository.findByClientidAndOrgid(clientId, orgId)
|
||||
.orElseThrow(() -> new NotFoundException("Client not found: " + clientId));
|
||||
|
||||
credentialRepository.delete(credential);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.eactive.apim.portal.apps.commission.constant;
|
||||
|
||||
/**
|
||||
* Application constants.
|
||||
*/
|
||||
public final class Constants {
|
||||
|
||||
public static final String SYSTEM_ACCOUNT = "system";
|
||||
|
||||
public static final String APIM_FOR_OBP_KEY = "x-obp-trust-system";
|
||||
|
||||
public static final String APIM_CODE_FOR_OBP_KEY = "x-obp-partnercode";
|
||||
|
||||
public static final String SMS_URL = "/api/messaging/sms";
|
||||
|
||||
public static final String COMMISSION_URL = "/api/billing/billing/findBillingForCondition";
|
||||
|
||||
public static final String COMMISSION_PRINT_URL = "/api/billing/billing/findBillingForCondition/print";
|
||||
|
||||
public static final String OBP_ORGANIZATION_URL = "/api/customer/findCustomerByBusinessManRegistrationNo/";
|
||||
|
||||
public static String LANGUAGE = "ko";
|
||||
|
||||
private Constants() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package com.eactive.apim.portal.apps.commission.controller;
|
||||
|
||||
import com.eactive.apim.portal.apps.commission.constant.Constants;
|
||||
import com.eactive.apim.portal.apps.commission.dto.CommissionDTO;
|
||||
import com.eactive.apim.portal.apps.commission.dto.CommissionSearch;
|
||||
import com.eactive.apim.portal.apps.commission.exception.ErrorUtil;
|
||||
import com.eactive.apim.portal.apps.commission.service.ExternalService;
|
||||
import com.eactive.apim.portal.common.util.SecurityUtil;
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
|
||||
@Controller
|
||||
@RequestMapping(value = "/commission")
|
||||
@Secured("ROLE_APP")
|
||||
@RequiredArgsConstructor
|
||||
public class CommissionController {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(CommissionController.class);
|
||||
|
||||
private final ExternalService externalService;
|
||||
private final PortalPropertyService portalPropertyService;
|
||||
|
||||
@GetMapping("/manage")
|
||||
public String commissionManagePage(Model model) {
|
||||
// 기본 정보 설정
|
||||
model.addAttribute("organization", SecurityUtil.getUserOrg());
|
||||
return "apps/commission/commissionManage";
|
||||
}
|
||||
|
||||
@GetMapping("/print/{year}/{month}")
|
||||
public String commissionPrintPage(
|
||||
@PathVariable("year") String year,
|
||||
@PathVariable("month") String month,
|
||||
Model model) {
|
||||
|
||||
CommissionDTO commissionDTO = null;
|
||||
CommissionSearch search = new CommissionSearch();
|
||||
search.setPartnerCode(SecurityUtil.getUserOrg().getOrgCode());
|
||||
search.setYear(year);
|
||||
search.setMonth(month);
|
||||
|
||||
if (!StringUtils.isEmpty(search.getPartnerCode())) {
|
||||
try {
|
||||
String obmUrl = portalPropertyService.getPortalPropertiesAsMap("Portal").getOrDefault("obp.obm.url", "");
|
||||
String url = obmUrl + Constants.COMMISSION_PRINT_URL;
|
||||
String result = externalService.getResponseHttpPost(url, toJson(search));
|
||||
HashMap<String, Object> returnObj = new ObjectMapper().readValue(result, HashMap.class);
|
||||
|
||||
if (!StringUtils.isEmpty(returnObj.get("error"))) {
|
||||
HashMap<String, Object> error = (HashMap) returnObj.get("error");
|
||||
model.addAttribute("error", error.get("message").toString());
|
||||
return "apps/commission/commissionPrint";
|
||||
}
|
||||
|
||||
if (StringUtils.pathEquals(search.getPartnerCode(), "000002-01")) {
|
||||
log.debug("Found Together : " + search.getPartnerCode());
|
||||
search.setPartnerCode("000002-02");
|
||||
String togetherResult = externalService.getResponseHttpPost(url, toJson(search));
|
||||
|
||||
HashMap togetherLendingReturnObj = new ObjectMapper().readValue(togetherResult, HashMap.class);
|
||||
log.debug("togetherLendingReturnObj : " + togetherLendingReturnObj.toString());
|
||||
if (!StringUtils.isEmpty(togetherLendingReturnObj.get("error"))) {
|
||||
HashMap error = (HashMap) togetherLendingReturnObj.get("error");
|
||||
model.addAttribute("error", error.get("message").toString());
|
||||
return "apps/commission/commissionPrint";
|
||||
}
|
||||
|
||||
HashMap<String, Object> mergeMap = mergeCommissionData(returnObj, togetherLendingReturnObj);
|
||||
result = new ObjectMapper().writeValueAsString(mergeMap);
|
||||
}
|
||||
commissionDTO = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).readValue(result, CommissionDTO.class);
|
||||
} catch (Exception e) {
|
||||
log.warn(e.getMessage(), e);
|
||||
model.addAttribute("error", "수수료 조회에 실패하였습니다.");
|
||||
return "apps/commission/commissionPrint";
|
||||
}
|
||||
}
|
||||
|
||||
// Model에 데이터 바인딩
|
||||
model.addAttribute("commission", commissionDTO);
|
||||
model.addAttribute("organization", SecurityUtil.getUserOrg().getOrgName());
|
||||
model.addAttribute("year", year);
|
||||
model.addAttribute("month", month);
|
||||
model.addAttribute("toDay", java.time.LocalDate.now().toString());
|
||||
|
||||
return "apps/commission/commissionPrint";
|
||||
}
|
||||
|
||||
// ==================== API Endpoints ====================
|
||||
|
||||
@PostMapping()
|
||||
public ResponseEntity<?> list(@RequestBody CommissionSearch search) {
|
||||
CommissionDTO commissionDTO = null;
|
||||
search.setPartnerCode(SecurityUtil.getUserOrg().getOrgCode());
|
||||
|
||||
if (!StringUtils.isEmpty(search.getPartnerCode())) {
|
||||
try {
|
||||
String obmUrl = portalPropertyService.getPortalPropertiesAsMap("Portal").getOrDefault("obp.obm.url", "");
|
||||
String url = obmUrl + Constants.COMMISSION_URL;
|
||||
String result = externalService.getResponseHttpPost(url, toJson(search));
|
||||
HashMap<String, Object> returnObj = new ObjectMapper().readValue(result, HashMap.class);
|
||||
if (!StringUtils.isEmpty(returnObj.get("error"))) {
|
||||
HashMap<String, Object> error = (HashMap) returnObj.get("error");
|
||||
return ErrorUtil.create("fail.commission.serverError", error.get("message").toString());
|
||||
}
|
||||
|
||||
if (StringUtils.pathEquals(search.getPartnerCode(), "000002-01")) {
|
||||
log.debug("Found Together : " + search.getPartnerCode());
|
||||
search.setPartnerCode("000002-02");
|
||||
String togetherResult = externalService.getResponseHttpPost(url, toJson(search));
|
||||
|
||||
HashMap<String, Object> togetherLendingReturnObj = new ObjectMapper().readValue(togetherResult, HashMap.class);
|
||||
log.debug("togetherLendingReturnObj : " + togetherLendingReturnObj.toString());
|
||||
if (!StringUtils.isEmpty(togetherLendingReturnObj.get("error"))) {
|
||||
HashMap<String, Object> error = (HashMap) togetherLendingReturnObj.get("error");
|
||||
return ErrorUtil.create("fail.commission.serverError", error.get("message").toString());
|
||||
}
|
||||
|
||||
HashMap<String, Object> mergeMap = mergeCommissionData(returnObj, togetherLendingReturnObj);
|
||||
result = new ObjectMapper().writeValueAsString(mergeMap);
|
||||
}
|
||||
commissionDTO = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).readValue(result, CommissionDTO.class);
|
||||
} catch (Exception e) {
|
||||
log.warn(e.getMessage(), e);
|
||||
return ErrorUtil.create("fail.commission.list", "수수료 조회에 실패하였습니다.");
|
||||
}
|
||||
return ResponseEntity.ok().body(commissionDTO);
|
||||
}
|
||||
return ErrorUtil.create("fail.commission.needPartnerCode");
|
||||
}
|
||||
|
||||
private String toJson(CommissionSearch search) {
|
||||
try {
|
||||
Map<String, String> jsonMap = new HashMap<>();
|
||||
jsonMap.put("partnerCode", search.getPartnerCode());
|
||||
jsonMap.put("targetOnMonth", search.getYear() + search.getMonth());
|
||||
|
||||
return new ObjectMapper().writeValueAsString(jsonMap);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to serialize CommissionSearch to JSON", e);
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely parse double value from HashMap
|
||||
* Handles both numeric types and string representations
|
||||
*/
|
||||
private double safeGetDouble(HashMap<String, Object> map, String key) {
|
||||
Object value = map.get(key);
|
||||
if (value == null) {
|
||||
return 0.0;
|
||||
}
|
||||
try {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).doubleValue();
|
||||
} else if (value instanceof String) {
|
||||
return Double.parseDouble((String) value);
|
||||
}
|
||||
return 0.0;
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Failed to parse double value for key: {} with value: {}", key, value);
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
public HashMap<String, Object> mergeCommissionData(HashMap<String, Object> o1, HashMap<String, Object> o2) {
|
||||
|
||||
HashMap<String, Object> mergeMap = new HashMap<String, Object>();
|
||||
|
||||
String o1Status = MapUtils.getString(o1, "status");
|
||||
String o2Status = MapUtils.getString(o2, "status");
|
||||
String resultStatus = null;
|
||||
if (Integer.parseInt(o1Status) <= Integer.parseInt(o2Status)) {
|
||||
resultStatus = o1Status;
|
||||
} else {
|
||||
resultStatus = o2Status;
|
||||
}
|
||||
double apiTotalValueSum = safeGetDouble(o1, "apiTotalValueSum") + safeGetDouble(o2, "apiTotalValueSum");
|
||||
double apiSum = safeGetDouble(o1, "apiSum") + safeGetDouble(o2, "apiSum");
|
||||
double apiTotalValueSum2 = safeGetDouble(o1, "apiTotalValueSum2") + safeGetDouble(o2, "apiTotalValueSum2");
|
||||
double apiSum2 = safeGetDouble(o1, "apiSum2") + safeGetDouble(o2, "apiSum2");
|
||||
double productTotalValueCountSum = safeGetDouble(o1, "productTotalValueCountSum") + safeGetDouble(o2, "productTotalValueCountSum");
|
||||
double productTotalValueAmountSum = safeGetDouble(o1, "productTotalValueAmountSum") + safeGetDouble(o2, "productTotalValueAmountSum");
|
||||
double productSum = safeGetDouble(o1, "productSum") + safeGetDouble(o2, "productSum");
|
||||
double productTotalValueCountSum2 = safeGetDouble(o1, "productTotalValueCountSum2") + safeGetDouble(o2, "productTotalValueCountSum2");
|
||||
double productTotalValueAmountSum2 = safeGetDouble(o1, "productTotalValueAmountSum2") + safeGetDouble(o2, "productTotalValueAmountSum2");
|
||||
double productSum2 = safeGetDouble(o1, "productSum2") + safeGetDouble(o2, "productSum2");
|
||||
double totalValueCountSum = safeGetDouble(o1, "totalValueCountSum") + safeGetDouble(o2, "totalValueCountSum");
|
||||
double totalValueCountSum2 = safeGetDouble(o1, "totalValueCountSum2") + safeGetDouble(o2, "totalValueCountSum2");
|
||||
double totalValueAmountSum = safeGetDouble(o1, "totalValueAmountSum") + safeGetDouble(o2, "totalValueAmountSum");
|
||||
double totalValueAmountSum2 = safeGetDouble(o1, "totalValueAmountSum2") + safeGetDouble(o2, "totalValueAmountSum2");
|
||||
double sum = safeGetDouble(o1, "sum") + safeGetDouble(o2, "sum");
|
||||
double sum2 = safeGetDouble(o1, "sum2") + safeGetDouble(o2, "sum2");
|
||||
|
||||
List<Map> o1List = (ArrayList) o1.get("list");
|
||||
List<Map> o2pList = (ArrayList) o2.get("list");
|
||||
o1List.addAll(o2pList);
|
||||
|
||||
mergeMap.put("status", resultStatus);
|
||||
mergeMap.put("apiTotalValueSum", String.format("%.2f", apiTotalValueSum));
|
||||
mergeMap.put("apiSum", String.format("%.2f", apiSum));
|
||||
mergeMap.put("apiTotalValueSum2", String.format("%.2f", apiTotalValueSum2));
|
||||
mergeMap.put("apiSum2", String.format("%.2f", apiSum2));
|
||||
mergeMap.put("productTotalValueCountSum", String.format("%.2f", productTotalValueCountSum));
|
||||
mergeMap.put("productTotalValueAmountSum", String.format("%.2f", productTotalValueAmountSum));
|
||||
mergeMap.put("productSum", String.format("%.2f", productSum));
|
||||
mergeMap.put("productTotalValueCountSum2", String.format("%.2f", productTotalValueCountSum2));
|
||||
mergeMap.put("productTotalValueAmountSum2", String.format("%.2f", productTotalValueAmountSum2));
|
||||
mergeMap.put("productSum2", String.format("%.2f", productSum2));
|
||||
mergeMap.put("totalValueCountSum", String.format("%.2f", totalValueCountSum));
|
||||
mergeMap.put("totalValueCountSum2", String.format("%.2f", totalValueCountSum2));
|
||||
mergeMap.put("totalValueAmountSum", String.format("%.2f", totalValueAmountSum));
|
||||
mergeMap.put("totalValueAmountSum2", String.format("%.2f", totalValueAmountSum2));
|
||||
mergeMap.put("sum", String.format("%.2f", sum));
|
||||
mergeMap.put("sum2", String.format("%.2f", sum2));
|
||||
mergeMap.put("list", o1List);
|
||||
|
||||
return mergeMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.eactive.apim.portal.apps.commission.dto;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CommissionDTO {
|
||||
|
||||
private String status;
|
||||
private String apiTotalValueSum;
|
||||
private String apiSum;
|
||||
private String apiTotalValueSum2;
|
||||
private String apiSum2;
|
||||
private String productTotalValueCountSum;
|
||||
private String productTotalValueAmountSum;
|
||||
private String productSum;
|
||||
private String productTotalValueCountSum2;
|
||||
private String productTotalValueAmountSum2;
|
||||
private String productSum2;
|
||||
private String totalValueCountSum;
|
||||
private String totalValueCountSum2;
|
||||
private String totalValueAmountSum;
|
||||
private String totalValueAmountSum2;
|
||||
private String sum;
|
||||
private String sum2;
|
||||
private List<CommissionList> list;
|
||||
private String documentFormatNo;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.eactive.apim.portal.apps.commission.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Created by jskim on 17. 01. 26.
|
||||
*
|
||||
* @author jskim
|
||||
*/
|
||||
@Data
|
||||
public class CommissionList {
|
||||
|
||||
private String paymentTargetName;
|
||||
private String paymentTargetType;
|
||||
private String value;
|
||||
private String minimumAmount;
|
||||
private String maximumAmount;
|
||||
private String totalCount;
|
||||
private String totalCount2;
|
||||
private String totalValue;
|
||||
private String totalValue2;
|
||||
private String minimumAmount2;
|
||||
private String maximumAmount2;
|
||||
private String paymentBaseName;
|
||||
private String paymentTimingName;
|
||||
private String valueDivisionName;
|
||||
private String sum;
|
||||
private String sum2;
|
||||
private String detailFeeTypeName;
|
||||
private String value2;
|
||||
private String changeReason2;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.eactive.apim.portal.apps.commission.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CommissionSearch {
|
||||
|
||||
@ApiModelProperty(value = "대상년")
|
||||
private String year;
|
||||
|
||||
@ApiModelProperty(value = "대상월")
|
||||
private String month;
|
||||
|
||||
private String partnerCode;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.eactive.apim.portal.apps.commission.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
/**
|
||||
* Created by ybsong on 16. 12. 7.
|
||||
*
|
||||
* @author ybsong
|
||||
*/
|
||||
public class ErrorUtil {
|
||||
public static ResponseEntity<ErrorVM> create(HttpStatus status, String message, String description) {
|
||||
return new ResponseEntity<>(new ErrorVM(message, description), status);
|
||||
}
|
||||
|
||||
public static ResponseEntity<ErrorVM> create(HttpStatus status, String message) {
|
||||
return create(status, message, null);
|
||||
}
|
||||
|
||||
public static ResponseEntity<ErrorVM> create(String message, String description) {
|
||||
return create(HttpStatus.BAD_REQUEST, message, description);
|
||||
}
|
||||
|
||||
public static ResponseEntity<ErrorVM> create(String message) {
|
||||
return create(message, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.eactive.apim.portal.apps.commission.exception;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* View Model for transferring error message with a list of field errors.
|
||||
*/
|
||||
@Data
|
||||
public class ErrorVM implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String message;
|
||||
private final String description;
|
||||
|
||||
private List<FieldErrorVM> fieldErrors;
|
||||
|
||||
public ErrorVM(String message) {
|
||||
this(message, null);
|
||||
}
|
||||
|
||||
public ErrorVM(String message, String description) {
|
||||
this.message = message;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public ErrorVM(String message, String description, List<FieldErrorVM> fieldErrors) {
|
||||
this.message = message;
|
||||
this.description = description;
|
||||
this.fieldErrors = fieldErrors;
|
||||
}
|
||||
|
||||
public void add(String objectName, String field, String message) {
|
||||
if (fieldErrors == null) {
|
||||
fieldErrors = new ArrayList<>();
|
||||
}
|
||||
fieldErrors.add(new FieldErrorVM(objectName, field, message));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.eactive.apim.portal.apps.commission.exception;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class FieldErrorVM implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String objectName;
|
||||
|
||||
private final String field;
|
||||
|
||||
private final String message;
|
||||
|
||||
public FieldErrorVM(String dto, String field, String message) {
|
||||
this.objectName = dto;
|
||||
this.field = field;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getObjectName() {
|
||||
return objectName;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.eactive.apim.portal.apps.commission.service;
|
||||
|
||||
import com.eactive.apim.portal.apps.commission.constant.Constants;
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* 외부 API 호출 서비스
|
||||
* RestTemplate 기반으로 외부 시스템과 HTTP 통신을 수행합니다.
|
||||
*
|
||||
* @author ybsong
|
||||
* @since 2017-03-02
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
public class ExternalService {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(ExternalService.class);
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final PortalPropertyService portalPropertyService;
|
||||
|
||||
/**
|
||||
* HTTP POST 요청을 통해 외부 API를 호출하고 응답을 받습니다.
|
||||
*
|
||||
* @param url 호출할 API URL
|
||||
* @param payload 요청 본문 (JSON 문자열)
|
||||
* @return API 응답 본문 (문자열), 오류 발생 시 null
|
||||
*/
|
||||
public String getResponseHttpPost(String url, String payload) {
|
||||
try {
|
||||
|
||||
Map<String, String> portalProperties = portalPropertyService.getPortalPropertiesAsMap("Portal");
|
||||
|
||||
// HTTP 헤더 설정
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set(Constants.APIM_FOR_OBP_KEY, portalProperties.getOrDefault("obp.api_key","APIMPT-0002-QVBJTVBULTAwMDI="));
|
||||
headers.set(Constants.APIM_CODE_FOR_OBP_KEY, portalProperties.getOrDefault("obp.partner_code","100001-01"));
|
||||
|
||||
// HTTP 요청 엔티티 생성 (헤더 + 본문)
|
||||
HttpEntity<String> requestEntity = new HttpEntity<>(payload, headers);
|
||||
|
||||
// POST 요청 실행
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
|
||||
|
||||
// 응답 본문 반환
|
||||
return response.getBody();
|
||||
|
||||
} catch (RestClientException e) {
|
||||
log.error("Failed to call external API: url={}, error={}", url, e.getMessage(), e);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error during HTTP POST: url={}", url, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.eactive.apim.portal.apps.community.partnershipapplication.controller;
|
||||
package com.eactive.apim.portal.apps.community.partnership.controller;
|
||||
|
||||
import com.eactive.apim.portal.apps.community.partnershipapplication.dto.PartnershipApplicationDTO;
|
||||
import com.eactive.apim.portal.apps.community.partnershipapplication.service.PartnershipApplicationFacade;
|
||||
import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO;
|
||||
import com.eactive.apim.portal.apps.community.partnership.service.PartnershipApplicationFacade;
|
||||
import com.eactive.apim.portal.common.util.SecurityUtil;
|
||||
import java.io.IOException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -44,7 +44,7 @@ public class PartnershipApplicationController {
|
||||
|
||||
@PostMapping
|
||||
public String createPartnershipApplication(@Valid @ModelAttribute PartnershipApplicationDTO partnershipApplicationDTO,
|
||||
BindingResult bindingResult, HttpServletRequest httpServletRequest, RedirectAttributes redirectAttributes) throws IOException {
|
||||
BindingResult bindingResult, HttpServletRequest httpServletRequest, RedirectAttributes redirectAttributes) throws IOException {
|
||||
if (bindingResult.hasErrors()) {
|
||||
return "apps/community/mainPartnershipForm";
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.eactive.apim.portal.apps.community.partnershipapplication.dto;
|
||||
package com.eactive.apim.portal.apps.community.partnership.dto;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.eactive.apim.portal.apps.community.partnershipapplication.mapper;
|
||||
package com.eactive.apim.portal.apps.community.partnership.mapper;
|
||||
|
||||
|
||||
import com.eactive.apim.portal.apps.community.partnershipapplication.dto.PartnershipApplicationDTO;
|
||||
import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO;
|
||||
import com.eactive.apim.portal.common.mapper.CommonMapper;
|
||||
import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication;
|
||||
import org.mapstruct.Mapper;
|
||||
@@ -9,8 +9,8 @@ import org.mapstruct.ReportingPolicy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Mapper(componentModel = "spring",
|
||||
uses = {CommonMapper.class},
|
||||
unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||
uses = {CommonMapper.class},
|
||||
unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||
@Component
|
||||
|
||||
public interface PartnershipApplicationMapper {
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.eactive.apim.portal.apps.community.partnershipapplication.repository;
|
||||
package com.eactive.apim.portal.apps.community.partnership.repository;
|
||||
|
||||
import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication;
|
||||
import com.eactive.eai.rms.data.EMSDataSource;
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.eactive.apim.portal.apps.community.partnershipapplication.service;
|
||||
package com.eactive.apim.portal.apps.community.partnership.service;
|
||||
|
||||
import com.eactive.apim.portal.apps.community.partnershipapplication.dto.PartnershipApplicationDTO;
|
||||
import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface PartnershipApplicationFacade {
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.eactive.apim.portal.apps.community.partnershipapplication.service;
|
||||
package com.eactive.apim.portal.apps.community.partnership.service;
|
||||
|
||||
import com.eactive.apim.portal.apps.community.partnershipapplication.dto.PartnershipApplicationDTO;
|
||||
import com.eactive.apim.portal.apps.community.partnershipapplication.mapper.PartnershipApplicationMapper;
|
||||
import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO;
|
||||
import com.eactive.apim.portal.apps.community.partnership.mapper.PartnershipApplicationMapper;
|
||||
import com.eactive.apim.portal.file.entity.FileInfo;
|
||||
import com.eactive.apim.portal.file.service.FileService;
|
||||
import com.eactive.apim.portal.file.service.FileTypeContext;
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.eactive.apim.portal.apps.community.partnershipapplication.service;
|
||||
package com.eactive.apim.portal.apps.community.partnership.service;
|
||||
|
||||
import com.eactive.apim.portal.apps.community.partnershipapplication.repository.PartnershipApplicationRepository;
|
||||
import com.eactive.apim.portal.apps.community.partnership.repository.PartnershipApplicationRepository;
|
||||
import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -4,7 +4,6 @@ import com.eactive.apim.portal.apps.dashboard.service.DashboardService;
|
||||
import com.eactive.apim.portal.common.pagerouter.PageHandler;
|
||||
import com.eactive.apim.portal.common.util.SecurityUtil;
|
||||
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -22,9 +21,7 @@ public class DashboardHandler implements PageHandler {
|
||||
if (SecurityUtil.getPortalAuthenticatedUser() != null && SecurityUtil.getPortalAuthenticatedUser().getPortalOrg() != null) {
|
||||
PortalOrg org = SecurityUtil.getPortalAuthenticatedUser().getPortalOrg();
|
||||
model.addAttribute("pendingKeyCount", dashboardService.getPendingApiKeyRequestCount(org));
|
||||
model.addAttribute("testKeyCount", dashboardService.getTestApiKeyCount(org));
|
||||
model.addAttribute("pendingProdCount", dashboardService.getProdApiKeyRequestCount(org));
|
||||
model.addAttribute("prodKeyCount", dashboardService.getProdApiKeyCount(org));
|
||||
model.addAttribute("testKeyCount", dashboardService.getApiKeyCount(org));
|
||||
|
||||
model.addAttribute("totalApi", dashboardService.getTotalApi(org));
|
||||
model.addAttribute("dailyTotal", dashboardService.getDailyTotalApiUsage(org));
|
||||
|
||||
@@ -5,24 +5,18 @@ import com.eactive.apim.portal.app.entity.Credential;
|
||||
import com.eactive.apim.portal.app.repository.CredentialRepository;
|
||||
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
|
||||
import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
|
||||
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
|
||||
import com.eactive.apim.portal.approval.statemachine.ProcessingState;
|
||||
import com.eactive.apim.portal.approval.statemachine.RequestedState;
|
||||
import com.eactive.apim.portal.apps.approval.service.ApprovalService;
|
||||
import com.eactive.apim.portal.apps.dashboard.ApiDailyStatistics;
|
||||
import com.eactive.apim.portal.apps.dashboard.repository.PortalStatisticsRepository;
|
||||
import com.eactive.apim.portal.apps.dashboard.repository.PortalStatisticsRepository.TopApiUsage;
|
||||
import com.eactive.apim.portal.common.util.DateUtil;
|
||||
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -39,15 +33,12 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Service
|
||||
public class DashboardService {
|
||||
|
||||
private final static String PROD_SERVER = "PROD";
|
||||
private final static String STG_SERVER = "STG";
|
||||
|
||||
private final AppRequestRepository appRequestRepository;
|
||||
private final CredentialRepository credentialRepository;
|
||||
private final PortalStatisticsRepository portalStatisticsRepository;
|
||||
|
||||
public int getTestApiKeyCount(PortalOrg org) {
|
||||
return credentialRepository.countAllByOrgidAndServer(org.getId(), STG_SERVER);
|
||||
public int getApiKeyCount(PortalOrg org) {
|
||||
return credentialRepository.countAllByOrgid(org.getId());
|
||||
}
|
||||
|
||||
public int getPendingApiKeyRequestCount(PortalOrg org) {
|
||||
@@ -55,18 +46,9 @@ public class DashboardService {
|
||||
Arrays.asList(new ProcessingState(), new RequestedState()));
|
||||
}
|
||||
|
||||
public int getProdApiKeyRequestCount(PortalOrg org) {
|
||||
return appRequestRepository.countAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(org, Arrays.asList(AppRequestType.PROD_NEW, AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE),
|
||||
Arrays.asList(new ProcessingState(), new RequestedState()));
|
||||
}
|
||||
|
||||
public int getProdApiKeyCount(PortalOrg org) {
|
||||
return credentialRepository.findAllByOrgidAndServer(org.getId(), PROD_SERVER).size();
|
||||
}
|
||||
|
||||
public int getTotalApi(PortalOrg org) {
|
||||
|
||||
List<Credential> credentials = credentialRepository.findAllByOrgidAndServer(org.getId(), PROD_SERVER);
|
||||
List<Credential> credentials = credentialRepository.findAllByOrgid(org.getId());
|
||||
return (int) credentials.stream()
|
||||
.flatMap(credential -> credential.getApiList().stream())
|
||||
.map(ApiSpecInfo::getApiId)
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.eactive.apim.portal.apps.community.notice.service.PortalNoticeFacade;
|
||||
import com.eactive.apim.portal.apps.main.service.MainApiFacade;
|
||||
import com.eactive.apim.portal.common.util.SecurityUtil;
|
||||
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -26,13 +27,12 @@ public class IndexController {
|
||||
|
||||
private final ApiSearchFacade apiSearchFacade;
|
||||
private final MainApiFacade mainApiFacade;
|
||||
private final PortalNoticeFacade portalNoticeFacade;
|
||||
private final PortalPropertyService portalPropertyService;
|
||||
|
||||
/**
|
||||
* 메인 페이지 API는 Portal Property 에 main.service.list 에 등록된 그룹을 기준으로 API를 조회함.
|
||||
* 프로퍼티에 등록되어 있어도 어드민의 그룹 표시 설정이 N이면 표시 되지 않음.
|
||||
* 어드민의 API 스펙의 표시 설정이 N이면 표시되지 않음.
|
||||
* 그룹 또는 권한이 지정된 경우, 해당사용자의 권한 또는 기관이 일치해야 표시됨.
|
||||
* 어드민의 API 스펙의 표시 설정이 N이면 표시되지 않음. 그룹 또는 권한이 지정된 경우, 해당사용자의 권한 또는 기관이 일치해야 표시됨.
|
||||
* <p>
|
||||
* 검색어는 Portal Property 또는 메인 관리 화면에 main.keyword.list 에 등록된 내용이 표시 됨.
|
||||
*
|
||||
@@ -41,23 +41,8 @@ public class IndexController {
|
||||
*/
|
||||
@GetMapping("/")
|
||||
public String index(Model model) {
|
||||
// Step 1: Fetch and sort API services
|
||||
List<ApiServiceDTO> apiServices = getSortedApiServices(null);
|
||||
|
||||
// Step 2: Create a map of API ID to ApiServiceDTO for icon mapping
|
||||
Map<String, ApiServiceDTO> mainIconsMap = createMainIconsMap(apiServices);
|
||||
|
||||
// Step 3: Merge all API lists from apiServices
|
||||
List<ApiServiceApiDTO> allApis = getAllApis(apiServices);
|
||||
|
||||
// Step 4: Enrich API spec info DTOs with main icon and service info
|
||||
List<ApiSpecInfoDto> apiSpecInfoDtos = enrichApiSpecInfo(apiServices, allApis, mainIconsMap);
|
||||
|
||||
// Step 5: Calculate total API count
|
||||
int totalApiCount = calculateTotalApiCount(apiServices);
|
||||
|
||||
// Step 6: Fetch portal notices and hashtags
|
||||
List<PortalNoticeDTO> portalNotices = portalNoticeFacade.getLatestNotices();
|
||||
List<ApiServiceDTO> services = mainApiFacade.getOpenApiServices();
|
||||
List<String> hashTags = mainApiFacade.getHashTags();
|
||||
|
||||
ApiGroupSearch search = new ApiGroupSearch();
|
||||
@@ -65,107 +50,19 @@ public class IndexController {
|
||||
|
||||
int selectedApiCount = (int) searchResult.get("selectedApiCount");
|
||||
|
||||
// 서비스 이용 수
|
||||
int serviceCount = ((List<?>) searchResult.get("services")).size();
|
||||
|
||||
// 승인된 기업 건수
|
||||
int approvedOrgCount = mainApiFacade.getApprovedOrgCount();
|
||||
|
||||
// Step 7: Add attributes to model
|
||||
addAttributesToModel(model, apiServices, apiSearchFacade.sortApisByService(apiSpecInfoDtos, apiServices), totalApiCount, portalNotices, hashTags, serviceCount, approvedOrgCount, selectedApiCount);
|
||||
addAttributesToModel(model, services, hashTags, serviceCount, approvedOrgCount, selectedApiCount);
|
||||
|
||||
return "apps/main/index";
|
||||
}
|
||||
|
||||
private List<ApiServiceDTO> getSortedApiServices(String apiId) {
|
||||
List<ApiServiceDTO> apiServices = mainApiFacade.getOpenApiServices(apiId);
|
||||
apiServices.sort(Comparator.comparing(ApiServiceDTO::getDisplayOrder));
|
||||
return apiServices;
|
||||
}
|
||||
|
||||
private Map<String, ApiServiceDTO> createMainIconsMap(List<ApiServiceDTO> apiServices) {
|
||||
Map<String, ApiServiceDTO> mainIconsMap = new HashMap<>();
|
||||
apiServices.forEach(service ->
|
||||
service.getApiGroupApiList().forEach(api ->
|
||||
mainIconsMap.put(api.getApiId(), service)
|
||||
)
|
||||
);
|
||||
return mainIconsMap;
|
||||
}
|
||||
|
||||
private List<ApiServiceApiDTO> getAllApis(List<ApiServiceDTO> apiServices) {
|
||||
return apiServices.stream()
|
||||
.flatMap(service -> service.getApiGroupApiList().stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<ApiSpecInfoDto> enrichApiSpecInfo(List<ApiServiceDTO> services, List<ApiServiceApiDTO> allApis, Map<String, ApiServiceDTO> mainIconsMap) {
|
||||
List<String> apiIds = allApis.stream().map(ApiServiceApiDTO::getApiId).collect(Collectors.toList());
|
||||
List<ApiSpecInfoDto> apiSpecInfoDtos = mainApiFacade.getOpenApis(apiIds);
|
||||
apiSpecInfoDtos.forEach(spec -> {
|
||||
ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId());
|
||||
if (serviceDTO != null) {
|
||||
spec.setMainIcon(serviceDTO.getMainIcon());
|
||||
spec.setService(serviceDTO.getGroupName());
|
||||
}
|
||||
});
|
||||
|
||||
boolean isAuthenticated = SecurityUtil.isAuthenticated();
|
||||
String roleCode;
|
||||
String org;
|
||||
|
||||
if (isAuthenticated) {
|
||||
roleCode = SecurityUtil.getPortalAuthenticatedUser().getRoleCode().toString();
|
||||
org = SecurityUtil.getUserOrg().getId();
|
||||
} else {
|
||||
roleCode = null;
|
||||
org = null;
|
||||
}
|
||||
|
||||
List<ApiSpecInfoDto> filteredApiSpecInfoDtos = apiSpecInfoDtos.stream()
|
||||
.filter(spec -> {
|
||||
if (!isAuthenticated) {
|
||||
return spec.getDisplayOrg() == null && spec.getDisplayRoleCode() == null;
|
||||
}
|
||||
boolean orgMatch = false;
|
||||
if (org != null) {
|
||||
orgMatch = spec.getDisplayOrg() == null || spec.getDisplayOrg().isEmpty() || spec.getDisplayOrg().contains(org);
|
||||
}
|
||||
boolean roleMatch = spec.getDisplayRoleCode() == null || spec.getDisplayRoleCode().isEmpty() || spec.getDisplayRoleCode().contains(roleCode);
|
||||
return orgMatch || roleMatch;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Set<String> filteredApiIds = filteredApiSpecInfoDtos.stream()
|
||||
.map(ApiSpecInfoDto::getApiId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
services.forEach(service -> {
|
||||
if (service.getApiGroupApiList() != null) {
|
||||
List<ApiServiceApiDTO> filteredApiList = service.getApiGroupApiList().stream()
|
||||
.filter(api -> filteredApiIds.contains(api.getApiId()))
|
||||
.collect(Collectors.toList());
|
||||
service.setApiGroupApiList(filteredApiList);
|
||||
}
|
||||
});
|
||||
|
||||
return filteredApiSpecInfoDtos.stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private int calculateTotalApiCount(List<ApiServiceDTO> apiServices) {
|
||||
return apiServices.stream()
|
||||
.mapToInt(service -> service.getApiGroupApiList().size())
|
||||
.sum();
|
||||
}
|
||||
|
||||
private void addAttributesToModel(Model model, List<ApiServiceDTO> apiServices,
|
||||
List<ApiSpecInfoDto> apiSpecInfoDtos, int totalApiCount,
|
||||
List<PortalNoticeDTO> portalNotices, List<String> hashTags, int serviceCount, int approvedOrgCount, int selectedApiCount) {
|
||||
private void addAttributesToModel(Model model, List<ApiServiceDTO> apiServices, List<String> hashTags, int serviceCount, int approvedOrgCount, int selectedApiCount) {
|
||||
|
||||
model.addAttribute("services", apiServices);
|
||||
model.addAttribute("apis", apiSpecInfoDtos);
|
||||
model.addAttribute("totalApiCount", totalApiCount);
|
||||
model.addAttribute("portalNotices", portalNotices);
|
||||
model.addAttribute("hashtags", hashTags);
|
||||
|
||||
model.addAttribute("serviceCount", serviceCount);
|
||||
@@ -173,15 +70,4 @@ public class IndexController {
|
||||
model.addAttribute("selectedApiCount", selectedApiCount);
|
||||
}
|
||||
|
||||
@GetMapping("/api_fragments")
|
||||
public String getApiFragments(@RequestParam(value = "id", required = false) String id, Model model) {
|
||||
List<ApiServiceDTO> apiServices = getSortedApiServices(id);
|
||||
Map<String, ApiServiceDTO> mainIconsMap = createMainIconsMap(apiServices);
|
||||
List<ApiServiceApiDTO> allApis = getAllApis(apiServices);
|
||||
List<ApiSpecInfoDto> apiSpecInfoDtos = enrichApiSpecInfo(apiServices, allApis, mainIconsMap);
|
||||
model.addAttribute("apis", apiSpecInfoDtos);
|
||||
return "apps/main/apiListFragment :: apiListFragment";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -7,17 +7,13 @@ import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
|
||||
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
|
||||
import com.eactive.apim.portal.apps.user.service.PortalOrgService;
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Transactional
|
||||
@Service
|
||||
@@ -34,17 +30,13 @@ public class MainApiFacade {
|
||||
return portalOrgService.getActiveApprovedOrgCount();
|
||||
}
|
||||
|
||||
public List<ApiServiceDTO> getOpenApiServices(String serviceId) {
|
||||
public List<ApiServiceDTO> getOpenApiServices() {
|
||||
ApiGroupSearch search = new ApiGroupSearch();
|
||||
|
||||
Map<String, String> property = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY);
|
||||
String services = property.getOrDefault("main.service.list", "");
|
||||
|
||||
if (StringUtils.hasText(serviceId) && services.contains(serviceId)) {
|
||||
search.setGroupIds(Collections.singletonList(serviceId));
|
||||
} else {
|
||||
search.setGroupIds(Arrays.asList(services.split(",")));
|
||||
}
|
||||
search.setGroupIds(Arrays.asList(services.replace(" ", "").split(",")));
|
||||
|
||||
return apiServiceService.searchApiGroups(search);
|
||||
}
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
package com.eactive.apim.portal.apps.proxy;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/_proxy") // /proxy 대신 /_proxy 사용
|
||||
@Slf4j
|
||||
public class ApimProxyController {
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final ProxyProperties proxyProperties;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final MediaType JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8);
|
||||
|
||||
|
||||
public ApimProxyController(
|
||||
RestTemplateBuilder restTemplateBuilder,
|
||||
ProxyProperties proxyProperties,
|
||||
ObjectMapper objectMapper) {
|
||||
this.proxyProperties = proxyProperties;
|
||||
this.objectMapper = objectMapper;
|
||||
this.restTemplate = restTemplateBuilder
|
||||
.setConnectTimeout(Duration.ofMillis(proxyProperties.getTimeout().getConnect()))
|
||||
.setReadTimeout(Duration.ofMillis(proxyProperties.getTimeout().getRead()))
|
||||
.build();
|
||||
}
|
||||
|
||||
private ResponseEntity<String> createJsonResponse(HttpStatus status, String message) {
|
||||
ObjectNode jsonResponse = objectMapper.createObjectNode()
|
||||
.put("status", status.value())
|
||||
.put("message", message);
|
||||
return ResponseEntity
|
||||
.status(status)
|
||||
.contentType(JSON_UTF8)
|
||||
.body(jsonResponse.toString());
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/**")
|
||||
public ResponseEntity<String> proxyRequest(
|
||||
HttpServletRequest request,
|
||||
@RequestBody(required = false) String body,
|
||||
@RequestHeader HttpHeaders headers) {
|
||||
|
||||
String targetKey = headers.getFirst("target_host");
|
||||
if (targetKey == null || targetKey.trim().isEmpty()) {
|
||||
log.error("Missing or empty target_host header");
|
||||
return createJsonResponse(HttpStatus.BAD_REQUEST, "Error: target_host header is required");
|
||||
}
|
||||
|
||||
String targetUrl = proxyProperties.getTargets().get(targetKey.trim().toLowerCase());
|
||||
if (targetUrl == null) {
|
||||
log.error("Invalid target_host: {}. Available targets: {}",
|
||||
targetKey, proxyProperties.getTargets().keySet());
|
||||
return createJsonResponse(HttpStatus.BAD_REQUEST,
|
||||
"Error: Invalid target_host. Must be one of: " +
|
||||
String.join(", ", proxyProperties.getTargets().keySet()));
|
||||
}
|
||||
|
||||
try {
|
||||
String requestUrl = request.getRequestURI().replace("/_proxy", "");
|
||||
URI uri = UriComponentsBuilder
|
||||
.fromHttpUrl(targetUrl + requestUrl)
|
||||
.query(request.getQueryString())
|
||||
.build()
|
||||
.toUri();
|
||||
|
||||
HttpMethod method = HttpMethod.valueOf(request.getMethod());
|
||||
|
||||
HttpHeaders proxyHeaders = new HttpHeaders();
|
||||
headers.forEach((key, value) -> {
|
||||
if (!key.equalsIgnoreCase("host") &&
|
||||
!key.equalsIgnoreCase("target_host")) {
|
||||
proxyHeaders.addAll(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
HttpEntity<String> httpEntity = new HttpEntity<>(body, proxyHeaders);
|
||||
|
||||
log.debug("Forwarding request to: {} ({})", targetKey, uri);
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
uri,
|
||||
method,
|
||||
httpEntity,
|
||||
String.class
|
||||
);
|
||||
log.debug("Received response status: {}", response.getStatusCode());
|
||||
|
||||
// Create JSON response with the original response data
|
||||
ObjectNode jsonResponse = objectMapper.createObjectNode()
|
||||
.put("status", response.getStatusCode().value());
|
||||
|
||||
// Try to parse the response body as JSON, if it fails, include it as a string
|
||||
try {
|
||||
jsonResponse.set("data", objectMapper.readTree(response.getBody()));
|
||||
} catch (Exception e) {
|
||||
jsonResponse.put("data", response.getBody());
|
||||
}
|
||||
|
||||
return ResponseEntity
|
||||
.status(response.getStatusCode())
|
||||
.contentType(JSON_UTF8)
|
||||
.body(jsonResponse.toString());
|
||||
|
||||
} catch (HttpStatusCodeException e) {
|
||||
log.error("Target server returned error: {} for host: {}", e.getStatusCode(), targetKey);
|
||||
ObjectNode jsonResponse = objectMapper.createObjectNode()
|
||||
.put("status", e.getStatusCode().value());
|
||||
|
||||
// Try to parse the error response body as JSON, if it fails, include it as a string
|
||||
try {
|
||||
jsonResponse.set("data", objectMapper.readTree(e.getResponseBodyAsString()));
|
||||
} catch (Exception ex) {
|
||||
jsonResponse.put("data", e.getResponseBodyAsString());
|
||||
}
|
||||
|
||||
return ResponseEntity
|
||||
.status(e.getStatusCode())
|
||||
.contentType(JSON_UTF8)
|
||||
.body(jsonResponse.toString());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error during proxy request to: {}", targetKey, e);
|
||||
return createJsonResponse(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.eactive.apim.portal.apps.proxy;
|
||||
|
||||
import java.util.Map;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "proxy")
|
||||
@Getter
|
||||
@Setter
|
||||
public class ProxyProperties {
|
||||
private Map<String, String> targets;
|
||||
private Timeout timeout = new Timeout();
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Timeout {
|
||||
private int connect = 5000;
|
||||
private int read = 5000;
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ public class PortalGlobalExceptionHandler {
|
||||
log.error(ex.getMessage());
|
||||
ModelAndView modelAndView = new ModelAndView();
|
||||
modelAndView.addObject("errorMessage", ex.getMessage());
|
||||
modelAndView.setViewName("common/error/error");
|
||||
modelAndView.setViewName("error");
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public class PortalGlobalExceptionHandler {
|
||||
log.error(ex.getMessage());
|
||||
ModelAndView modelAndView = new ModelAndView();
|
||||
modelAndView.addObject("errorMessage", ex.getMessage());
|
||||
modelAndView.setViewName("common/error/error");
|
||||
modelAndView.setViewName("error");
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public class PortalGlobalExceptionHandler {
|
||||
log.error("Exception occurred: ", ex);
|
||||
ModelAndView modelAndView = new ModelAndView();
|
||||
modelAndView.addObject("errorMessage", ex.getMessage());
|
||||
modelAndView.setViewName("common/error/error");
|
||||
modelAndView.setViewName("error");
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,9 +49,11 @@ public final class SecurityUtil {
|
||||
|
||||
public static PortalOrgDTO getUserOrg() {
|
||||
PortalOrgDTO portalOrgDTO = new PortalOrgDTO();
|
||||
if (Objects.requireNonNull(getPortalAuthenticatedUser()).getPortalOrg() != null) {
|
||||
portalOrgDTO.setOrgName(getPortalAuthenticatedUser().getPortalOrg().getOrgName());
|
||||
portalOrgDTO.setId(getPortalAuthenticatedUser().getPortalOrg().getId());
|
||||
PortalAuthenticatedUser portalAuthenticatedUser = getPortalAuthenticatedUser();
|
||||
if (Objects.requireNonNull(portalAuthenticatedUser).getPortalOrg() != null) {
|
||||
portalOrgDTO.setOrgName(portalAuthenticatedUser.getPortalOrg().getOrgName());
|
||||
portalOrgDTO.setId(portalAuthenticatedUser.getPortalOrg().getId());
|
||||
portalOrgDTO.setOrgCode(portalAuthenticatedUser.getPortalOrg().getOrgCode());
|
||||
}
|
||||
|
||||
return portalOrgDTO;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.eactive.apim.portal.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* RestTemplate 설정 클래스
|
||||
* 외부 API 호출을 위한 RestTemplate Bean을 제공합니다.
|
||||
*
|
||||
* @author sungpilhyun
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
/**
|
||||
* RestTemplate Bean 생성
|
||||
*
|
||||
* @return 설정된 RestTemplate 인스턴스
|
||||
*/
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
|
||||
// 연결 타임아웃: 10초
|
||||
factory.setConnectTimeout(10000);
|
||||
|
||||
// 읽기 타임아웃: 30초
|
||||
factory.setReadTimeout(30000);
|
||||
|
||||
return new RestTemplate(factory);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
title.html=Kbank API Portal
|
||||
title.html=KJbank API Portal
|
||||
apim.route.register.file=\uCCA8\uBD80\uD30C\uC77C
|
||||
field.required=\uD544\uC218 \uC785\uB825\uD56D\uBAA9\uC785\uB2C8\uB2E4.
|
||||
companyName=\uD68C\uC0AC\uBA85
|
||||
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 270 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 859 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 712 KiB |
|
Before Width: | Height: | Size: 500 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 403 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 808 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 0C17.0751 0 22 4.92487 22 11C22 17.0751 17.0751 22 11 22C4.92487 22 0 17.0751 0 11C0 4.92487 4.92487 0 11 0Z" fill="#0049B4"/>
|
||||
<path d="M14.846 7.86287C15.0951 7.56868 15.5352 7.53184 15.8294 7.78084C16.123 8.02998 16.1592 8.47019 15.9105 8.76423L10.8626 14.7301C10.5482 15.1014 9.97571 15.1038 9.65853 14.7349L6.93685 11.5592C6.68656 11.2666 6.72002 10.8266 7.01205 10.5758C7.30461 10.3252 7.7455 10.3587 7.99642 10.651L10.2552 13.2857L14.846 7.86287Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 593 B |
|
After Width: | Height: | Size: 990 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 0C17.0751 0 22 4.92487 22 11C22 17.0751 17.0751 22 11 22C4.92487 22 0 17.0751 0 11C0 4.92487 4.92487 0 11 0ZM11 1.375C5.68426 1.375 1.375 5.68426 1.375 11C1.375 16.3157 5.68426 20.625 11 20.625C16.3157 20.625 20.625 16.3157 20.625 11C20.625 5.68426 16.3157 1.375 11 1.375Z" fill="#8C959F"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 409 B |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 676 KiB |
|
After Width: | Height: | Size: 581 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 886 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 920 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 952 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |