TIL

24_01_26 TIL

nakgopsae 2024. 1. 26. 22:08

- 오늘 계획

coupon 기능 구현

 

- 오늘 한것

쿠폰 요구사항에 맞춰 기능을 구현하는데 테이블의 구조가 어려웠다 팀원분이 발행정보를 저장하는 쿠폰테이블과 유저사이의 브릿지테이블/ 발행된쿠폰테이블을 만들어서 기능 구현을 하라고 하여 구상했지만 실패하였고 기본CRUD 만있는 작업물에 같은 팀원과 같이 작업을 진행하였는데 

 

쿠폰 요구사항.
1. 쿠폰은 사업자와 어드민만이 발행할수 있다.
2. 사업자가 만든 쿠폰은 사업자의 가게 이외에서는 사용이 불가능하다.
3. 쿠폰의 발행 수를 조절할 수 있다. 0이면 무한으로 발행하는 쿠폰이다.
4. 운영진은 모든 쿠폰을 삭제할 수 있다.
5. 쿠폰에서는 원가와 쿠폰가의 차이를 계산할 수 있어야한다.
6. 환불시에 쿠폰을 사용했으면 쿠폰을 다시 재발급 해야한다.
7. 쿠폰을 사용했을때는 해당 쿠폰이 사용되었는지 여부가 표시되어야 합니다. (service에 해당 기능을 만들어주세요.)

 

이 와 같은 비즈니스 요구 조건을 만족하기 위해 테이블을 구상하였는데

