Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 52 additions & 7 deletions src/main/java/com/project/core/controller/CustomerController.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,68 @@
package com.project.core.controller;

import com.project.core.controller.dto.request.FindCustomerRequest;
import java.util.List;

import org.springframework.http.ResponseEntity;

import com.project.core.controller.dto.request.ChangeEmailRequest;
import com.project.core.controller.dto.request.ChangeGradeRequest;
import com.project.core.controller.dto.response.ChangeEmailResponse;
import com.project.core.controller.dto.response.ChangeGradeResponse;
import com.project.core.infra.entity.customer.Customer;
import com.project.core.service.CustomerService;
import lombok.RequiredArgsConstructor;
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;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;



@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class CustomerController {
private final CustomerService customerService;
private final CustomerService customerService;

/**
* 고객 조회 (전화번호 기준)
*/
@GetMapping
public ResponseEntity<List<Customer>> loadByContactEnc(
@RequestParam String contactEnc
) {
List<Customer> customers = customerService.loadByContactEnc(contactEnc);
return ResponseEntity.ok(customers);
}
Comment on lines +34 to +39

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

컨트롤러에서 JPA 엔티티(Customer)를 직접 반환하고 있습니다. 이는 다음과 같은 문제를 야기할 수 있습니다.

  • 엔티티의 모든 필드가 외부에 노출되어 API 스펙이 내부 구현에 강하게 결합됩니다.
  • 지연 로딩(Lazy Loading) 관련 예외가 발생할 수 있습니다.
  • 엔티티 필드 변경 시 API 응답 구조가 예기치 않게 변경될 수 있습니다.

Customer 엔티티의 정보를 담는 별도의 DTO(e.g., CustomerResponseDto)를 생성하고, 엔티티를 DTO로 변환하여 반환하도록 수정하는 것이 좋습니다.


/**
* 이메일 변경
* @throws Exception
*/
@PostMapping("/{userId}/email")
public ResponseEntity<ChangeEmailResponse> changeEmail(
@PathVariable Long userId,
@RequestBody ChangeEmailRequest request
) throws Exception {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

throws Exception은 너무 포괄적인 예외 선언입니다. 실제 발생하는 예외를 명시하거나, ControllerAdvice를 통해 전역적으로 처리하는 것이 좋습니다. customerService.changeEmailEnc 메서드는 확인된 예외(checked exception)를 던지지 않으므로 이 선언은 불필요해 보입니다. 제거하는 것을 권장합니다.

Suggested change
) throws Exception {
) {

ChangeEmailResponse response =
customerService.changeEmailEnc(userId, request);
return ResponseEntity.ok(response);
}

/**
* 고객 등급 변경
*/
@PostMapping("/{userId}/grade")
public ResponseEntity<ChangeGradeResponse> changeGrade(
@PathVariable Long userId,
@RequestBody ChangeGradeRequest request
) {
ChangeGradeResponse response =
customerService.changeUserGrade(userId, request);
return ResponseEntity.ok(response);
}

@GetMapping
public Long save(@RequestBody FindCustomerRequest request) {
Customer customer = customerService.loadByContactEnc(request.contactEnc());
return customer.getCustomerId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.project.core.infra.entity.discount;

import java.util.ArrayList;
import java.util.List;

import com.project.core.infra.entity.discount.enums.Active;
import com.project.core.infra.entity.discount.enums.Category;
import com.project.core.infra.entity.discount.enums.DiscountType;
import com.project.core.infra.entity.discount.enums.TargetScope;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "discount_policy")
public class DiscountPolicy {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "discount_id")
private Long discountId;

@Column(name = "name", nullable = false)
private String name;

@Column(name = "discount_type", nullable = false)
private DiscountType discountType;

@Column(name = "value", nullable = false)
private Double value;
Comment on lines +39 to +40

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

금액이나 비율과 같은 값을 Double 타입으로 저장하면 부동 소수점 연산 시 정밀도 문제가 발생할 수 있습니다. 금융 관련 계산에서는 BigDecimal 타입을 사용하는 것이 표준적인 방법입니다. value 필드의 타입을 BigDecimal로 변경해주세요.

Suggested change
@Column(name = "value", nullable = false)
private Double value;
@Column(name = "value", nullable = false)
private java.math.BigDecimal value;


@Column(name = "category", nullable = false)
private Category category;

@Column(name = "target_scope", nullable = false)
private TargetScope targetScope;

@Column(name = "active", nullable = false)
private Active active;
//------------------------------------------------------------------
@OneToMany(mappedBy = "discountPolicy", cascade = CascadeType.ALL)
private List<SubscriptionDiscount> discountHistory = new ArrayList<>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.project.core.infra.entity.discount;

import java.time.LocalDateTime;

import com.project.core.infra.entity.discount.enums.DiscountType;
import com.project.core.infra.entity.discount.enums.Status;
import com.project.core.infra.entity.discount.enums.TargetScope;
import com.project.core.infra.entity.subscription.Subscription;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "subscription_discount")
public class SubscriptionDiscount {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "sd_id")
private Long sdId;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "discount_id", nullable = false)
private DiscountPolicy discountPolicy;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "sub_id", nullable = false)
private Subscription subscription;

@Column(name = "discount_type", nullable = false)
private DiscountType discountType;

@Column(name = "value", nullable = false)
private Double value;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

