♻️ refactor/product/KIKI-48-Product : 요구사항 수정에 따른 상품 조회 수정#63
♻️ refactor/product/KIKI-48-Product : 요구사항 수정에 따른 상품 조회 수정#63
Conversation
…ch' into feat/product/KIKI-48-Product
- 한글 인코딩을 위한 my.cnf 추가 - 시간대 서울로 설정
Test Results34 tests 34 ✅ 6s ⏱️ Results for commit 99c171c. ♻️ This comment has been updated with latest results. |
- 가격 계산 함수 util 처리
Walkthrough
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Client
participant API as ProductController
participant S as ProductService
participant R as ProductRepository (Mongo)
participant D as ProductDocument
participant DTO as ProductDetailResponse
C->>API: GET /products/{id}
API->>S: getProduct(id)
S->>R: findById(id)
R-->>S: ProductDocument
S->>D: toDomain()
D-->>S: Product (thumbnail, options 등)
rect rgba(200,230,255,0.3)
note over S,DTO: 변경: 옵션 매핑, 가격 문자열 계산
S->>DTO: from(Product)
DTO-->>S: ProductDetailResponse(originalPrice: String, options: List<...>)
end
S-->>API: ProductDetailResponse
API-->>C: 200 OK + JSON
sequenceDiagram
autonumber
participant SYS as App
participant ESD as ProductESDocument
participant ES as Elasticsearch(kikihi.products)
SYS->>ESD: from(Product)
rect rgba(255,240,200,0.4)
note right of ESD: 변경: index=kikihi.products<br/>필드 thumbnail 사용
end
ESD-->>SYS: ESDocument(thumbnail)
SYS->>ES: index/update(document)
ES-->>SYS: ack
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/dev-ci-cd.yml (1)
165-196: 배포 단계에서 docker compose 실행 경로 불일치(치명적)
.env는/home/ubuntu/app/docker/로,docker-compose.yml은/home/ubuntu/app/로 전송한 뒤, SSH 단계에서cd /home/ubuntu/app/docker/로 이동해docker compose를 실행합니다. 이 경우 현재 디렉터리에 compose 파일이 없어 실패합니다(혹은 다른 파일을 참조).해결 옵션 1: compose 파일을
.env와 같은 디렉터리로 보냅니다.- - name: 3. docker-compose.yml 전달 + - name: 3. docker-compose.yml 전달 uses: appleboy/scp-action@master with: host: ${{ secrets.EC2_PUBLIC_IP }} username: ${{ secrets.SSH_USER }} key: ${{ secrets.EC2_PRIVATE_KEY }} - source: "./docker/docker-compose.yml" - target: "/home/ubuntu/app/" + source: "./docker/docker-compose.yml" + target: "/home/ubuntu/app/docker/"해결 옵션 2: 실행 경로를 compose 파일 위치로 바꾸거나, -f 옵션을 명시합니다.
- cd /home/ubuntu/app/docker/ - docker compose pull spring - docker compose up -d spring + cd /home/ubuntu/app/ + docker compose --env-file ./docker/.env -f ./docker-compose.yml pull spring + docker compose --env-file ./docker/.env -f ./docker-compose.yml up -d spring
🧹 Nitpick comments (17)
.github/workflows/dev-ci-cd.yml (2)
12-12: 브랜치 목록 YAML 스타일 개선 및 린트 경고 해소YAMLlint 경고(too few spaces after comma)를 해소하고, 유지보수성을 높이기 위해 플로우 시퀀스 대신 블록 시퀀스로 표기하는 것을 권장합니다.
아래처럼 변경하면 가독성과 편집 편의성이 개선됩니다.
-on: - push: - branches: [ "develop","feat/product/KIKI-61-ElasticSearch"] +on: + push: + branches: + - develop + - feat/product/KIKI-61-ElasticSearch
153-156: CD 잡(배포) 실행 조건 가드 추가 권장Line 12에서 feature 브랜치에 대한 push 트리거를 추가했기 때문에, 현재 설정으로는 feature 브랜치에서도 CD가 실행됩니다. develop만 배포 대상으로 의도하셨다면 CD 잡에 조건을 추가하세요.
CD: needs: CI runs-on: ubuntu-22.04 environment: Oracle + if: github.ref == 'refs/heads/develop' + concurrency: + group: deploy-oracle-${{ github.ref }} + cancel-in-progress: true
- if: develop 브랜치에서만 배포
- concurrency: 동일 브랜치 중복 배포 러닝 방지
src/main/java/site/kikihi/custom/platform/adapter/out/elasticSearch/ProductESDocument.java (3)
26-26: indexName 변경에 따른 운영 영향 점검(에일리어스 권장)
indexName = "kikihi.products"로 변경되었습니다. 기존products인덱스/템플릿/파이프라인/monstache 설정과의 호환성, 재색인 계획을 점검하세요. 코드 변경 없이 롤오버/재색인을 유연하게 하려면 고정 인덱스명 대신 에일리어스(예:products)를 사용하고 실제 물리 인덱스는 버전 접미사로 운용하는 패턴을 권장합니다.필요 시:
- 신규 물리 인덱스:
kikihi.products-000001- write alias:
products->kikihi.products-000001- 재색인 후 alias 스위치
45-47: thumbnail 필드 매핑 타입: Text → Keyword 권장
thumbnail은 URL/식별자 성격으로 전문 검색보다는 정확히 일치하는 값 비교/집계가 일반적입니다.FieldType.Keyword가 더 적합합니다. 현재Text는 불필요한 분석기로 인해 저장 공간/인덱싱 비용이 늘 수 있습니다.- @Field(type = FieldType.Text) - private String thumbnail; + @Field(type = FieldType.Keyword) + private String thumbnail;
54-56: options 매핑: Object → Nested 검토
List<Map<String, Object>> options를 검색 쿼리에서 내부 필드 단위로 조건 결합(동일 객체 내 필드의 AND 일치 등)할 계획이면FieldType.Nested가 필요합니다.Object는 배열의 객체 간 필드 상호 간섭(cross-object matches)이 발생할 수 있습니다.- @Field(type = FieldType.Object) - private List<Map<String, Object>> options; // + @Field(type = FieldType.Nested) + private List<Map<String, Object>> options; //주의: 매핑 변경은 재색인이 필요합니다. 실제 질의 패턴을 확인 후 결정하세요.
src/main/java/site/kikihi/custom/platform/adapter/out/mongo/product/ProductDocument.java (2)
35-36: @field("thumbnail") 명시 여부(선택사항)필드명과 저장 필드명이 동일하여 이 어노테이션은 기능적으로는 불필요합니다. 다만 ES와의 명시적 정합성을 위해 남겨둘 수도 있습니다. 팀 컨벤션에 따라 일관되게 적용 여부를 결정하세요.
44-45: 동적 Map 기반 options: 유효성/스키마 관리 제안
List<Map<String, Object>>로 변경되어 유연성은 커졌지만, 타입 안정성/검증/문서화가 약해집니다. API 레벨에서 최소 키 세트(예: name, values, extraPrice 등)에 대한 검증과 스키마 문서화를 권장합니다. 중장기적으로는 얇은 POJO/record(예:OptionItem)로 감싸는 어댑터 계층을 두면 도메인 모델의 안정성이 좋아집니다.원하시면 Bean Validation(예:
@Validated+ 커스텀 Validator`) 기반의 옵션 유효성 검증 템플릿을 제공해 드릴게요.Also applies to: 60-60
docker/docker-compose.yml (5)
63-64: MySQL 설정 마운트 위치 조정 권장현재
/etc/my.cnf로 마운트하면 이미지 기본 설정을 완전히 대체할 수 있습니다. 드롭인 방식(/etc/mysql/conf.d/*.cnf)을 쓰면 기본 설정을 보존하면서 우리 설정만 추가할 수 있어 안전합니다.- - ./mysql/my.cnf:/etc/my.cnf + - ./mysql/my.cnf:/etc/mysql/conf.d/kikihi.cnf참고:
/etc/my.cnf를 대체해야 한다면, 기본 include 디렉터리(!includedir /etc/mysql/conf.d/등)를 my.cnf에 명시적으로 추가하는 것을 권장합니다.
69-69: TZ 설정과 MySQL time zone 일관성 확인컨테이너
TZ=Asia/Seoul은 OS 시간대만 설정합니다. MySQL 서버의default_time_zone(my.cnf)도 설정되어 있다면 서로 일관되게 유지하세요. MySQL이 이름 기반 시간대를 쓰려면 timezone 테이블 로딩이 필요합니다(없다면+09:00오프셋 사용 고려).
178-178: mem_limit 환경변수 기본값 제공
.env에ES_MEM_LIMIT/KB_MEM_LIMIT가 누락될 경우 파싱 실패가 발생할 수 있습니다. 기본값을 지정해 안전하게 처리하세요.- mem_limit: ${ES_MEM_LIMIT} + mem_limit: ${ES_MEM_LIMIT:-1g} ... - mem_limit: ${KB_MEM_LIMIT} + mem_limit: ${KB_MEM_LIMIT:-1g}Also applies to: 227-227
138-139: 오타: monstahce → monstache사용자 생성/패스워드 설정 부분에서
monstahce_system오타가 있습니다. 보안 유저 명칭과 일치하도록 수정하세요.- echo "Setting monstahce_system password"; - until curl -s -X POST --cacert config/certs/ca/ca.crt -u "elastic:${ELASTIC_PASSWORD}" -H "Content-Type: application/json" https://es01:9200/_security/user/monstahce_system/_password -d "{\"password\":\"${MONSTACHE_PASSWORD}\"}" | grep -q "^{}"; do sleep 10; done; + echo "Setting monstache_system password"; + until curl -s -X POST --cacert config/certs/ca/ca.crt -u "elastic:${ELASTIC_PASSWORD}" -H "Content-Type: application/json" https://es01:9200/_security/user/monstache_system/_password -d "{\"password\":\"${MONSTACHE_PASSWORD}\"}" | grep -q "^{}"; do sleep 10; done;
5-5: latest 태그 사용 지양(재현성/롤백성)
kikihistore/server:latest는 재현성/롤백성을 해칩니다. CI 빌드 번호나 Git SHA 기반의 불변 태그를 권장합니다. CD에서latestpull로 인해 의도치 않은 버전이 배포될 수 있습니다.docker/mysql/my.cnf (3)
9-10: Collation 최신화 제안MySQL 8.0에서는
utf8mb4_0900_ai_ci가 일반적으로 더 정확하고 현대적인 정렬을 제공합니다. 특별한 호환성 이슈가 없다면 교체를 고려하세요.-collation-server = utf8mb4_general_ci +collation-server = utf8mb4_0900_ai_ci
12-14: default-time-zone 설정 주의(타임존 테이블 의존성)
'Asia/Seoul'과 같은 이름 기반 설정은 MySQL 타임존 테이블이 로딩되어 있어야 합니다. 그렇지 않으면 적용 실패/경고가 발생할 수 있습니다. 운영 단순화를 위해 오프셋 기반 설정으로 전환하거나, 초기화 스크립트로 타임존 테이블을 로딩하세요.간단 대안:
-default-time-zone = 'Asia/Seoul' +default_time_zone = '+09:00'또는 compose 초기화 스크립트(
docker-entrypoint-initdb.d/)에서 타임존 로딩:mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p"${MYSQL_ROOT_PASSWORD}" mysql
1-6: /etc/my.cnf 대체 시 include 유지 권장현재 compose에서
/etc/my.cnf로 직접 마운트하는 경우, 이미지 기본 my.cnf의 include 지시문을 잃을 수 있습니다. 안전하게 기본 설정을 포함하도록 include를 명시하세요(섹션 밖 최상단/하단).예시:
[client] default-character-set = utf8mb4 [mysql] default-character-set = utf8mb4 [mysqld] # Character set & collation character-set-server = utf8mb4 -collation-server = utf8mb4_general_ci +collation-server = utf8mb4_0900_ai_ci @@ max_connections = 200 + +!includedir /etc/mysql/conf.d/ +!includedir /etc/mysql/mysql.conf.d/Also applies to: 7-21
src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java (2)
141-170: ProductOptions.from 메서드의 null 안전성 개선Line 156에서
Double.valueOf를 사용하지만 null 처리가 없어 NPE가 발생할 수 있습니다. 또한 vendors 처리 로직이 복잡합니다.public static List<ProductOptions> from(Product product) { List<Map<String, Object>> productOptions = Optional.ofNullable(product.getOptions()) .orElse(Collections.emptyList()); return productOptions.stream() /// option_name 없는 건 필터링 .filter(option -> option.get("option_name") != null) .map(option -> { /// 옵션 명 가져오기 String optionName = (String) option.get("option_name"); Double price = null; /// 가격이 있다면 if (option.get("main_price") != null) { - price = Double.valueOf(option.get("main_price").toString()); + try { + price = Double.valueOf(option.get("main_price").toString()); + } catch (NumberFormatException e) { + // 로그 남기거나 기본값 설정 + price = null; + } } String site = null; String siteUrl = null; Object vendorsObj = option.get("vendors"); if (vendorsObj instanceof Map<?, ?> vendors) { site = (String) vendors.get("shop"); siteUrl = (String) vendors.get("url"); } return of(optionName, price, site, siteUrl); }) .toList(); }
44-45: 스키마 예시와 실제 출력 형식 불일치
@Schema어노테이션의 예시가 "599000 ~"인데 실제 코드에서는 "599000원 ~" 형식으로 출력됩니다.- @Schema(description = "최저가 가격부터", example = "599000 ~") + @Schema(description = "최저가 가격부터", example = "599000원 ~") String originalPrice,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (11)
.github/workflows/dev-ci-cd.yml(1 hunks)docker/dev-docker-compose.yml(1 hunks)docker/docker-compose.yml(3 hunks)docker/mysql/my.cnf(1 hunks)src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java(4 hunks)src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/BookmarkControllerSpec.java(1 hunks)src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/ProductControllerSpec.java(1 hunks)src/main/java/site/kikihi/custom/platform/adapter/out/elasticSearch/ProductESDocument.java(4 hunks)src/main/java/site/kikihi/custom/platform/adapter/out/mongo/product/ProductDocument.java(3 hunks)src/test/java/site/kikihi/custom/platform/application/service/ProductServiceIntTest.java(0 hunks)src/test/java/site/kikihi/custom/platform/domain/product/ProductFixtures.java(8 hunks)
💤 Files with no reviewable changes (1)
- src/test/java/site/kikihi/custom/platform/application/service/ProductServiceIntTest.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/site/kikihi/custom/platform/adapter/out/elasticSearch/ProductESDocument.java (1)
src/main/java/site/kikihi/custom/platform/adapter/out/mongo/product/ProductDocument.java (1)
Document(17-64)
🪛 YAMLlint (1.37.1)
.github/workflows/dev-ci-cd.yml
[warning] 12-12: too few spaces after comma
(commas)
🔇 Additional comments (5)
src/main/java/site/kikihi/custom/platform/adapter/out/elasticSearch/ProductESDocument.java (1)
73-75: 필드명 변경 전파 확인 완료
다음 검사를 통해 더 이상thumbnailUrl또는 구 인덱스명(products)에 대한 참조가 없습니다. 따라서 필드명 변경(thumbnailUrl→thumbnail)이 전파된 것으로 판단됩니다.검사에 사용한 스크립트 예시:
#!/bin/bash # 잔여 thumbnailUrl 참조 확인 rg -nC2 thumbnailUrl # @Document(indexName="products") 잔여 확인 rg -nC2 '@Document.*indexName.*"products"'docker/dev-docker-compose.yml (2)
21-21: 타임존 설정이 올바르게 적용되었습니다MySQL 컨테이너에 한국 시간대를 설정한 것은 적절합니다. 이는
my.cnf의 타임존 설정과 일치합니다.
15-15: MySQL 설정 파일 존재 및 내용 확인 완료
docker/mysql/my.cnf파일이 정상적으로 존재하며, 다음과 같은 설정이 포함되어 있습니다:
- 기본 문자셋: utf8mb4
- 서버 타임존: Asia/Seoul
- 호스트명 네임 해석 비활성화(skip-host-cache, skip-name-resolve)
- 최대 연결 수(max_connections): 200
따라서 마운트 경로 및 설정 파일 관련 문제는 없습니다. 계속 진행해 주세요.
src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/BookmarkControllerSpec.java (1)
91-91: Swagger 예시 Product ID 업데이트 확인Product ID가
6896ed675198cf586e933d6c로 변경되었습니다. 이 ID가 실제 테스트 환경에서 유효한지 확인이 필요합니다.src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/ProductControllerSpec.java (1)
31-31: Swagger 예시 ID 일관성 확인Product ID 예시가
BookmarkControllerSpec.java의 변경사항과 일치하게 업데이트되었습니다.
| private static String getPrice(Product product) { | ||
|
|
||
| // 옵션 Optional 처리 | ||
| List<Map<String, Object>> productOptions = Optional.ofNullable(product.getOptions()) | ||
| .orElse(Collections.emptyList()); | ||
|
|
||
| // 옵션이 없다면 기본 가격 제공 | ||
| if (productOptions.isEmpty()) { | ||
| return (int) product.getPrice() + "원"; | ||
| } | ||
|
|
||
| // 옵션 중 최저가 찾기 | ||
| OptionalDouble minPrice = productOptions.stream() | ||
| .map(option -> option.get("main_price")) | ||
| .filter(Objects::nonNull) | ||
| .mapToDouble(price -> Double.parseDouble(price.toString())) | ||
| .min(); | ||
|
|
||
| // 최저가 있으면 옵션 가격으로, 없으면 기본 가격으로 | ||
| if (minPrice.isPresent()) { | ||
| return (int) minPrice.getAsDouble() + "원 ~"; | ||
| } else { | ||
| return (int) product.getPrice() + "원"; | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
가격 계산 로직의 견고성 개선 필요
getPrice 메서드에서 몇 가지 개선이 필요합니다:
- Line 102, 114, 116: 정수 변환 시 소수점 이하가 손실됩니다
- Line 109:
Double.parseDouble이 NumberFormatException을 발생시킬 수 있습니다 - 공백 처리가 일관되지 않습니다 (102번 줄은 "원", 114번 줄은 "원 ~")
private static String getPrice(Product product) {
// 옵션 Optional 처리
List<Map<String, Object>> productOptions = Optional.ofNullable(product.getOptions())
.orElse(Collections.emptyList());
// 옵션이 없다면 기본 가격 제공
if (productOptions.isEmpty()) {
- return (int) product.getPrice() + "원";
+ return String.format("%.0f원", product.getPrice());
}
// 옵션 중 최저가 찾기
OptionalDouble minPrice = productOptions.stream()
.map(option -> option.get("main_price"))
.filter(Objects::nonNull)
- .mapToDouble(price -> Double.parseDouble(price.toString()))
+ .mapToDouble(price -> {
+ try {
+ return Double.parseDouble(price.toString());
+ } catch (NumberFormatException e) {
+ return Double.MAX_VALUE;
+ }
+ })
+ .filter(price -> price != Double.MAX_VALUE)
.min();
// 최저가 있으면 옵션 가격으로, 없으면 기본 가격으로
if (minPrice.isPresent()) {
- return (int) minPrice.getAsDouble() + "원 ~";
+ return String.format("%.0f원 ~", minPrice.getAsDouble());
} else {
- return (int) product.getPrice() + "원";
+ return String.format("%.0f원", product.getPrice());
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private static String getPrice(Product product) { | |
| // 옵션 Optional 처리 | |
| List<Map<String, Object>> productOptions = Optional.ofNullable(product.getOptions()) | |
| .orElse(Collections.emptyList()); | |
| // 옵션이 없다면 기본 가격 제공 | |
| if (productOptions.isEmpty()) { | |
| return (int) product.getPrice() + "원"; | |
| } | |
| // 옵션 중 최저가 찾기 | |
| OptionalDouble minPrice = productOptions.stream() | |
| .map(option -> option.get("main_price")) | |
| .filter(Objects::nonNull) | |
| .mapToDouble(price -> Double.parseDouble(price.toString())) | |
| .min(); | |
| // 최저가 있으면 옵션 가격으로, 없으면 기본 가격으로 | |
| if (minPrice.isPresent()) { | |
| return (int) minPrice.getAsDouble() + "원 ~"; | |
| } else { | |
| return (int) product.getPrice() + "원"; | |
| } | |
| } | |
| private static String getPrice(Product product) { | |
| // 옵션 Optional 처리 | |
| List<Map<String, Object>> productOptions = Optional.ofNullable(product.getOptions()) | |
| .orElse(Collections.emptyList()); | |
| // 옵션이 없다면 기본 가격 제공 | |
| if (productOptions.isEmpty()) { | |
| return String.format("%.0f원", product.getPrice()); | |
| } | |
| // 옵션 중 최저가 찾기 | |
| OptionalDouble minPrice = productOptions.stream() | |
| .map(option -> option.get("main_price")) | |
| .filter(Objects::nonNull) | |
| .mapToDouble(price -> { | |
| try { | |
| return Double.parseDouble(price.toString()); | |
| } catch (NumberFormatException e) { | |
| return Double.MAX_VALUE; | |
| } | |
| }) | |
| .filter(price -> price != Double.MAX_VALUE) | |
| .min(); | |
| // 최저가 있으면 옵션 가격으로, 없으면 기본 가격으로 | |
| if (minPrice.isPresent()) { | |
| return String.format("%.0f원 ~", minPrice.getAsDouble()); | |
| } else { | |
| return String.format("%.0f원", product.getPrice()); | |
| } | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java
around lines 94 to 118, the getPrice method currently truncates decimals by
casting to int, risks NumberFormatException from Double.parseDouble, and uses
inconsistent suffix spacing ("원" vs "원 ~"); change the logic to safely parse
numeric option values (handle Number, BigDecimal, or String) using a try/catch
or a safe parse helper that falls back to product.getPrice() on parse failure,
compute the displayed price without losing cents (use BigDecimal or double and
format to show no decimal if whole number otherwise show decimal as needed), and
normalize the suffix/spacing consistently (e.g., "원" for single price and "원 ~"
with same spacing for range); ensure nulls are handled and the method always
returns a well-formatted string even when parsing fails.
| Map<String, Object> options = new HashMap<>(); | ||
| options.put("option_name", "화이트"); | ||
| options.put("option_name", "블랙"); | ||
| options.put("option_name", "실버"); |
There was a problem hiding this comment.
중복된 Map key로 인한 데이터 손실 문제
모든 fixture 메서드에서 동일한 key "option_name"을 세 번 put하고 있어, 최종적으로는 마지막 값인 "실버"만 남게 됩니다. 의도한 대로 세 가지 옵션을 모두 포함하려면 List 구조로 변경해야 합니다.
- Map<String, Object> options = new HashMap<>();
- options.put("option_name", "화이트");
- options.put("option_name", "블랙");
- options.put("option_name", "실버");
+ List<Map<String, Object>> optionsList = new ArrayList<>();
+
+ Map<String, Object> option1 = new HashMap<>();
+ option1.put("option_name", "화이트");
+ optionsList.add(option1);
+
+ Map<String, Object> option2 = new HashMap<>();
+ option2.put("option_name", "블랙");
+ optionsList.add(option2);
+
+ Map<String, Object> option3 = new HashMap<>();
+ option3.put("option_name", "실버");
+ optionsList.add(option3);그리고 builder 호출 부분도 수정:
- .options(List.of(options))
+ .options(optionsList)Also applies to: 36-39, 60-63, 84-87
📌 작업한 내용
MySQL 도커 컴포즈 설정 수정
Swagger 상품 기본 데이터 수정
상품 관련 기능 수정
DTO 내 옵션 출력 로직 수정
DTO 내 유사 상품 추천 분리
🔍 참고 사항
🖼️ 스크린샷
🔗 관련 이슈
#62
✅ 체크리스트
Summary by CodeRabbit
신기능
리팩터
문서
작업