@Entity
@Table(name = "coupon")
class Coupon(
    @Column(name = "name")
    var name:String,
	//쿠폰의 이름
    @Column(name = "discount")
    var discount:Double,
	//할인율
    @Enumerated(EnumType.STRING)
    @Column(name = "publisher")
    var publisher: Publisher,
	//발행처 enum으로 처리
    @Column(name = "max_count")
    var maxCount:Int,
	//발급 최대수량
    @Column(name = "user_id")
    val userId : Long,
	//userId를 참조하기 위한userId
    //true와 false로 사용유무 구분을 위함
    @Column(name = "issued")
    val issued: Boolean
	//발행여부 이지만 여기 테이블에는 안쓰일듯
) {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id:Long?=null
	//쿠폰의 아이디
    @Column(name = "issued_count")
    var issuedCount:Int = 0
	//발행된 갯수
    fun toResponse():CouponResponseDto{
        return CouponResponseDto(
            id = this.id!!,
            name = this.name,
            discount = this.discount,
            publisher =this.publisher.name,
            maxCount =this.maxCount,
            issuedCount =this.issuedCount,
            userId = this.userId,
           // storeId = this.storeId
}
/////////////////////////////////////////////////////////////////////////////////////////////


@Entity
@Table(name = "coupon_issued")
class Issued(

    @Column(name = "coupon_id")
    val coupon: Long,

    @Column(name = "coupon_discount")
    val couponDiscount: Int,

    @Column(name = "coupon_issued_count")
    val couponIssuedCount : Int



)
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null

    @Column(name = "coupon_issued")
    var couponIssued: Boolean = false

    fun updateIssued() {
        this.couponIssued = true
    }
}
// 개발하다가 만것 설계중 어려움 ,,

 

코드를 찍는 건 그닥 어렵지 않고 시간만 있으면 할수있지만 구조를 잘 못잡아서 팀원과 함께 일단 절반 정도를 구상하여 엔티티를 작성해 보았다

 

coupon 도메인 내에서 PostMapping이 두번 쓰여져서 오류가 있어 경로를 다르게 설정할까 했으나 일단은 두개로 분리하여 작업 하였다 쿠폰의 발행정보를 담는 테이블의 작성용 post와  중간 브릿지 발행된 userMember와 연관이 있는 테이블의 작성용 이렇게 두개를 

 

일단은 기본 crud를 제외하고 요구사항을 위해 발행정보 저장 쿠폰생성 컨트롤러와 서비스를 작성해 보았는데

@RequestMapping("/coupons")
@RestController
class CouponController(
    private val couponService: CouponService
) {
    @PostMapping
    @PreAuthorize("hasRole('STORE') or hasRole('ADMIN')")
    fun createCoupon(
        @RequestBody createCouponDto: CreateCouponDto,
        @AuthenticationPrincipal user: UserPrincipal,
    ):ResponseEntity<CouponResponseDto>{
        return ResponseEntity
            .status(HttpStatus.CREATED)
            .body(couponService.createCoupon(createCouponDto,user.id))
    }// 사업자와 어드민만 발행할수있다 -->쿠폰 레파지토리에 준비가 된거고
    }
    @PreAuthorize로 Role값이 STORE / ADMIN 인 로그인사용자만 생성할수있게 해두었고
    로그인유저의 정보를 가져오기위해 
    @AuthenticationPrincipal user:UserPrincipal을 어노테이션으로 달아두었다
    
    //controller

 

서비스 구현체

@Service
class CouponServiceImpl(
    private val couponRepository: CouponRepository,
    private val issuedRepository: IssuedRepository,
    private val memberRepository: MemberRepository,
    private val storeRepository: StoreRepository
):CouponService{


    override fun createCoupon(createCouponDto: CreateCouponDto , userId:Long): CouponResponseDto {
        val member: Member = memberRepository.findByIdOrNull(userId) ?: throw ModelNotFoundException("Model",userId)
        val publisher = when (member.role.toString()) {
            "ADMIN" -> Publisher.ADMIN
            "STORE" -> Publisher.STORE
            else -> throw IllegalArgumentException("Invalid role")
        }
        val couponSave =
            couponRepository.save(
                Coupon(
                    name = createCouponDto.name,
                    discount = createCouponDto.discount,
                    publisher = publisher,
                    maxCount = createCouponDto.maxCount,
                    issued = false,
                    userId = member.id!!,
                )
            )
        return couponSave.toResponse()
    }
     //현재까지는 잘 작동합니다.
     //TODO: 생성시 역할이 STORE일 경우 STORE 아이디도 DB에 기록이 되고, ADMIN일 경우 storeId 테이블이 null값으로 오게 하려고 했습니다.

 

아직 요구 사항을 완전히 구현하지 못하였지만 작동 까지는 완료되게 만들어보았다

 

내가 집중 작업한 부분은 삭제 부분인데

@DeleteMapping("/{couponId}/{storeId}")
    @PreAuthorize("hasRole('STORE') or hasRole('ADMIN') ")
    fun deleteCoupon( @AuthenticationPrincipal user: UserPrincipal,
        @PathVariable couponId:Long, @PathVariable storeId:Long?
    ):ResponseEntity<Unit>{
        return ResponseEntity
            .status(HttpStatus.NO_CONTENT)
            .build()
    }
    // controller
  
  
  
   override fun deleteCoupon(couponId: Long, userId: Long, storeId: Long?) {
        val findCoupon = couponRepository.findByIdOrNull(couponId) ?: throw ModelNotFoundException("coupon", couponId)
        val member: Member = memberRepository.findByIdOrNull(userId) ?: throw ModelNotFoundException("Model", userId)
        val findStore = storeRepository.findByIdOrNull(storeId) ?: throw ModelNotFoundException("store", storeId)

        if (member.role.toString() == "ADMIN") {
            couponRepository.delete(findCoupon)
        } else if (member.role.toString() == "STORE") {
            if (findStore.userId == member.id) {
                //스토어 아이디로 조회한 Store의 user_id가 val member(인자값으로 받은userId)랑 같다면 지워도 된다
                couponRepository.delete(findCoupon)
            } else {
                throw Exception("가게의 주인이 아니여서 권한이 없습니다")
            }
            //create 부분에 storeId 부분을 만들지 못해서 검증을 못했습니다!
        }
    }

 

아직 논리가 좋지 못해 주석으로 요구사항과 절차를 적어놓고 한개씩 주먹구구식으로 해석하면서 코드를 작성했다 

삭제요청은 관리자와 가게주인만 할 수 있고 삭제실행을 하였을때 지우고 싶은 쿠폰번호와 가게 번호( nullable) 를 받는다

이를 repository에 접근하여 있는지 체크하고 없다면 Exception을 일으키는 변수를 만든다

if문을 통해 관리자일시 삭제 / 가게주인일시 store테이블에 있는 user_id를 참조하여 member.userId와 같은지 확인후 같다면 삭제 / 아니라면 권한이 없다는 에러를 던진다

 

하루종일 구상만하고 있다가 하나 작성하는데 몇시간이 걸렸다 

 

데이터테이블의 관계를 명확히하고 요구사항을 깔끔하게 정리하여 머리속이나 그림으로 만들어 내지 않으면 코드로 구현하기 힘들다는 걸 느꼈고 앞으로의 프로젝트는 이런 능력을 더 키워서 도전 해보아야겠다고 생각했다

 

 

'TIL' 카테고리의 다른 글

24_02_06 TIL  (1) 2024.02.06
24_01_29 TIL  (1) 2024.01.29
24_01_23 TIL  (0) 2024.01.23
24_01_29 TIL  (0) 2024.01.19
24_01_16 TIL  (0) 2024.01.16