store update
This commit is contained in:
parent
43a423dbc4
commit
68919c1615
@ -1,5 +1,6 @@
|
||||
package com.ktds.hi.member.domain;
|
||||
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
@ -13,11 +14,12 @@ import lombok.NoArgsConstructor;
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "taste_tag")
|
||||
public class TasteTag {
|
||||
|
||||
private Long id;
|
||||
private String tagName;
|
||||
private TagType tagType;
|
||||
private String description;
|
||||
private TagType tagType; //카테고리
|
||||
private String description; //매운맛, 짠맛
|
||||
private Boolean isActive;
|
||||
}
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
package com.ktds.hi.store.biz.service;
|
||||
|
||||
import com.ktds.hi.store.biz.usecase.in.TagUseCase;
|
||||
import com.ktds.hi.store.biz.usecase.out.TagRepositoryPort;
|
||||
import com.ktds.hi.store.domain.Tag;
|
||||
import com.ktds.hi.store.infra.dto.response.TopClickedTagResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 태그 서비스 클래스
|
||||
* 태그 관련 비즈니스 로직을 구현
|
||||
*
|
||||
* @author 하이오더 개발팀
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Transactional(readOnly = true)
|
||||
public class TagService implements TagUseCase {
|
||||
|
||||
private final TagRepositoryPort tagRepositoryPort;
|
||||
|
||||
@Override
|
||||
public List<TopClickedTagResponse> getTopClickedTags() {
|
||||
log.info("가장 많이 클릭된 상위 5개 태그 조회 시작");
|
||||
|
||||
List<Tag> topTags = tagRepositoryPort.findTopClickedTags();
|
||||
|
||||
AtomicInteger rank = new AtomicInteger(1);
|
||||
|
||||
List<TopClickedTagResponse> responses = topTags.stream()
|
||||
.map(tag -> TopClickedTagResponse.builder()
|
||||
.tagId(tag.getId())
|
||||
.tagName(tag.getTagName())
|
||||
.tagCategory(tag.getTagCategory().name())
|
||||
.tagColor(tag.getTagColor())
|
||||
.clickCount(tag.getClickCount())
|
||||
.rank(rank.getAndIncrement())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
log.info("가장 많이 클릭된 상위 5개 태그 조회 완료: count={}", responses.size());
|
||||
return responses;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void recordTagClick(Long tagId) {
|
||||
log.info("태그 클릭 이벤트 처리 시작: tagId={}", tagId);
|
||||
|
||||
Tag updatedTag = tagRepositoryPort.incrementTagClickCount(tagId);
|
||||
|
||||
log.info("태그 클릭 수 증가 완료: tagId={}, clickCount={}",
|
||||
tagId, updatedTag.getClickCount());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package com.ktds.hi.store.biz.usecase.in;
|
||||
|
||||
import com.ktds.hi.store.infra.dto.response.TopClickedTagResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 태그 유스케이스 인터페이스
|
||||
* 태그 관련 비즈니스 로직을 정의
|
||||
*
|
||||
* @author 하이오더 개발팀
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public interface TagUseCase {
|
||||
|
||||
/**
|
||||
* 가장 많이 클릭된 상위 5개 태그 조회
|
||||
*/
|
||||
List<TopClickedTagResponse> getTopClickedTags();
|
||||
|
||||
/**
|
||||
* 태그 클릭 이벤트 처리
|
||||
*/
|
||||
void recordTagClick(Long tagId);
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package com.ktds.hi.store.biz.usecase.out;
|
||||
|
||||
// store/src/main/java/com/ktds/hi/store/biz/usecase/out/TagRepositoryPort.java
|
||||
import com.ktds.hi.store.domain.Tag;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 태그 리포지토리 포트 인터페이스
|
||||
* 태그 데이터 영속성 기능을 정의
|
||||
*
|
||||
* @author 하이오더 개발팀
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public interface TagRepositoryPort {
|
||||
|
||||
/**
|
||||
* 활성화된 모든 태그 조회
|
||||
*/
|
||||
List<Tag> findAllActiveTags();
|
||||
|
||||
/**
|
||||
* 태그 ID로 태그 조회
|
||||
*/
|
||||
Optional<Tag> findTagById(Long tagId);
|
||||
|
||||
/**
|
||||
* 태그명으로 태그 조회
|
||||
*/
|
||||
Optional<Tag> findTagByName(String tagName);
|
||||
|
||||
/**
|
||||
* 가장 많이 클릭된 상위 5개 태그 조회
|
||||
*/
|
||||
List<Tag> findTopClickedTags();
|
||||
|
||||
/**
|
||||
* 태그 클릭 수 증가
|
||||
*/
|
||||
Tag incrementTagClickCount(Long tagId);
|
||||
|
||||
/**
|
||||
* 태그 저장
|
||||
*/
|
||||
Tag saveTag(Tag tag);
|
||||
}
|
||||
46
store/src/main/java/com/ktds/hi/store/domain/Tag.java
Normal file
46
store/src/main/java/com/ktds/hi/store/domain/Tag.java
Normal file
@ -0,0 +1,46 @@
|
||||
package com.ktds.hi.store.domain;
|
||||
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 태그 도메인 클래스
|
||||
* 매장 태그 정보를 나타냄
|
||||
*
|
||||
* @author 하이오더 개발팀
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
public class Tag {
|
||||
private Long id;
|
||||
private String tagName;
|
||||
private TagCategory tagCategory;
|
||||
private String tagColor;
|
||||
private Integer sortOrder;
|
||||
private Boolean isActive;
|
||||
private Long clickCount;
|
||||
|
||||
/**
|
||||
* 클릭 수 증가
|
||||
*/
|
||||
public Tag incrementClickCount() {
|
||||
return Tag.builder()
|
||||
.id(this.id)
|
||||
.tagName(this.tagName)
|
||||
.tagCategory(this.tagCategory)
|
||||
.tagColor(this.tagColor)
|
||||
.sortOrder(this.sortOrder)
|
||||
.isActive(this.isActive)
|
||||
.clickCount(this.clickCount != null ? this.clickCount + 1 : 1L)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 활성 상태 확인
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return Boolean.TRUE.equals(this.isActive);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.ktds.hi.store.domain;
|
||||
|
||||
|
||||
/**
|
||||
* 태그 카테고리 열거형 클래스
|
||||
* 매장 태그의 분류를 정의
|
||||
*
|
||||
* @author 하이오더 개발팀
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public enum TagCategory {
|
||||
TASTE("맛"), // 매운맛, 단맛, 짠맛 등
|
||||
ATMOSPHERE("분위기"), // 깨끗한, 혼밥, 데이트 등
|
||||
ALLERGY("알러지"), // 유제품, 견과류, 갑각류 등
|
||||
SERVICE("서비스"), // 빠른서비스, 친절한, 조용한 등
|
||||
PRICE("가격대"), // 저렴한, 합리적인, 가성비 등
|
||||
FOOD_TYPE("음식 종류"), // 한식, 중식, 일식 등
|
||||
HEALTH("건강 정보"); // 저염, 저당, 글루텐프리 등
|
||||
|
||||
private final String description;
|
||||
|
||||
TagCategory(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.ktds.hi.store.infra.controller;
|
||||
|
||||
import com.ktds.hi.store.biz.usecase.in.TagUseCase;
|
||||
import com.ktds.hi.store.infra.dto.response.TopClickedTagResponse;
|
||||
import com.ktds.hi.common.dto.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 태그 컨트롤러 클래스
|
||||
* 태그 관련 API 엔드포인트를 제공
|
||||
*
|
||||
* @author 하이오더 개발팀
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/stores/tags")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "태그 관리 API", description = "매장 태그 조회 및 통계 관련 API")
|
||||
public class TagController {
|
||||
|
||||
private final TagUseCase tagUseCase;
|
||||
|
||||
/**
|
||||
* 가장 많이 클릭된 상위 5개 태그 조회 API
|
||||
*/
|
||||
@GetMapping("/top-clicked")
|
||||
@Operation(summary = "인기 태그 조회", description = "가장 많이 클릭된 상위 5개 태그를 조회합니다.")
|
||||
public ResponseEntity<ApiResponse<List<TopClickedTagResponse>>> getTopClickedTags() {
|
||||
|
||||
List<TopClickedTagResponse> topTags = tagUseCase.getTopClickedTags();
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(topTags));
|
||||
}
|
||||
|
||||
/**
|
||||
* 태그 클릭 이벤트 기록 API
|
||||
*/
|
||||
@PostMapping("/{tagId}/click")
|
||||
@Operation(summary = "태그 클릭 기록", description = "태그 클릭 이벤트를 기록하고 클릭 수를 증가시킵니다.")
|
||||
public ResponseEntity<ApiResponse<Void>> recordTagClick(@PathVariable Long tagId) {
|
||||
|
||||
tagUseCase.recordTagClick(tagId);
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.ktds.hi.store.infra.dto.response;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 인기 태그 응답 DTO 클래스
|
||||
* 가장 많이 클릭된 태그 정보를 전달
|
||||
*
|
||||
* @author 하이오더 개발팀
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
public class TopClickedTagResponse {
|
||||
private Long tagId;
|
||||
private String tagName;
|
||||
private String tagCategory;
|
||||
private String tagColor;
|
||||
private Long clickCount;
|
||||
private Integer rank;
|
||||
}
|
||||
@ -0,0 +1,124 @@
|
||||
package com.ktds.hi.store.infra.gateway;
|
||||
|
||||
import com.ktds.hi.store.biz.usecase.out.TagRepositoryPort;
|
||||
import com.ktds.hi.store.domain.Tag;
|
||||
import com.ktds.hi.store.domain.TagCategory;
|
||||
import com.ktds.hi.store.infra.gateway.entity.TagEntity;
|
||||
import com.ktds.hi.store.infra.gateway.repository.TagJpaRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 태그 리포지토리 어댑터 클래스
|
||||
* TagRepositoryPort를 구현하여 태그 데이터 액세스 기능을 제공
|
||||
*
|
||||
* @author 하이오더 개발팀
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class TagRepositoryAdapter implements TagRepositoryPort {
|
||||
|
||||
private final TagJpaRepository tagJpaRepository;
|
||||
|
||||
@Override
|
||||
public List<Tag> findAllActiveTags() {
|
||||
log.info("활성화된 모든 태그 조회");
|
||||
|
||||
List<TagEntity> entities = tagJpaRepository.findByIsActiveTrueOrderByTagName();
|
||||
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Tag> findTagById(Long tagId) {
|
||||
log.info("태그 ID로 태그 조회: tagId={}", tagId);
|
||||
|
||||
return tagJpaRepository.findById(tagId)
|
||||
.filter(entity -> Boolean.TRUE.equals(entity.getIsActive()))
|
||||
.map(this::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Tag> findTagByName(String tagName) {
|
||||
log.info("태그명으로 태그 조회: tagName={}", tagName);
|
||||
|
||||
return tagJpaRepository.findByTagNameAndIsActiveTrue(tagName)
|
||||
.map(this::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tag> findTopClickedTags() {
|
||||
log.info("가장 많이 클릭된 상위 5개 태그 조회");
|
||||
|
||||
List<TagEntity> entities = tagJpaRepository.findTop5ByOrderByClickCountDesc(
|
||||
PageRequest.of(0, 5)
|
||||
);
|
||||
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tag incrementTagClickCount(Long tagId) {
|
||||
log.info("태그 클릭 수 증가: tagId={}", tagId);
|
||||
|
||||
TagEntity entity = tagJpaRepository.findById(tagId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("태그를 찾을 수 없습니다: " + tagId));
|
||||
|
||||
entity.incrementClickCount();
|
||||
TagEntity saved = tagJpaRepository.save(entity);
|
||||
|
||||
return toDomain(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tag saveTag(Tag tag) {
|
||||
log.info("태그 저장: tagName={}", tag.getTagName());
|
||||
|
||||
TagEntity entity = toEntity(tag);
|
||||
TagEntity saved = tagJpaRepository.save(entity);
|
||||
|
||||
return toDomain(saved);
|
||||
}
|
||||
|
||||
/**
|
||||
* 엔티티를 도메인으로 변환
|
||||
*/
|
||||
private Tag toDomain(TagEntity entity) {
|
||||
return Tag.builder()
|
||||
.id(entity.getId())
|
||||
.tagName(entity.getTagName())
|
||||
.tagCategory(entity.getTagCategory())
|
||||
.tagColor(entity.getTagColor())
|
||||
.sortOrder(entity.getSortOrder())
|
||||
.isActive(entity.getIsActive())
|
||||
.clickCount(entity.getClickCount())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 도메인을 엔티티로 변환
|
||||
*/
|
||||
private TagEntity toEntity(Tag domain) {
|
||||
return TagEntity.builder()
|
||||
.id(domain.getId())
|
||||
.tagName(domain.getTagName())
|
||||
.tagCategory(domain.getTagCategory())
|
||||
.tagColor(domain.getTagColor())
|
||||
.sortOrder(domain.getSortOrder())
|
||||
.isActive(domain.getIsActive())
|
||||
.clickCount(domain.getClickCount())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.ktds.hi.store.infra.gateway.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import com.ktds.hi.store.domain.TagCategory;
|
||||
/**
|
||||
* 태그 엔티티 클래스
|
||||
* 매장 태그 정보를 저장
|
||||
*
|
||||
* @author 하이오더 개발팀
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "tags")
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TagEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "tag_name", nullable = false, length = 50)
|
||||
private String tagName; // 매운맛, 깨끗한, 유제품 등
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "tag_category", nullable = false)
|
||||
private TagCategory tagCategory; // TASTE, ATMOSPHERE, ALLERGY 등
|
||||
|
||||
@Column(name = "tag_color", length = 7)
|
||||
private String tagColor; // #FF5722
|
||||
|
||||
@Column(name = "sort_order")
|
||||
private Integer sortOrder;
|
||||
|
||||
@Column(name = "is_active")
|
||||
@Builder.Default
|
||||
private Boolean isActive = true;
|
||||
|
||||
@Column(name = "click_count")
|
||||
@Builder.Default
|
||||
private Long clickCount = 0L;
|
||||
|
||||
/**
|
||||
* 클릭 수 증가
|
||||
*/
|
||||
public void incrementClickCount() {
|
||||
this.clickCount = this.clickCount != null ? this.clickCount + 1 : 1L;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package com.ktds.hi.store.infra.gateway.repository;
|
||||
|
||||
import com.ktds.hi.store.domain.TagCategory;
|
||||
import com.ktds.hi.store.infra.gateway.entity.TagEntity;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 태그 JPA 리포지토리 인터페이스
|
||||
* 태그 데이터의 CRUD 작업을 담당
|
||||
*
|
||||
* @author 하이오더 개발팀
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@Repository
|
||||
public interface TagJpaRepository extends JpaRepository<TagEntity, Long> {
|
||||
|
||||
/**
|
||||
* 활성화된 태그 목록 조회
|
||||
*/
|
||||
List<TagEntity> findByIsActiveTrueOrderByTagName();
|
||||
|
||||
/**
|
||||
* 태그명으로 조회
|
||||
*/
|
||||
Optional<TagEntity> findByTagNameAndIsActiveTrue(String tagName);
|
||||
|
||||
/**
|
||||
* 카테고리별 태그 조회
|
||||
*/
|
||||
List<TagEntity> findByTagCategoryAndIsActiveTrueOrderByTagName(TagCategory category);
|
||||
|
||||
/**
|
||||
* 클릭 수 기준 상위 태그 조회
|
||||
*/
|
||||
@Query("SELECT t FROM TagEntity t WHERE t.isActive = true ORDER BY t.clickCount DESC")
|
||||
List<TagEntity> findTopClickedTags(PageRequest pageRequest);
|
||||
|
||||
/**
|
||||
* 클릭 수 기준 상위 5개 태그 조회
|
||||
*/
|
||||
@Query("SELECT t FROM TagEntity t WHERE t.isActive = true ORDER BY t.clickCount DESC")
|
||||
List<TagEntity> findTop5ByOrderByClickCountDesc(PageRequest pageRequest);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user