1 Commits

Author SHA1 Message Date
Rinjae ca44e8a967 - PortalProperty 설명 자동 채우기 로직 추가: 비어있을 때만 적용
- isBlank 유틸 메서드 추가
2026-07-03 16:55:05 +09:00
17 changed files with 15 additions and 356 deletions
@@ -23,8 +23,6 @@ public interface AppRequestRepository extends BaseRepository<AppRequest, String>
List<AppRequest> findAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List<AppRequestType> types, List<ApprovalState> approvalStates); List<AppRequest> findAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List<AppRequestType> types, List<ApprovalState> approvalStates);
List<AppRequest> findAllByOrgAndTypeIsInAndApprovalIsNull(PortalOrg org, List<AppRequestType> types);
int countAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List<AppRequestType> types, List<ApprovalState> approvalStates); int countAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List<AppRequestType> types, List<ApprovalState> approvalStates);
Optional<AppRequest> findByIdAndOrg(String id, PortalOrg org); Optional<AppRequest> findByIdAndOrg(String id, PortalOrg org);
@@ -1,66 +0,0 @@
package com.eactive.apim.portal.jpa;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import java.io.Serializable;
import java.security.SecureRandom;
/**
* UUID version 7 (time-ordered, RFC 9562) 생성기.
*
* <p>상위 48비트에 Unix epoch 밀리초 타임스탬프를 두어 생성 시각 순으로 정렬되므로,
* 무작위 v4 대비 인덱스(B-Tree) 삽입 지역성이 좋아 PK 인덱스 단편화를 줄인다.
* canonical 36자 문자열로 반환하여 기존 {@code VARCHAR2(36)} 컬럼과 호환된다.</p>
*
* <p>레이아웃(128비트): unix_ts_ms(48) | ver=0b0111(4) | rand_a(12) | var=0b10(2) | rand_b(62)</p>
*
* <p>Java 8 호환(java.util.UUID + SecureRandom). Hibernate {@link IdentifierGenerator} 구현이며
* 엔티티에서 {@code @GenericGenerator(strategy = "...UuidV7Generator")} 로 지정한다.</p>
*/
public class UuidV7Generator implements IdentifierGenerator {
private static final SecureRandom RANDOM = new SecureRandom();
@Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
return generateString();
}
/** UUID v7 을 canonical 36자 문자열로 생성한다. */
public static String generateString() {
long timestamp = System.currentTimeMillis();
byte[] value = new byte[16];
// 상위 48비트: unix epoch millis (big-endian)
value[0] = (byte) ((timestamp >>> 40) & 0xFF);
value[1] = (byte) ((timestamp >>> 32) & 0xFF);
value[2] = (byte) ((timestamp >>> 24) & 0xFF);
value[3] = (byte) ((timestamp >>> 16) & 0xFF);
value[4] = (byte) ((timestamp >>> 8) & 0xFF);
value[5] = (byte) (timestamp & 0xFF);
// 나머지 10바이트는 난수로 채운다.
byte[] rand = new byte[10];
RANDOM.nextBytes(rand);
System.arraycopy(rand, 0, value, 6, 10);
// version 7: value[6] 상위 니블을 0111 로 설정
value[6] = (byte) ((value[6] & 0x0F) | 0x70);
// variant 10xx: value[8] 상위 2비트를 10 으로 설정
value[8] = (byte) ((value[8] & 0x3F) | 0x80);
long msb = 0L;
for (int i = 0; i < 8; i++) {
msb = (msb << 8) | (value[i] & 0xFFL);
}
long lsb = 0L;
for (int i = 8; i < 16; i++) {
lsb = (lsb << 8) | (value[i] & 0xFFL);
}
return new java.util.UUID(msb, lsb).toString();
}
}
@@ -121,8 +121,5 @@ public class PortalOrg extends Auditable implements Serializable, com.eactive.ea
this.orgSectors = null; this.orgSectors = null;
this.orgIndustryType = null; this.orgIndustryType = null;
this.serviceName = null; this.serviceName = null;
this.orgDesc = null;
this.ipWhitelist = null;
this.reverseProxyPath = null;
} }
} }
@@ -4,23 +4,8 @@ import com.eactive.apim.portal.portalproperty.entity.PortalProperty;
import com.eactive.apim.portal.portalproperty.entity.PortalPropertyId; import com.eactive.apim.portal.portalproperty.entity.PortalPropertyId;
import com.eactive.eai.data.jpa.BaseRepository; import com.eactive.eai.data.jpa.BaseRepository;
import com.eactive.eai.rms.data.EMSDataSource; import com.eactive.eai.rms.data.EMSDataSource;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
@EMSDataSource @EMSDataSource
public interface PortalPropertyRepository extends BaseRepository<PortalProperty, PortalPropertyId> { public interface PortalPropertyRepository extends BaseRepository<PortalProperty, PortalPropertyId> {
/**
* (PROPERTY_GROUP_NAME, PROPERTY_NAME) 조합이 2건 이상인 중복 키를 조회한다.
* DB에 유니크 제약을 걸지 않으므로 기동 시 무결성 점검용으로 사용한다.
*
* @return {@code Object[]{propertyGroupName, propertyName, count}} 목록 (중복 없으면 빈 리스트)
*/
@Query("SELECT p.id.propertyGroupName, p.id.propertyName, COUNT(p) "
+ "FROM PortalProperty p "
+ "GROUP BY p.id.propertyGroupName, p.id.propertyName "
+ "HAVING COUNT(p) > 1")
List<Object[]> findDuplicatePropertyKeys();
} }
@@ -88,11 +88,7 @@ public class PortalUser extends Auditable implements Serializable, com.eactive.e
@Comment("로그인실패횟수") @Comment("로그인실패횟수")
private Integer loginFailureCount; private Integer loginFailureCount;
// PASSWORD_CHANGE_DATE 컬럼은 VARCHAR2(14) yyyyMMddHHmmss 문자열이다. @Column(name = "password_change_date")
// LocalDateTime 을 그대로 바인딩하면 27자로 변환되어 ORA-12899(값 초과)가 발생하므로
// 14자 문자열로 변환하는 컨버터를 적용한다.
@Column(name = "password_change_date", length = 14)
@Convert(converter = com.eactive.eai.data.converter.LocalDateTimeToStringConverter14.class)
@Comment("비밀번호변경일시") @Comment("비밀번호변경일시")
private LocalDateTime passwordChangeDate; private LocalDateTime passwordChangeDate;
@@ -20,8 +20,7 @@ public class TwoFactorAuth {
@Id @Id
@Column(name = "id", length = 36, nullable = false) @Column(name = "id", length = 36, nullable = false)
// UUID v7(타임스탬프 기반, 시간순 정렬) — PK 인덱스 삽입 지역성 개선. 기존 v4 행과 컬럼 호환. @GenericGenerator(name = "uuid-gen", strategy = "uuid2")
@GenericGenerator(name = "uuid-gen", strategy = "com.eactive.apim.portal.jpa.UuidV7Generator")
@GeneratedValue(generator = "uuid-gen", strategy = GenerationType.IDENTITY) @GeneratedValue(generator = "uuid-gen", strategy = GenerationType.IDENTITY)
private String id; private String id;
@@ -1,76 +0,0 @@
package com.eactive.apim.portal.portaluser.entity;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
import lombok.Data;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 사용자 역할(권한) 변경 감사 이력.
*
* <p>법인 관리자 위임/회수, 소속 제외 등으로 {@code PortalUser.roleCode} 가 변경될 때
* 이전 역할 → 새 역할, 변경 유형, 변경 수행자(관리자)를 이력으로 남긴다.
* {@link UserPasswordHistory} 와 동일한 저장 패턴(시퀀스 PK, 14자리 문자열 일시)을 따른다.</p>
*/
@Data
@Entity
@Table(name = "ptl_user_role_history")
public class UserRoleHistory implements Serializable, com.eactive.eai.data.Data {
private static final long serialVersionUID = 1L;
@Id
@GenericGenerator(
name = "ptl_user_role_history_seq_gen",
strategy = "com.eactive.eai.rms.data.jpa.SchemaPrefixedSequenceGenerator",
parameters = {
@Parameter(name = "sequence_name", value = "PTL_USER_ROLE_HISTORY_SEQ"),
@Parameter(name = "initial_value", value = "1"),
@Parameter(name = "increment_size", value = "1")
}
)
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "ptl_user_role_history_seq_gen"
)
@Column(name = "id", columnDefinition = "number(19)")
private Long id;
@Column(name = "user_id", length = 255, nullable = false)
@Comment("대상 사용자 ID(loginId)")
private String userId;
@Column(name = "before_role", length = 30)
@Comment("변경 전 역할")
private String beforeRole;
@Column(name = "after_role", length = 30, nullable = false)
@Comment("변경 후 역할")
private String afterRole;
@Column(name = "change_type", length = 30, nullable = false)
@Comment("변경 유형(MANAGER_ASSIGN/MANAGER_REVOKE/ORG_REMOVE 등)")
private String changeType;
@Column(name = "changed_by", length = 255, nullable = false)
@Comment("변경 수행자(loginId)")
private String changedBy;
@Column(name = "change_date", length = 14, nullable = false)
@Comment("변경 일시")
@Convert(converter = LocalDateTimeToStringConverter14.class)
private LocalDateTime changeDate;
@Column(name = "created_by", length = 255, nullable = false)
@Comment("생성자")
private String createdBy;
@Column(name = "created_date", length = 14, nullable = false)
@Comment("생성일시")
@Convert(converter = LocalDateTimeToStringConverter14.class)
private LocalDateTime createdDate;
}
@@ -2,9 +2,7 @@ package com.eactive.apim.portal.portaluser.repository;
import com.eactive.apim.portal.portaluser.entity.TwoFactorAuth; import com.eactive.apim.portal.portaluser.entity.TwoFactorAuth;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import java.time.LocalDateTime;
import java.util.Optional; import java.util.Optional;
public interface TwoFactorAuthRepository extends JpaRepository<TwoFactorAuth, String> { public interface TwoFactorAuthRepository extends JpaRepository<TwoFactorAuth, String> {
@@ -14,14 +12,6 @@ public interface TwoFactorAuthRepository extends JpaRepository<TwoFactorAuth, St
void deleteAllByRecipientKey(String recipientKey); void deleteAllByRecipientKey(String recipientKey);
/**
* 만료된 인증번호 레코드를 일괄 삭제한다. (정리 스케줄러용)
* expires_on 은 LocalDateTimeToStringConverter14(yyyyMMddHHmmss) 로 저장되며,
* 해당 포맷은 사전식 정렬이 시간순과 일치하므로 문자열 &lt; 비교가 곧 시간 비교다.
*/
@Modifying
int deleteAllByExpiresAtBefore(LocalDateTime threshold);
// 아래 두 메서드는 recipient(암호화 컬럼) 기반이라 신뢰할 수 없음 — 신규 코드에서는 RecipientKey 버전을 사용할 것. // 아래 두 메서드는 recipient(암호화 컬럼) 기반이라 신뢰할 수 없음 — 신규 코드에서는 RecipientKey 버전을 사용할 것.
@Deprecated @Deprecated
Optional<TwoFactorAuth> findByRecipient(String recipientKey); Optional<TwoFactorAuth> findByRecipient(String recipientKey);
@@ -1,13 +0,0 @@
package com.eactive.apim.portal.portaluser.repository;
import com.eactive.apim.portal.portaluser.entity.UserRoleHistory;
import com.eactive.eai.rms.data.EMSDataSource;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
@EMSDataSource
public interface UserRoleHistoryRepository extends JpaRepository<UserRoleHistory, Long> {
List<UserRoleHistory> findByUserIdOrderByChangeDateDesc(String userId);
}
@@ -1,37 +1,7 @@
package com.eactive.apim.portal.portaluser.service; package com.eactive.apim.portal.portaluser.service;
/**
* 인증번호(2FA/이메일/SMS) 검증·발송 실패 예외.
*
* <p>기존에는 메시지 문자열만 있었으나, 감사 로그에서 실패 사유(만료/불일치/미존재)를
* 코드로 구분해야 하므로 {@link Reason} 을 추가했다. 기존 메시지 전용 생성자는 유지되며
* 이 경우 {@code reason == null} 이다(하위호환).</p>
*/
public class AuthNumberException extends RuntimeException{ public class AuthNumberException extends RuntimeException{
/** 실패 사유 분류 */
public enum Reason {
/** 저장된 인증번호가 없음(미발송/이미 소비/스케줄러 정리) */
NOT_FOUND,
/** 유효시간 초과 */
EXPIRED,
/** 인증번호 불일치 */
MISMATCH
}
private final Reason reason;
public AuthNumberException(String message) { public AuthNumberException(String message) {
super(message); super(message);
this.reason = null;
}
public AuthNumberException(String message, Reason reason) {
super(message);
this.reason = reason;
}
public Reason getReason() {
return reason;
} }
} }
@@ -86,23 +86,4 @@ public class Inquiry extends Auditable {
@Column(name = "ATTACH_FILE") @Column(name = "ATTACH_FILE")
private String attachFile; private String attachFile;
/**
* 공개범위 (전체공개/법인공개/비공개). 기본값 ORG.
* PTL_PROPERTY 기관 기본값이 상한(ceiling)이며 읽기/쓰기 시 clamp 된다.
*/
@Enumerated(EnumType.STRING)
@Column(name = "VISIBILITY", length = 16)
private VisibilityScope visibility = VisibilityScope.ORG;
/**
* 조회수. sessionStorage 기반 dedup 후 POST 요청으로만 증가한다.
*/
@Column(name = "VIEW_COUNT")
private long viewCount = 0L;
/** 조회수 1 증가. */
public void increaseViewCount() {
this.viewCount++;
}
} }
@@ -36,18 +36,6 @@ public class InquiryComment extends Auditable implements Serializable {
@Column(name = "ADMIN_YN", length = 1) @Column(name = "ADMIN_YN", length = 1)
private String adminYn = "N"; private String adminYn = "N";
/**
* 댓글 공개범위. 개념상 공개(ALL)/비공개(PRIVATE) 2단계.
* 비공개 댓글은 작성자·같은 법인 corp-manager·관리자만 열람 가능.
*/
@Enumerated(EnumType.STRING)
@Column(name = "VISIBILITY", length = 16)
private VisibilityScope visibility = VisibilityScope.ALL;
public boolean isPrivate() {
return this.visibility == VisibilityScope.PRIVATE;
}
public void markDeleted() { public void markDeleted() {
this.delYn = "Y"; this.delYn = "Y";
} }
@@ -1,67 +0,0 @@
package com.eactive.apim.portal.qna.entity;
/**
* 문의(Q&amp;A) 게시물/댓글의 공개범위.
*
* <p>{@code width}는 공개 범위의 넓이 랭크로, 값이 클수록 더 넓게 공개된다.
* {@code PTL_PROPERTY}에 설정된 기관 기본값이 상한(ceiling)이 되며, 게시물이 상한보다
* 넓게 설정될 수 없다({@link #clampTo}). property가 항상 우선한다.</p>
*
* <ul>
* <li>{@code ALL} : 전체 로그인 사용자 공개</li>
* <li>{@code ORG} : 같은 법인 소속 사용자 공개 (게시물 기본값)</li>
* <li>{@code PRIVATE} : 작성자 본인만 (댓글의 "비공개")</li>
* </ul>
*/
public enum VisibilityScope {
ALL(3, "전체공개"),
ORG(2, "법인공개"),
PRIVATE(1, "비공개");
private final int width;
private final String label;
VisibilityScope(int width, String label) {
this.width = width;
this.label = label;
}
/** 공개 범위 넓이 랭크. 클수록 넓게 공개. */
public int width() {
return width;
}
/** 화면 표기용 한글 라벨. */
public String label() {
return label;
}
/**
* 문자열을 enum으로 안전 변환. null/빈값/미해당은 defaultScope 반환.
*/
public static VisibilityScope fromString(String value, VisibilityScope defaultScope) {
if (value == null || value.trim().isEmpty()) {
return defaultScope;
}
try {
return VisibilityScope.valueOf(value.trim().toUpperCase());
} catch (IllegalArgumentException e) {
return defaultScope;
}
}
/**
* 요청 공개범위를 상한으로 clamp 한다. 요청이 상한보다 넓으면 상한으로 낮춘다.
* (property가 항상 우선하므로, 저장값이 옛 상한이라 더 넓더라도 읽기 시 재-clamp 한다.)
*/
public static VisibilityScope clampTo(VisibilityScope requested, VisibilityScope ceiling) {
if (requested == null) {
return ceiling;
}
if (ceiling == null) {
return requested;
}
return requested.width() > ceiling.width() ? ceiling : requested;
}
}
@@ -1,7 +1,6 @@
package com.eactive.apim.portal.template.entity; package com.eactive.apim.portal.template.entity;
import com.eactive.apim.portal.common.util.PhoneNumberUtil;
import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter; import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter;
import com.eactive.apim.portal.portaluser.entity.PortalUser; import com.eactive.apim.portal.portaluser.entity.PortalUser;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14; import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
@@ -84,11 +83,4 @@ public class MessageRequest implements Serializable {
@Column(name = "service_id") @Column(name = "service_id")
private String serviceId; private String serviceId;
/** 저장 시 전화번호를 하이픈 구분 정규형으로 통일 */
@PrePersist
@PreUpdate
private void normalizePhones() {
this.phone = PhoneNumberUtil.normalize(this.phone);
}
} }
@@ -78,7 +78,7 @@ public class MessageSendService {
String subject = buildMessage(template.getSubjectTemplate(), messageParams); String subject = buildMessage(template.getSubjectTemplate(), messageParams);
if (template.getEnableSms().equalsIgnoreCase("Y")) { if (template.getEnableSms().equalsIgnoreCase("Y")) {
String smsEaiInterfaceId = portalProperties.get("djb.ums.sms.if_id"); String smsEaiInterfaceId = portalProperties.get("ums.sms.if_id");
MessageRequest smsRequest = new MessageRequest(); MessageRequest smsRequest = new MessageRequest();
smsRequest.setMessageCode(MessageCode.valueOf(template.getMessageCode().toUpperCase())); smsRequest.setMessageCode(MessageCode.valueOf(template.getMessageCode().toUpperCase()));
smsRequest.setSubject(subject); smsRequest.setSubject(subject);
@@ -88,15 +88,19 @@ public class MessageSendService {
smsRequest.setUsername(user.getUsername()); smsRequest.setUsername(user.getUsername());
smsRequest.setPhone(user.getPhone()); smsRequest.setPhone(user.getPhone());
smsRequest.setEaiInterfaceId(smsEaiInterfaceId); smsRequest.setEaiInterfaceId(smsEaiInterfaceId);
smsRequest.setServiceId(portalProperties.get("djb.ums.sms.tx_id")); smsRequest.setServiceId(portalProperties.get("ums.sms.tx_id"));
smsRequest.setUserId(user.getUserId()); smsRequest.setUserId(user.getUserId());
if ("ADMIN_VERIFICATION_MOBILEPHONE".equals(template.getMessageCode().toUpperCase())) {
smsRequest.setMessageType("KAKAO"); smsRequest.setMessageType("KAKAO");
} else {
smsRequest.setMessageType("SMS");
}
messageRequestRepository.save(smsRequest); messageRequestRepository.save(smsRequest);
logger.debug(smsRequest.toString()); logger.debug(smsRequest.toString());
} }
if (template.getEnableEmail().equalsIgnoreCase("Y")) { if (template.getEnableEmail().equalsIgnoreCase("Y")) {
String mailEaiInterfaceId = portalProperties.get("djb.ums.email.if_id"); String mailEaiInterfaceId = portalProperties.get("ums.email.if_id");
MessageRequest emailRequest = new MessageRequest(); MessageRequest emailRequest = new MessageRequest();
emailRequest.setMessageCode(MessageCode.valueOf(template.getMessageCode().toUpperCase())); emailRequest.setMessageCode(MessageCode.valueOf(template.getMessageCode().toUpperCase()));
emailRequest.setSubject(subject); emailRequest.setSubject(subject);
@@ -106,7 +110,7 @@ public class MessageSendService {
emailRequest.setUsername(user.getUsername()); emailRequest.setUsername(user.getUsername());
emailRequest.setEmail(user.getUserId()); emailRequest.setEmail(user.getUserId());
emailRequest.setEaiInterfaceId(mailEaiInterfaceId); emailRequest.setEaiInterfaceId(mailEaiInterfaceId);
emailRequest.setServiceId(portalProperties.get("djb.ums.email.tx_id")); emailRequest.setServiceId(portalProperties.get("ums.email.tx_id"));
emailRequest.setMessageType("EMAIL"); emailRequest.setMessageType("EMAIL");
emailRequest.setUserId(user.getUserId()); emailRequest.setUserId(user.getUserId());
messageRequestRepository.save(emailRequest); messageRequestRepository.save(emailRequest);
@@ -114,7 +118,7 @@ public class MessageSendService {
} }
if (template.getEnableMessenger().equalsIgnoreCase("Y")) { if (template.getEnableMessenger().equalsIgnoreCase("Y")) {
String swingEaiInterfaceId = portalProperties.get("djb.ums.messenger.if_id"); String swingEaiInterfaceId = portalProperties.get("ums.messenger.if_id");
MessageRequest messengerRequest = new MessageRequest(); MessageRequest messengerRequest = new MessageRequest();
messengerRequest.setMessageCode(MessageCode.valueOf(template.getMessageCode())); messengerRequest.setMessageCode(MessageCode.valueOf(template.getMessageCode()));
messengerRequest.setSubject(subject); messengerRequest.setSubject(subject);
@@ -124,7 +128,7 @@ public class MessageSendService {
messengerRequest.setUsername(user.getUsername()); messengerRequest.setUsername(user.getUsername());
messengerRequest.setMessengerId(user.getUserId()); messengerRequest.setMessengerId(user.getUserId());
messengerRequest.setEaiInterfaceId(swingEaiInterfaceId); messengerRequest.setEaiInterfaceId(swingEaiInterfaceId);
messengerRequest.setServiceId(portalProperties.get("djb.ums.messenger.tx_id")); messengerRequest.setServiceId(portalProperties.get("ums.messenger.tx_id"));
messengerRequest.setMessageType("MESSENGER"); messengerRequest.setMessageType("MESSENGER");
messengerRequest.setUserId(user.getUserId()); messengerRequest.setUserId(user.getUserId());
messageRequestRepository.save(messengerRequest); messageRequestRepository.save(messengerRequest);
@@ -11,7 +11,6 @@ import lombok.NonNull;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment; import org.hibernate.annotations.Comment;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.annotation.LastModifiedDate;
import javax.persistence.*; import javax.persistence.*;
@@ -74,12 +73,6 @@ public class UserInfo extends AbstractEntity<String> implements Serializable {
@LastModifiedDate @LastModifiedDate
private LocalDateTime lastamndyms; private LocalDateTime lastamndyms;
@Column(length = 14)
@Comment("등록 일시")
@Convert(converter = LocalDateTimeToStringConverter14.class)
@CreatedDate
private LocalDateTime regdyms;
@Column(length = 80) @Column(length = 80)
@Comment("부서명") @Comment("부서명")
private String dvsnname; private String dvsnname;
@@ -143,10 +136,6 @@ public class UserInfo extends AbstractEntity<String> implements Serializable {
@Comment("사용자 계정 상태") @Comment("사용자 계정 상태")
private String status; private String status;
@Column
@Comment("로그인 실패 횟수")
private Integer loginfailcount;
@Override @Override
public @NonNull String getId() { public @NonNull String getId() {
return userid; return userid;
@@ -45,12 +45,4 @@ public class UserLog {
@Column(name = "success", nullable = false) @Column(name = "success", nullable = false)
private boolean success; private boolean success;
/** 실패 사유 코드 (LoginFailureReason). 성공 행은 null */
@Column(name = "failure_reason", length = 64)
private String failureReason;
/** 로그인 유형 코드 (LoginType: NORMAL/TWO_FACTOR/SIGNUP_AUTO). 실패 행·과거 행은 null */
@Column(name = "login_type", length = 32)
private String loginType;
} }