회선번호 처리 개선 및 다양한 API 기능 강화

- user-service: 회원등록 API를 upsert 방식으로 변경 (기존 사용자 업데이트 지원)
- user-service: userName 필드 응답 누락 문제 해결 (DB 데이터 업데이트)
- kos-mock: Mock 데이터 생성 기간을 3개월에서 6개월로 확장
- product-service: 회선번호 대시 처리 지원 (010-1234-5678, 01012345678 모두 허용)
- bill-service: 회선번호 대시 선택적 처리 지원 (유연한 입력 형식)
- api-gateway: CORS 중복 헤더 제거 필터 추가

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
hiondal
2025-09-10 19:25:13 +09:00
parent 9bfdeda316
commit 2599d57a37
17 changed files with 323 additions and 238 deletions
@@ -29,7 +29,7 @@ import java.util.List;
public class SecurityConfig {
private final JwtTokenProvider jwtTokenProvider;
@Value("${cors.allowed-origins")
@Value("${cors.allowed-origins}")
private String allowedOrigins;
@Bean
@@ -79,60 +79,6 @@ public class UserController {
return ResponseEntity.ok(response);
}
/**
* 고객 ID로 사용자 정보 조회
* @param customerId 고객 ID
* @return 사용자 정보
*/
@Operation(
summary = "고객 ID로 사용자 정보 조회",
description = "고객 ID로 해당 고객의 사용자 정보를 조회합니다."
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음"),
@ApiResponse(responseCode = "500", description = "서버 내부 오류")
})
@GetMapping("/by-customer/{customerId}")
public ResponseEntity<UserInfoResponse> getUserInfoByCustomerId(
@Parameter(description = "고객 ID", required = true)
@PathVariable String customerId
) {
log.info("고객 ID로 사용자 정보 조회 요청: customerId={}", customerId);
UserInfoResponse response = userService.getUserInfoByCustomerId(customerId);
log.info("고객 ID로 사용자 정보 조회 성공: customerId={}", customerId);
return ResponseEntity.ok(response);
}
/**
* 회선번호로 사용자 정보 조회
* @param lineNumber 회선번호
* @return 사용자 정보
*/
@Operation(
summary = "회선번호로 사용자 정보 조회",
description = "회선번호로 해당 회선의 사용자 정보를 조회합니다."
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음"),
@ApiResponse(responseCode = "500", description = "서버 내부 오류")
})
@GetMapping("/by-line/{lineNumber}")
public ResponseEntity<UserInfoResponse> getUserInfoByLineNumber(
@Parameter(description = "회선번호", required = true)
@PathVariable String lineNumber
) {
log.info("회선번호로 사용자 정보 조회 요청: lineNumber={}", lineNumber);
UserInfoResponse response = userService.getUserInfoByLineNumber(lineNumber);
log.info("회선번호로 사용자 정보 조회 성공: lineNumber={}", lineNumber);
return ResponseEntity.ok(response);
}
/**
* 권한 부여
* @param userId 사용자 ID
@@ -17,11 +17,4 @@ public class UserNotFoundException extends RuntimeException {
return new UserNotFoundException("사용자를 찾을 수 없습니다. userId: " + userId);
}
public static UserNotFoundException byCustomerId(String customerId) {
return new UserNotFoundException("사용자를 찾을 수 없습니다. customerId: " + customerId);
}
public static UserNotFoundException byLineNumber(String lineNumber) {
return new UserNotFoundException("사용자를 찾을 수 없습니다. lineNumber: " + lineNumber);
}
}
@@ -88,54 +88,6 @@ public class UserService {
.build();
}
/**
* 고객 ID로 사용자 정보 조회
* @param customerId 고객 ID
* @return 사용자 정보
*/
public UserInfoResponse getUserInfoByCustomerId(String customerId) {
AuthUserEntity user = authUserRepository.findByCustomerId(customerId)
.orElseThrow(() -> UserNotFoundException.byCustomerId(customerId));
// 사용자 권한 목록 조회
List<String> permissions = authUserPermissionRepository.findPermissionCodesByUserId(user.getUserId());
return UserInfoResponse.builder()
.userId(user.getUserId())
.customerId(user.getCustomerId())
.lineNumber(user.getLineNumber())
.userName(user.getUserName())
.accountStatus(user.getAccountStatus().name())
.lastLoginAt(user.getLastLoginAt())
.lastPasswordChangedAt(user.getLastPasswordChangedAt())
.permissions(permissions)
.build();
}
/**
* 회선번호로 사용자 정보 조회
* @param lineNumber 회선번호
* @return 사용자 정보
*/
public UserInfoResponse getUserInfoByLineNumber(String lineNumber) {
AuthUserEntity user = authUserRepository.findByLineNumber(lineNumber)
.orElseThrow(() -> UserNotFoundException.byLineNumber(lineNumber));
// 사용자 권한 목록 조회
List<String> permissions = authUserPermissionRepository.findPermissionCodesByUserId(user.getUserId());
return UserInfoResponse.builder()
.userId(user.getUserId())
.customerId(user.getCustomerId())
.lineNumber(user.getLineNumber())
.userName(user.getUserName())
.accountStatus(user.getAccountStatus().name())
.lastLoginAt(user.getLastLoginAt())
.lastPasswordChangedAt(user.getLastPasswordChangedAt())
.permissions(permissions)
.build();
}
/**
* 권한 부여
* @param userId 사용자 ID
@@ -231,45 +183,53 @@ public class UserService {
}
/**
* 사용자 등록
* 사용자 등록 또는 업데이트 (Upsert)
* @param request 사용자 등록 요청
* @return 등록된 사용자 정보
* @return 등록/업데이트된 사용자 정보
*/
@Transactional
public UserRegistrationResponse registerUser(UserRegistrationRequest request) {
log.info("사용자 등록 요청: userId={}, customerId={}", request.getUserId(), request.getCustomerId());
// 중복 검사
validateUserUniqueness(request);
log.info("사용자 등록/업데이트 요청: userId={}, customerId={}", request.getUserId(), request.getCustomerId());
// 권한 코드 유효성 검증
validatePermissionCodes(request.getPermissions());
// 사용자 엔티티 생성
AuthUserEntity user = createUserEntity(request);
// 기존 사용자 확인
Optional<AuthUserEntity> existingUser = authUserRepository.findById(request.getUserId());
// 사용자 저장
AuthUserEntity savedUser = authUserRepository.save(user);
AuthUserEntity savedUser;
boolean isUpdate = false;
// 권한 부여
grantUserPermissions(savedUser.getUserId(), request.getPermissions());
if (existingUser.isPresent()) {
// 업데이트 로직
savedUser = updateExistingUser(existingUser.get(), request);
isUpdate = true;
log.info("기존 사용자 업데이트: userId={}", request.getUserId());
} else {
// 새 사용자 등록 전 유니크 검사
validateUserUniquenessForNewUser(request);
// 사용자 엔티티 생성
AuthUserEntity user = createUserEntity(request);
// 사용자 저장
savedUser = authUserRepository.save(user);
log.info("신규 사용자 등록: userId={}", request.getUserId());
}
// 권한 부여/업데이트
updateUserPermissions(savedUser.getUserId(), request.getPermissions());
// 응답 생성
UserRegistrationResponse response = buildRegistrationResponse(savedUser, request.getPermissions(), request.getUserName());
UserRegistrationResponse response = buildRegistrationResponse(savedUser, request.getPermissions(), request.getUserName(), isUpdate);
log.info("사용자 등록 완료: userId={}", savedUser.getUserId());
return response;
}
/**
* 사용자 유니크 필드 중복 검사
* 신규 사용자 등록 시 유니크 필드 중복 검사
*/
private void validateUserUniqueness(UserRegistrationRequest request) {
// 사용자 ID 중복 확인
if (authUserRepository.existsByUserId(request.getUserId())) {
throw new RuntimeException("이미 존재하는 사용자 ID입니다: " + request.getUserId());
}
private void validateUserUniquenessForNewUser(UserRegistrationRequest request) {
// 고객 ID 중복 확인
if (authUserRepository.existsByCustomerId(request.getCustomerId())) {
throw new RuntimeException("이미 존재하는 고객 ID입니다: " + request.getCustomerId());
@@ -281,6 +241,67 @@ public class UserService {
}
}
/**
* 기존 사용자 정보 업데이트
*/
private AuthUserEntity updateExistingUser(AuthUserEntity existingUser, UserRegistrationRequest request) {
// 다른 사용자가 같은 customerId나 lineNumber를 사용하는지 확인
validateUniqueFieldsForUpdate(existingUser.getUserId(), request);
// Salt 생성 (UUID 기반)
String salt = java.util.UUID.randomUUID().toString().replace("-", "").substring(0, 16);
// password + salt 결합 후 해시
String saltedPassword = request.getPassword() + salt;
String hashedPassword = passwordEncoder.encode(saltedPassword);
// 기존 엔티티 업데이트 (Builder 패턴 사용을 위해 새 엔티티 생성)
AuthUserEntity updatedUser = AuthUserEntity.builder()
.userId(existingUser.getUserId())
.customerId(request.getCustomerId())
.lineNumber(request.getLineNumber())
.userName(request.getUserName())
.passwordHash(hashedPassword)
.passwordSalt(salt)
.accountStatus(existingUser.getAccountStatus())
.failedLoginCount(existingUser.getFailedLoginCount())
.lastFailedLoginAt(existingUser.getLastFailedLoginAt())
.accountLockedUntil(existingUser.getAccountLockedUntil())
.lastLoginAt(existingUser.getLastLoginAt())
.lastPasswordChangedAt(existingUser.getLastPasswordChangedAt())
.build();
return authUserRepository.save(updatedUser);
}
/**
* 업데이트 시 다른 사용자와의 유니크 필드 중복 검사
*/
private void validateUniqueFieldsForUpdate(String userId, UserRegistrationRequest request) {
// 현재 사용자가 아닌 다른 사용자가 같은 customerId를 사용하는지 확인
Optional<AuthUserEntity> existingCustomer = authUserRepository.findByCustomerId(request.getCustomerId());
if (existingCustomer.isPresent() && !existingCustomer.get().getUserId().equals(userId)) {
throw new RuntimeException("이미 다른 사용자가 사용하는 고객 ID입니다: " + request.getCustomerId());
}
// 현재 사용자가 아닌 다른 사용자가 같은 lineNumber를 사용하는지 확인
Optional<AuthUserEntity> existingLine = authUserRepository.findByLineNumber(request.getLineNumber());
if (existingLine.isPresent() && !existingLine.get().getUserId().equals(userId)) {
throw new RuntimeException("이미 다른 사용자가 사용하는 회선번호입니다: " + request.getLineNumber());
}
}
/**
* 사용자 권한 업데이트 (기존 권한 모두 제거 후 새로 추가)
*/
private void updateUserPermissions(String userId, List<String> permissionCodes) {
// 기존 권한 모두 철회
authUserPermissionRepository.deleteAllByUserId(userId);
// 새 권한 부여
grantUserPermissions(userId, permissionCodes);
}
/**
* 권한 코드 유효성 검증
*/
@@ -332,9 +353,9 @@ public class UserService {
}
/**
* 사용자 등록 응답 생성
* 사용자 등록/업데이트 응답 생성
*/
private UserRegistrationResponse buildRegistrationResponse(AuthUserEntity user, List<String> permissions, String userName) {
private UserRegistrationResponse buildRegistrationResponse(AuthUserEntity user, List<String> permissions, String userName, boolean isUpdate) {
UserRegistrationResponse.UserData userData = UserRegistrationResponse.UserData.builder()
.userId(user.getUserId())
.customerId(user.getCustomerId())
@@ -345,9 +366,11 @@ public class UserService {
.permissions(permissions)
.build();
String message = isUpdate ? "사용자 정보가 성공적으로 업데이트되었습니다." : "사용자가 성공적으로 등록되었습니다.";
return UserRegistrationResponse.builder()
.success(true)
.message("사용자가 성공적으로 등록되었습니다.")
.message(message)
.data(userData)
.build();
}