금액이나 비율과 같은 값을 Double 타입으로 저장하면 부동 소수점 연산 시 정밀도 문제가 발생할 수 있습니다. 금융 관련 계산에서는 BigDecimal 타입을 사용하는 것이 표준적인 방법입니다. value 필드의 타입을 BigDecimal로 변경해주세요. 이 변경에 맞춰 빌더(SubscriptionDiscount.builder())의 파라미터와 내부 로직도 함께 수정해야 합니다.

Suggested change
private Double value;
private java.math.BigDecimal value;


@Column(name = "target_scope", nullable = false)
private TargetScope targetScope;

@Column(name = "start_date", nullable = false)
private LocalDateTime startDate;

@Column(name = "end_date")
private LocalDateTime endDate;

@Column(name = "status", nullable = false)
private Status status;

public void setEndDate(LocalDateTime endDate) {
this.endDate = endDate;
}
public void setStatusTerminated() {
this.status = Status.TERMINATED;
}

@Builder
private SubscriptionDiscount(
DiscountPolicy discountPolicy,
Subscription subscription,
DiscountType discountType,
Double value,
TargetScope targetScope,
LocalDateTime startDate
) {
if (discountPolicy == null) throw new IllegalArgumentException("discountPolicy는 필수입니다.");
if (subscription == null) throw new IllegalArgumentException("subscription은 필수입니다.");
if (discountType == null) throw new IllegalArgumentException("discountType는 필수입니다.");
if (value == null) throw new IllegalArgumentException("value는 필수입니다.");
if (targetScope == null) throw new IllegalArgumentException("targetScope는 필수입니다.");

this.discountPolicy = discountPolicy;
this.subscription = subscription;
this.discountType = discountType;
this.value = value;
this.targetScope = targetScope;
this.startDate = startDate != null ? startDate : LocalDateTime.now();

// 기본값
this.endDate = null;
this.status = Status.ACTIVE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.project.core.infra.entity.discount.enums;

public enum Active {
ACTIVE
,INACTIVE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.project.core.infra.entity.discount.enums;

public enum Category {
복지
,프로모션
,결합
Comment on lines +4 to +6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Enum 상수를 한글로 정의하면 일부 시스템이나 라이브러리에서 인코딩 문제를 일으킬 수 있으며, 다국어 지원 시 관리가 어려워집니다. Enum 상수는 일반적으로 영문 대문자와 스네이크 케이스(UPPER_SNAKE_CASE)로 작성하는 것이 표준입니다. 한글명은 별도의 필드로 관리하는 것을 권장합니다.

Suggested change
복지
,프로모션
,결합
WELFARE,
PROMOTION,
COMBINATION

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.project.core.infra.entity.discount.enums;

public enum DiscountType {
Rate
,Fixed
Comment on lines +4 to +5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Java Enum 상수 명명 규칙에 따라 UPPER_SNAKE_CASE를 사용하는 것이 좋습니다. (e.g., Rate -> RATE).

Suggested change
Rate
,Fixed
RATE,
FIXED

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.project.core.infra.entity.discount.enums;

public enum Status {
ACTIVE
,TERMINATED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.project.core.infra.entity.discount.enums;

public enum TargetScope {
PLAN_FEE
,TOTAL_AMOUNT
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,8 @@ public void terminate(Clock clock) {
this.status = SubscriptionStatus.TERMINATED;
this.endDate = LocalDateTime.now(clock);
}
}

public void setPhoneNumber(String maskedNum) {
this.phoneNumber = maskedNum;
}
Comment on lines +77 to +79

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

phoneNumber 필드는 암호화된 전화번호를 저장해야 하는 중요한 데이터입니다. 이 setPhoneNumber 메서드는 SubscriptionService에서 마스킹된 전화번호를 저장하는 데 사용되고 있어, 영구적으로 데이터를 손상시킬 위험이 있습니다. 엔티티의 상태는 비즈니스 로직에 의해서만 변경되어야 하며, 화면 표시용 데이터로 덮어쓰여서는 안 됩니다. 이 메서드를 제거하고, SubscriptionService에서는 DTO를 사용하여 마스킹된 데이터를 전달하도록 리팩토링하는 것을 강력히 권장합니다.

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.project.core.infra.repository.customer;

import java.util.List;
import com.project.core.infra.entity.customer.Customer;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CustomerRepository extends JpaRepository<Customer, Long> {
Optional<Customer> findByContactEnc(String contactEnc);
List<Customer> findByContactEnc(String contactEnc);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.project.core.infra.repository.discount;

import org.springframework.data.jpa.repository.JpaRepository;

import com.project.core.infra.entity.discount.DiscountPolicy;

public interface DiscountPolicyRepository extends JpaRepository<DiscountPolicy, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.project.core.infra.repository.discount;

import java.util.List;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.project.core.infra.entity.discount.SubscriptionDiscount;

public interface SubscriptionDiscountRepository extends JpaRepository<SubscriptionDiscount, Long> {
List<SubscriptionDiscount> findBySubscription_SubId(Long subId);
Optional<SubscriptionDiscount> findBySdId(Long sdId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
import com.project.core.infra.entity.customer.Customer;
import com.project.core.infra.entity.subscription.Subscription;
import com.project.core.infra.entity.subscription.enums.SubscriptionStatus;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

public interface SubscriptionRepository extends JpaRepository<Subscription, Long> {
List<Subscription> findByCustomer_CustomerId(Long customerId);

long countByCustomerAndStatus(Customer customer, SubscriptionStatus status);

boolean existsByPhoneNumberAndStatus(String phoneNumber, SubscriptionStatus status);

boolean existsByPhoneNumber(String phoneNumber);
}
Loading
Loading