refactor(api): Map→SaveReq DTO 교체 + 입력 검증 + 잔여 Enum 적용 (리뷰 3b)
infra-reviewer가 지적한 입력 검증 누락 5건과 잔여 매직 스트링 정리.
Map<String,String> 입력 제거 — 신규 DTO 4종:
- auth/ChangePasswordReq (@NotBlank oldPassword/newPassword)
- auth/RefreshTokenReq (@NotBlank refreshToken)
- controller/settle/PaymentStatusUpdateReq (@NotBlank status)
- controller/receive/ResolveErrorReq (@NotBlank status, optional note)
검증 보강:
- PaymentController.generateTransferFile — settleMonth @Pattern("\d{6}")
- UploadController.preview — limit @Max(100)
잔여 매직 스트링 → 3a Enum 적용:
- AuthService 4곳 — UserStatus.LOCKED/RETIRED/ACTIVE
- UserService 2곳 — UserStatus.ACTIVE
- LedgerService:71 — ExceptionLedgerStatus.NEW
This commit is contained in:
@@ -8,8 +8,6 @@ import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "인증")
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@@ -24,8 +22,8 @@ public class AuthController {
|
||||
}
|
||||
|
||||
@PostMapping("/refresh")
|
||||
public ApiResponse<LoginResp> refresh(@RequestBody Map<String, String> body) {
|
||||
return ApiResponse.ok(authService.refresh(body.get("refreshToken")));
|
||||
public ApiResponse<LoginResp> refresh(@Valid @RequestBody RefreshTokenReq req) {
|
||||
return ApiResponse.ok(authService.refresh(req.getRefreshToken()));
|
||||
}
|
||||
|
||||
@GetMapping("/me")
|
||||
@@ -34,8 +32,8 @@ public class AuthController {
|
||||
}
|
||||
|
||||
@PostMapping("/password")
|
||||
public ApiResponse<Void> changePassword(@RequestBody Map<String, String> body) {
|
||||
authService.changePassword(body.get("oldPassword"), body.get("newPassword"));
|
||||
public ApiResponse<Void> changePassword(@Valid @RequestBody ChangePasswordReq req) {
|
||||
authService.changePassword(req.getOldPassword(), req.getNewPassword());
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.ga.common.system.SystemLogMapper;
|
||||
import com.ga.common.util.SecurityUtil;
|
||||
import com.ga.core.mapper.user.UserMapper;
|
||||
import com.ga.core.vo.user.UserResp;
|
||||
import com.ga.core.vo.user.UserStatus;
|
||||
import com.ga.core.vo.user.UserVO;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -38,18 +39,18 @@ public class AuthService {
|
||||
saveLoginLog(null, req.getLoginId(), "FAIL", "USER_NOT_FOUND", ipAddress, userAgent);
|
||||
throw new BizException(ErrorCode.LOGIN_FAIL);
|
||||
}
|
||||
if ("LOCKED".equals(user.getStatus())) {
|
||||
if (UserStatus.LOCKED.name().equals(user.getStatus())) {
|
||||
saveLoginLog(user.getUserId(), req.getLoginId(), "FAIL", "ACCOUNT_LOCKED", ipAddress, userAgent);
|
||||
throw new BizException(ErrorCode.ACCOUNT_LOCKED);
|
||||
}
|
||||
if ("RETIRED".equals(user.getStatus())) {
|
||||
if (UserStatus.RETIRED.name().equals(user.getStatus())) {
|
||||
throw new BizException(ErrorCode.ACCOUNT_RETIRED);
|
||||
}
|
||||
if (!passwordEncoder.matches(req.getPassword(), user.getPassword())) {
|
||||
userMapper.incrementFailCount(user.getUserId());
|
||||
int newFail = (user.getFailCount() == null ? 0 : user.getFailCount()) + 1;
|
||||
if (newFail >= failLimit) {
|
||||
userMapper.updateStatus(user.getUserId(), "LOCKED");
|
||||
userMapper.updateStatus(user.getUserId(), UserStatus.LOCKED.name());
|
||||
}
|
||||
saveLoginLog(user.getUserId(), req.getLoginId(), "FAIL", "BAD_PASSWORD", ipAddress, userAgent);
|
||||
throw new BizException(ErrorCode.LOGIN_FAIL);
|
||||
@@ -80,7 +81,7 @@ public class AuthService {
|
||||
}
|
||||
Long userId = claims.get("uid", Long.class);
|
||||
UserVO user = userMapper.selectById(userId);
|
||||
if (user == null || !"ACTIVE".equals(user.getStatus())) {
|
||||
if (user == null || !UserStatus.ACTIVE.name().equals(user.getStatus())) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
List<String> roles = userMapper.selectRoleCodes(userId);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ga.api.auth;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ChangePasswordReq {
|
||||
|
||||
@NotBlank
|
||||
private String oldPassword;
|
||||
|
||||
@NotBlank
|
||||
private String newPassword;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.ga.api.auth;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class RefreshTokenReq {
|
||||
|
||||
@NotBlank
|
||||
private String refreshToken;
|
||||
}
|
||||
@@ -6,11 +6,11 @@ import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import com.ga.core.vo.receive.*;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "데이터 수신")
|
||||
@RestController
|
||||
@@ -100,10 +100,8 @@ public class ReceiveController {
|
||||
@PutMapping("/errors/{errorId}/resolve")
|
||||
@RequirePermission(menu = "RECEIVE_DATA", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "RECEIVE_DATA", table = "parse_error_log")
|
||||
public ApiResponse<Void> resolveError(@PathVariable Long errorId, @RequestBody Map<String, String> body) {
|
||||
receiveService.resolveError(errorId,
|
||||
body.getOrDefault("status", "RESOLVED"),
|
||||
body.get("note"));
|
||||
public ApiResponse<Void> resolveError(@PathVariable Long errorId, @Valid @RequestBody ResolveErrorReq req) {
|
||||
receiveService.resolveError(errorId, req.getStatus(), req.getNote());
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ga.api.controller.receive;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ResolveErrorReq {
|
||||
|
||||
@NotBlank
|
||||
private String status;
|
||||
|
||||
private String note;
|
||||
}
|
||||
@@ -11,12 +11,14 @@ import com.ga.core.vo.settle.SettleSearchParam;
|
||||
import com.ga.core.vo.settle.TransferFileResp;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "지급 관리")
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/api/payments")
|
||||
@RequiredArgsConstructor
|
||||
@@ -35,8 +37,8 @@ public class PaymentController {
|
||||
@PutMapping("/{paymentId}/status")
|
||||
@RequirePermission(menu = "PAYMENT", perm = "UPDATE")
|
||||
@DataChangeLog(menu = "PAYMENT", table = "payment")
|
||||
public ApiResponse<Void> updateStatus(@PathVariable Long paymentId, @RequestBody Map<String, String> body) {
|
||||
mapper.updateStatus(paymentId, body.get("status"), body.get("failReason"));
|
||||
public ApiResponse<Void> updateStatus(@PathVariable Long paymentId, @Valid @RequestBody PaymentStatusUpdateReq req) {
|
||||
mapper.updateStatus(paymentId, req.getStatus(), req.getFailReason());
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@@ -45,7 +47,7 @@ public class PaymentController {
|
||||
@RequirePermission(menu = "PAYMENT", perm = "EXPORT")
|
||||
@DataChangeLog(menu = "PAYMENT", table = "payment")
|
||||
public ApiResponse<TransferFileResp> generateTransferFile(
|
||||
@RequestParam String settleMonth,
|
||||
@Pattern(regexp = "\\d{6}", message = "settleMonth는 YYYYMM 형식이어야 합니다") @RequestParam String settleMonth,
|
||||
@RequestParam String bankCode) {
|
||||
return ApiResponse.ok(transferFileService.generate(settleMonth, bankCode));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ga.api.controller.settle;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class PaymentStatusUpdateReq {
|
||||
|
||||
@NotBlank
|
||||
private String status;
|
||||
|
||||
private String failReason;
|
||||
}
|
||||
@@ -9,7 +9,9 @@ import com.ga.common.annotation.RequirePermission;
|
||||
import com.ga.common.model.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@@ -17,6 +19,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "엑셀 업로드")
|
||||
@Validated
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class UploadController {
|
||||
@@ -41,7 +44,7 @@ public class UploadController {
|
||||
public ApiResponse<List<Map<String, Object>>> preview(
|
||||
@PathVariable String templateCode,
|
||||
@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "limit", defaultValue = "5") int limit) {
|
||||
@Max(100) @RequestParam(value = "limit", defaultValue = "5") int limit) {
|
||||
List<Map<String, Object>> rows = uploadService.preview(templateCode, file, limit);
|
||||
return ApiResponse.ok(rows);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.ga.core.mapper.ledger.MaintainLedgerMapper;
|
||||
import com.ga.core.mapper.ledger.RecruitLedgerMapper;
|
||||
import com.ga.core.vo.ledger.*;
|
||||
import com.ga.core.vo.ledger.ApproveStatus;
|
||||
import com.ga.core.vo.ledger.ExceptionLedgerStatus;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -69,7 +70,7 @@ public class LedgerService {
|
||||
vo.setRecurringMonths(req.getRecurringMonths());
|
||||
vo.setRecurringLeft(req.getRecurringMonths());
|
||||
vo.setApproveStatus(ApproveStatus.PENDING.name());
|
||||
vo.setStatus("NEW"); // ExceptionLedger 전용 초기 상태 — ApproveStatus에 없음
|
||||
vo.setStatus(ExceptionLedgerStatus.NEW.name());
|
||||
exceptionMapper.insert(vo);
|
||||
return vo.getLedgerId();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.ga.core.mapper.user.UserMapper;
|
||||
import com.ga.core.vo.user.UserResp;
|
||||
import com.ga.core.vo.user.UserSaveReq;
|
||||
import com.ga.core.vo.user.UserSearchParam;
|
||||
import com.ga.core.vo.user.UserStatus;
|
||||
import com.ga.core.vo.user.UserVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
@@ -52,7 +53,7 @@ public class UserService {
|
||||
vo.setEmail(req.getEmail());
|
||||
vo.setPhone(req.getPhone());
|
||||
vo.setAgentId(req.getAgentId());
|
||||
vo.setStatus(req.getStatus() != null ? req.getStatus() : "ACTIVE");
|
||||
vo.setStatus(req.getStatus() != null ? req.getStatus() : UserStatus.ACTIVE.name());
|
||||
mapper.insert(vo);
|
||||
|
||||
if (req.getRoleIds() != null) {
|
||||
@@ -91,7 +92,7 @@ public class UserService {
|
||||
@Transactional
|
||||
public void unlock(Long userId) {
|
||||
mapper.resetFailCount(userId);
|
||||
mapper.updateStatus(userId, "ACTIVE");
|
||||
mapper.updateStatus(userId, UserStatus.ACTIVE.name());
|
||||
}
|
||||
|
||||
public List<String> roleCodes(Long userId) {
|
||||
|
||||
Reference in New Issue
Block a user