219 lines
9.4 KiB
Java
219 lines
9.4 KiB
Java
package com.snp.batch.service;
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.snp.batch.global.dto.bypass.BypassAccountResponse;
|
|
import com.snp.batch.global.dto.bypass.BypassAccountUpdateRequest;
|
|
import com.snp.batch.global.dto.bypass.ServiceIpDto;
|
|
import com.snp.batch.global.model.AccountStatus;
|
|
import com.snp.batch.global.model.BypassApiAccount;
|
|
import com.snp.batch.global.model.BypassApiServiceIp;
|
|
import com.snp.batch.global.repository.BypassApiAccountRepository;
|
|
import com.snp.batch.global.repository.BypassApiServiceIpRepository;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.data.domain.Page;
|
|
import org.springframework.data.domain.PageRequest;
|
|
import org.springframework.data.domain.Sort;
|
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import java.security.SecureRandom;
|
|
import java.time.LocalDate;
|
|
import java.util.List;
|
|
|
|
@Slf4j
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
public class BypassApiAccountService {
|
|
|
|
private static final String USERNAME_PREFIX = "bypass_";
|
|
private static final int USERNAME_RANDOM_LENGTH = 8;
|
|
private static final int PASSWORD_LENGTH = 16;
|
|
private static final String ALPHANUMERIC = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
private static final String PASSWORD_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%&*";
|
|
private static final SecureRandom RANDOM = new SecureRandom();
|
|
|
|
private final BypassApiAccountRepository accountRepository;
|
|
private final BypassApiServiceIpRepository serviceIpRepository;
|
|
private final PasswordEncoder passwordEncoder;
|
|
|
|
@Transactional
|
|
public BypassAccountResponse createAccount(String displayName, String organization,
|
|
String projectName,
|
|
String email, String phone,
|
|
LocalDate accessStartDate, LocalDate accessEndDate) {
|
|
String rawUsername = generateUsername();
|
|
String rawPassword = generatePassword();
|
|
|
|
BypassApiAccount account = BypassApiAccount.builder()
|
|
.username(rawUsername)
|
|
.passwordHash(passwordEncoder.encode(rawPassword))
|
|
.displayName(displayName)
|
|
.organization(organization)
|
|
.projectName(projectName)
|
|
.email(email)
|
|
.phone(phone)
|
|
.status(AccountStatus.ACTIVE)
|
|
.accessStartDate(accessStartDate)
|
|
.accessEndDate(accessEndDate)
|
|
.build();
|
|
|
|
BypassApiAccount saved = accountRepository.save(account);
|
|
log.info("Bypass API 계정 생성: username={}", rawUsername);
|
|
return toResponse(saved, rawPassword);
|
|
}
|
|
|
|
@Transactional(readOnly = true)
|
|
public Page<BypassAccountResponse> getAccounts(String status, int page, int size) {
|
|
PageRequest pageRequest = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt"));
|
|
Page<BypassApiAccount> accounts;
|
|
if (status != null && !status.isBlank()) {
|
|
accounts = accountRepository.findByStatus(AccountStatus.valueOf(status), pageRequest);
|
|
} else {
|
|
accounts = accountRepository.findAll(pageRequest);
|
|
}
|
|
return accounts.map(this::toResponse);
|
|
}
|
|
|
|
@Transactional(readOnly = true)
|
|
public BypassAccountResponse getAccount(Long id) {
|
|
return toResponse(findOrThrow(id));
|
|
}
|
|
|
|
@Transactional
|
|
public BypassAccountResponse updateAccount(Long id, BypassAccountUpdateRequest request) {
|
|
BypassApiAccount account = findOrThrow(id);
|
|
if (request.getDisplayName() != null) account.setDisplayName(request.getDisplayName());
|
|
if (request.getOrganization() != null) account.setOrganization(request.getOrganization());
|
|
if (request.getEmail() != null) account.setEmail(request.getEmail());
|
|
if (request.getPhone() != null) account.setPhone(request.getPhone());
|
|
if (request.getStatus() != null) account.setStatus(AccountStatus.valueOf(request.getStatus()));
|
|
if (request.getAccessStartDate() != null) account.setAccessStartDate(request.getAccessStartDate());
|
|
if (request.getAccessEndDate() != null) account.setAccessEndDate(request.getAccessEndDate());
|
|
return toResponse(accountRepository.save(account));
|
|
}
|
|
|
|
@Transactional
|
|
public void deleteAccount(Long id) {
|
|
BypassApiAccount account = findOrThrow(id);
|
|
accountRepository.delete(account);
|
|
log.info("Bypass API 계정 삭제: username={}", account.getUsername());
|
|
}
|
|
|
|
@Transactional
|
|
public BypassAccountResponse resetPassword(Long id) {
|
|
BypassApiAccount account = findOrThrow(id);
|
|
String rawPassword = generatePassword();
|
|
account.setPasswordHash(passwordEncoder.encode(rawPassword));
|
|
accountRepository.save(account);
|
|
log.info("Bypass API 비밀번호 재설정: username={}", account.getUsername());
|
|
return toResponse(account, rawPassword);
|
|
}
|
|
|
|
private BypassApiAccount findOrThrow(Long id) {
|
|
return accountRepository.findById(id)
|
|
.orElseThrow(() -> new IllegalArgumentException("계정을 찾을 수 없습니다: " + id));
|
|
}
|
|
|
|
private String generateUsername() {
|
|
for (int i = 0; i < 10; i++) {
|
|
StringBuilder sb = new StringBuilder(USERNAME_PREFIX);
|
|
for (int j = 0; j < USERNAME_RANDOM_LENGTH; j++) {
|
|
sb.append(ALPHANUMERIC.charAt(RANDOM.nextInt(ALPHANUMERIC.length())));
|
|
}
|
|
String candidate = sb.toString();
|
|
if (!accountRepository.existsByUsername(candidate)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
throw new IllegalStateException("Username 생성 실패: 10회 시도 초과");
|
|
}
|
|
|
|
private String generatePassword() {
|
|
StringBuilder sb = new StringBuilder(PASSWORD_LENGTH);
|
|
for (int i = 0; i < PASSWORD_LENGTH; i++) {
|
|
sb.append(PASSWORD_CHARS.charAt(RANDOM.nextInt(PASSWORD_CHARS.length())));
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
@Transactional(readOnly = true)
|
|
public List<ServiceIpDto> getServiceIps(Long accountId) {
|
|
findOrThrow(accountId);
|
|
return serviceIpRepository.findByAccountId(accountId).stream()
|
|
.map(ip -> ServiceIpDto.builder()
|
|
.ip(ip.getIpAddress())
|
|
.purpose(ip.getPurpose())
|
|
.description(ip.getDescription())
|
|
.expectedCallVolume(ip.getExpectedCallVolume())
|
|
.build())
|
|
.toList();
|
|
}
|
|
|
|
@Transactional
|
|
public ServiceIpDto addServiceIp(Long accountId, ServiceIpDto dto) {
|
|
BypassApiAccount account = findOrThrow(accountId);
|
|
BypassApiServiceIp saved = serviceIpRepository.save(BypassApiServiceIp.builder()
|
|
.account(account)
|
|
.ipAddress(dto.getIp())
|
|
.purpose(dto.getPurpose() != null ? dto.getPurpose() : "ETC")
|
|
.description(dto.getDescription())
|
|
.expectedCallVolume(dto.getExpectedCallVolume())
|
|
.build());
|
|
return ServiceIpDto.builder()
|
|
.ip(saved.getIpAddress())
|
|
.purpose(saved.getPurpose())
|
|
.description(saved.getDescription())
|
|
.expectedCallVolume(saved.getExpectedCallVolume())
|
|
.build();
|
|
}
|
|
|
|
@Transactional
|
|
public void deleteServiceIp(Long accountId, Long ipId) {
|
|
findOrThrow(accountId);
|
|
serviceIpRepository.deleteById(ipId);
|
|
}
|
|
|
|
private String getServiceIpsJson(Long accountId) {
|
|
List<BypassApiServiceIp> ips = serviceIpRepository.findByAccountId(accountId);
|
|
if (ips.isEmpty()) return null;
|
|
try {
|
|
ObjectMapper mapper = new ObjectMapper();
|
|
return mapper.writeValueAsString(ips.stream().map(ip ->
|
|
ServiceIpDto.builder()
|
|
.ip(ip.getIpAddress())
|
|
.purpose(ip.getPurpose())
|
|
.description(ip.getDescription())
|
|
.expectedCallVolume(ip.getExpectedCallVolume())
|
|
.build()
|
|
).toList());
|
|
} catch (Exception e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private BypassAccountResponse toResponse(BypassApiAccount account, String plainPassword) {
|
|
return BypassAccountResponse.builder()
|
|
.id(account.getId())
|
|
.username(account.getUsername())
|
|
.displayName(account.getDisplayName())
|
|
.organization(account.getOrganization())
|
|
.projectName(account.getProjectName())
|
|
.email(account.getEmail())
|
|
.phone(account.getPhone())
|
|
.status(account.getStatus().name())
|
|
.accessStartDate(account.getAccessStartDate())
|
|
.accessEndDate(account.getAccessEndDate())
|
|
.createdAt(account.getCreatedAt())
|
|
.updatedAt(account.getUpdatedAt())
|
|
.plainPassword(plainPassword)
|
|
.serviceIps(account.getId() != null ? getServiceIpsJson(account.getId()) : null)
|
|
.build();
|
|
}
|
|
|
|
private BypassAccountResponse toResponse(BypassApiAccount account) {
|
|
return toResponse(account, null);
|
|
}
|
|
}
|